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    
017    package org.jetbrains.jet.lang.types;
018    
019    import com.google.common.collect.Maps;
020    import com.google.common.collect.Multimap;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
024    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
025    import org.jetbrains.jet.utils.CommonSuppliers;
026    
027    import java.util.HashMap;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    
032    public 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 Iterable<out T>
055              trait Collection<out E>: Iterable<E>
056              trait MyFooCollection<F>: Collection<Foo<F>>
057    
058          For MyFooCollection<out CharSequence>, the following multimap will be returned:
059              T declared in Iterable -> Foo<out CharSequence>
060              E declared in Collection -> Foo<out CharSequence>
061              F declared in MyFooCollection -> out CharSequence
062         */
063        @NotNull
064        public static Multimap<TypeConstructor, TypeProjection> buildDeepSubstitutionMultimap(@NotNull JetType type) {
065            Multimap<TypeConstructor, TypeProjection> fullSubstitution = CommonSuppliers.newLinkedHashSetHashSetMultimap();
066            Map<TypeConstructor, TypeProjection> substitution = Maps.newHashMap();
067            TypeSubstitutor typeSubstitutor = TypeSubstitutor.create(substitution);
068            // we use the mutability of the map here
069            fillInDeepSubstitutor(type, typeSubstitutor, substitution, fullSubstitution);
070            return fullSubstitution;
071        }
072    
073        // we use the mutability of the substitution map here
074        private static void fillInDeepSubstitutor(@NotNull JetType context, @NotNull TypeSubstitutor substitutor, @NotNull Map<TypeConstructor, TypeProjection> substitution, @Nullable Multimap<TypeConstructor, TypeProjection> fullSubstitution) {
075            List<TypeParameterDescriptor> parameters = context.getConstructor().getParameters();
076            List<TypeProjection> arguments = context.getArguments();
077    
078            if (parameters.size() != arguments.size()) {
079                throw new IllegalStateException();
080            }
081    
082            for (int i = 0; i < arguments.size(); i++) {
083                TypeProjection argument = arguments.get(i);
084                TypeParameterDescriptor parameter = parameters.get(i);
085    
086                TypeProjection substitute = substitutor.substitute(argument);
087                assert substitute != null;
088                substitution.put(parameter.getTypeConstructor(), substitute);
089                if (fullSubstitution != null) {
090                    fullSubstitution.put(parameter.getTypeConstructor(), substitute);
091                }
092            }
093            if (KotlinBuiltIns.getInstance().isNothingOrNullableNothing(context)) return;
094            for (JetType supertype : context.getConstructor().getSupertypes()) {
095                fillInDeepSubstitutor(supertype, substitutor, substitution, fullSubstitution);
096            }
097        }
098    
099        @NotNull
100        public static Map<TypeConstructor, TypeProjection> buildSubstitutionContext(@NotNull List<TypeParameterDescriptor> parameters, @NotNull List<? extends TypeProjection> contextArguments) {
101            Map<TypeConstructor, TypeProjection> parameterValues = new HashMap<TypeConstructor, TypeProjection>();
102            fillInSubstitutionContext(parameters, contextArguments, parameterValues);
103            return parameterValues;
104        }
105    
106        private static void fillInSubstitutionContext(List<TypeParameterDescriptor> parameters, List<? extends TypeProjection> contextArguments, Map<TypeConstructor, TypeProjection> parameterValues) {
107            if (parameters.size() != contextArguments.size()) {
108                throw new IllegalArgumentException("type parameter count != context arguments: \n" +
109                                                   "parameters=" + parameters + "\n" +
110                                                   "contextArgs=" + contextArguments);
111            }
112            for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) {
113                TypeParameterDescriptor parameter = parameters.get(i);
114                TypeProjection value = contextArguments.get(i);
115                parameterValues.put(parameter.getTypeConstructor(), value);
116            }
117        }
118    
119        @NotNull
120        public static TypeProjection makeStarProjection(@NotNull TypeParameterDescriptor parameterDescriptor) {
121            return new TypeProjectionImpl(parameterDescriptor.getVariance() == Variance.OUT_VARIANCE
122                                      ? Variance.INVARIANT
123                                      : Variance.OUT_VARIANCE, parameterDescriptor.getUpperBoundsAsType());
124        }
125    
126        public static boolean hasUnsubstitutedTypeParameters(JetType type) {
127            if (type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor) {
128                return true;
129            }
130    
131            for(TypeProjection proj : type.getArguments()) {
132                if (hasUnsubstitutedTypeParameters(proj.getType())) {
133                    return true;
134                }
135            }
136    
137            return false;
138        }
139    
140        public static Map<TypeConstructor, TypeProjection> removeTrivialSubstitutions(Map<TypeConstructor, TypeProjection> context) {
141            Map<TypeConstructor, TypeProjection> clean = Maps.newHashMap(context);
142            boolean changed = false;
143            for (Iterator<Map.Entry<TypeConstructor, TypeProjection>> iterator = clean.entrySet().iterator(); iterator.hasNext(); ) {
144                Map.Entry<TypeConstructor, TypeProjection> entry = iterator.next();
145                TypeConstructor key = entry.getKey();
146                TypeProjection value = entry.getValue();
147                if (key == value.getType().getConstructor() && value.getProjectionKind() == Variance.INVARIANT) {
148                    iterator.remove();
149                    changed = true;
150                }
151            }
152            return changed ? clean : context;
153        }
154    
155        public static void assertNotImmediatelyRecursive(Map<TypeConstructor, TypeProjection> context) {
156            // Make sure we never replace a T with "Foo<T>" or something similar,
157            // because the substitution will not terminate in this case
158            // This check is not complete. It does not find cases like
159            //    T -> Foo<T1>
160            //    T -> Bar<T>
161    
162            for (Map.Entry<TypeConstructor, TypeProjection> entry : context.entrySet()) {
163                TypeConstructor key = entry.getKey();
164                TypeProjection value = entry.getValue();
165                if (TypeUtils.typeConstructorUsedInType(key, value.getType())) {
166                    throw new IllegalStateException("Immediately recursive substitution: " + context + "\nProblematic parameter: " + key + " -> " + value);
167                }
168            }
169        }
170    }