001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.types;
018
019import com.google.common.collect.Maps;
020import com.google.common.collect.Multimap;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
024import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
025import org.jetbrains.jet.util.CommonSuppliers;
026
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031
032public class SubstitutionUtils {
033    @NotNull
034    public static Map<TypeConstructor, TypeProjection> buildSubstitutionContext(@NotNull  JetType context) {
035        return buildSubstitutionContext(context.getConstructor().getParameters(), context.getArguments());
036    }
037
038    /**
039     * Builds a context with all the supertypes' parameters substituted
040     */
041    @NotNull
042    public static TypeSubstitutor buildDeepSubstitutor(@NotNull JetType type) {
043        Map<TypeConstructor, TypeProjection> substitution = Maps.newHashMap();
044        TypeSubstitutor typeSubstitutor = TypeSubstitutor.create(substitution);
045        // we use the mutability of the map here
046        fillInDeepSubstitutor(type, typeSubstitutor, substitution, null);
047        return typeSubstitutor;
048    }
049
050    /*
051      For each supertype of a given type, we map type parameters to type arguments.
052
053      For instance, we have the following class hierarchy:
054          trait Hashable
055          trait Iterable<out T>
056          trait Collection<out E>: Iterable<E>, Hashable
057          trait MyFooCollection<F>: Collection<Foo<F>>
058
059      For MyFunCollection<out CharSequence>, the following multimap will be returned:
060          T declared in Iterable -> Foo<out CharSequence>
061          E declared in Collection -> Foo<out CharSequence>
062          F declared in MyFooCollection -> out CharSequence
063     */
064    @NotNull
065    public static Multimap<TypeConstructor, TypeProjection> buildDeepSubstitutionMultimap(@NotNull JetType type) {
066        Multimap<TypeConstructor, TypeProjection> fullSubstitution = CommonSuppliers.newLinkedHashSetHashSetMultimap();
067        Map<TypeConstructor, TypeProjection> substitution = Maps.newHashMap();
068        TypeSubstitutor typeSubstitutor = TypeSubstitutor.create(substitution);
069        // we use the mutability of the map here
070        fillInDeepSubstitutor(type, typeSubstitutor, substitution, fullSubstitution);
071        return fullSubstitution;
072    }
073
074    // we use the mutability of the substitution map here
075    private static void fillInDeepSubstitutor(@NotNull JetType context, @NotNull TypeSubstitutor substitutor, @NotNull Map<TypeConstructor, TypeProjection> substitution, @Nullable Multimap<TypeConstructor, TypeProjection> fullSubstitution) {
076        List<TypeParameterDescriptor> parameters = context.getConstructor().getParameters();
077        List<TypeProjection> arguments = context.getArguments();
078
079        if (parameters.size() != arguments.size()) {
080            throw new IllegalStateException();
081        }
082
083        for (int i = 0; i < arguments.size(); i++) {
084            TypeProjection argument = arguments.get(i);
085            TypeParameterDescriptor parameter = parameters.get(i);
086
087            TypeProjection substitute = substitutor.substitute(argument);
088            assert substitute != null;
089            substitution.put(parameter.getTypeConstructor(), substitute);
090            if (fullSubstitution != null) {
091                fullSubstitution.put(parameter.getTypeConstructor(), substitute);
092            }
093        }
094        if (KotlinBuiltIns.getInstance().isNothingOrNullableNothing(context)) return;
095        for (JetType supertype : context.getConstructor().getSupertypes()) {
096            fillInDeepSubstitutor(supertype, substitutor, substitution, fullSubstitution);
097        }
098    }
099
100    @NotNull
101    public static Map<TypeConstructor, TypeProjection> buildSubstitutionContext(@NotNull List<TypeParameterDescriptor> parameters, @NotNull List<TypeProjection> contextArguments) {
102        Map<TypeConstructor, TypeProjection> parameterValues = new HashMap<TypeConstructor, TypeProjection>();
103        fillInSubstitutionContext(parameters, contextArguments, parameterValues);
104        return parameterValues;
105    }
106
107    private static void fillInSubstitutionContext(List<TypeParameterDescriptor> parameters, List<TypeProjection> contextArguments, Map<TypeConstructor, TypeProjection> parameterValues) {
108        if (parameters.size() != contextArguments.size()) {
109            throw new IllegalArgumentException("type parameter count != context arguments");
110        }
111        for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) {
112            TypeParameterDescriptor parameter = parameters.get(i);
113            TypeProjection value = contextArguments.get(i);
114            parameterValues.put(parameter.getTypeConstructor(), value);
115        }
116    }
117
118    @NotNull
119    public static TypeProjection makeStarProjection(@NotNull TypeParameterDescriptor parameterDescriptor) {
120        return new TypeProjection(parameterDescriptor.getVariance() == Variance.OUT_VARIANCE
121                                  ? Variance.INVARIANT
122                                  : Variance.OUT_VARIANCE, parameterDescriptor.getUpperBoundsAsType());
123    }
124
125    public static boolean hasUnsubstitutedTypeParameters(JetType type) {
126        if (type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor) {
127            return true;
128        }
129
130        for(TypeProjection proj : type.getArguments()) {
131            if (hasUnsubstitutedTypeParameters(proj.getType())) {
132                return true;
133            }
134        }
135
136        return false;
137    }
138
139    public static Map<TypeConstructor, TypeProjection> removeTrivialSubstitutions(Map<TypeConstructor, TypeProjection> context) {
140        Map<TypeConstructor, TypeProjection> clean = Maps.newHashMap(context);
141        boolean changed = false;
142        for (Iterator<Map.Entry<TypeConstructor, TypeProjection>> iterator = clean.entrySet().iterator(); iterator.hasNext(); ) {
143            Map.Entry<TypeConstructor, TypeProjection> entry = iterator.next();
144            TypeConstructor key = entry.getKey();
145            TypeProjection value = entry.getValue();
146            if (key == value.getType().getConstructor() && value.getProjectionKind() == Variance.INVARIANT) {
147                iterator.remove();
148                changed = true;
149            }
150        }
151        return changed ? clean : context;
152    }
153
154    public static void assertNotImmediatelyRecursive(Map<TypeConstructor, TypeProjection> context) {
155        // Make sure we never replace a T with "Foo<T>" or something similar,
156        // because the substitution will not terminate in this case
157        // This check is not complete. It does not find cases like
158        //    T -> Foo<T1>
159        //    T -> Bar<T>
160
161        for (Map.Entry<TypeConstructor, TypeProjection> entry : context.entrySet()) {
162            TypeConstructor key = entry.getKey();
163            TypeProjection value = entry.getValue();
164            if (TypeUtils.typeConstructorUsedInType(key, value.getType())) {
165                throw new IllegalStateException("Immediately recursive substitution: " + context + "\nProblematic parameter: " + key + " -> " + value);
166            }
167        }
168    }
169}