001    /*
002     * Copyright 2010-2016 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.kotlin.types;
018    
019    import kotlin.jvm.functions.Function1;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
023    import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
024    import org.jetbrains.kotlin.descriptors.annotations.Annotations;
025    import org.jetbrains.kotlin.descriptors.annotations.CompositeAnnotations;
026    import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations;
027    import org.jetbrains.kotlin.name.FqName;
028    import org.jetbrains.kotlin.resolve.calls.inference.CapturedTypeConstructorKt;
029    import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
030    import org.jetbrains.kotlin.types.typesApproximation.CapturedTypeApproximationKt;
031    
032    import java.util.ArrayList;
033    import java.util.List;
034    import java.util.Map;
035    
036    public class TypeSubstitutor {
037    
038        private static final int MAX_RECURSION_DEPTH = 100;
039    
040        public static final TypeSubstitutor EMPTY = create(TypeSubstitution.EMPTY);
041    
042        private static final class SubstitutionException extends Exception {
043            public SubstitutionException(String message) {
044                super(message);
045            }
046        }
047    
048        @NotNull
049        public static TypeSubstitutor create(@NotNull TypeSubstitution substitution) {
050            return new TypeSubstitutor(substitution);
051        }
052    
053        @NotNull
054        public static TypeSubstitutor createChainedSubstitutor(@NotNull TypeSubstitution first, @NotNull TypeSubstitution second) {
055            return create(DisjointKeysUnionTypeSubstitution.create(first, second));
056        }
057    
058        @NotNull
059        public static TypeSubstitutor create(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
060            return create(TypeConstructorSubstitution.createByConstructorsMap(substitutionContext));
061        }
062    
063        @NotNull
064        public static TypeSubstitutor create(@NotNull KotlinType context) {
065            return create(TypeConstructorSubstitution.create(context.getConstructor(), context.getArguments()));
066        }
067    
068    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
069    
070        private final @NotNull TypeSubstitution substitution;
071    
072        protected TypeSubstitutor(@NotNull TypeSubstitution substitution) {
073            this.substitution = substitution;
074        }
075    
076        public boolean isEmpty() {
077            return substitution.isEmpty();
078        }
079    
080        @NotNull
081        public TypeSubstitution getSubstitution() {
082            return substitution;
083        }
084    
085        @NotNull
086        public KotlinType safeSubstitute(@NotNull KotlinType type, @NotNull Variance howThisTypeIsUsed) {
087            if (isEmpty()) {
088                return type;
089            }
090    
091            try {
092                return unsafeSubstitute(new TypeProjectionImpl(howThisTypeIsUsed, type), 0).getType();
093            } catch (SubstitutionException e) {
094                return ErrorUtils.createErrorType(e.getMessage());
095            }
096        }
097    
098        @Nullable
099        public KotlinType substitute(@NotNull KotlinType type, @NotNull Variance howThisTypeIsUsed) {
100            TypeProjection projection = substitute(new TypeProjectionImpl(howThisTypeIsUsed, type));
101            return projection == null ? null : projection.getType();
102        }
103    
104        @Nullable
105        public TypeProjection substitute(@NotNull TypeProjection typeProjection) {
106            TypeProjection substitutedTypeProjection = substituteWithoutApproximation(typeProjection);
107            if (!substitution.approximateCapturedTypes() && !substitution.approximateContravariantCapturedTypes()) {
108                return substitutedTypeProjection;
109            }
110            return CapturedTypeApproximationKt.approximateCapturedTypesIfNecessary(
111                    substitutedTypeProjection, substitution.approximateContravariantCapturedTypes());
112        }
113    
114        @Nullable
115        public TypeProjection substituteWithoutApproximation(@NotNull TypeProjection typeProjection) {
116            if (isEmpty()) {
117                return typeProjection;
118            }
119    
120            try {
121                return unsafeSubstitute(typeProjection, 0);
122            } catch (SubstitutionException e) {
123                return null;
124            }
125        }
126    
127        @NotNull
128        private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
129            assertRecursionDepth(recursionDepth, originalProjection, substitution);
130    
131            if (originalProjection.isStarProjection()) return originalProjection;
132    
133            // The type is within the substitution range, i.e. T or T?
134            KotlinType type = originalProjection.getType();
135            TypeProjection replacement = substitution.get(type);
136            Variance originalProjectionKind = originalProjection.getProjectionKind();
137            if (replacement == null && FlexibleTypesKt.isFlexible(type) && !TypeCapabilitiesKt.isCustomTypeVariable(type)) {
138                Flexibility flexibility = FlexibleTypesKt.flexibility(type);
139                TypeProjection substitutedLower =
140                        unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibility.getLowerBound()), recursionDepth + 1);
141                TypeProjection substitutedUpper =
142                        unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibility.getUpperBound()), recursionDepth + 1);
143    
144                Variance substitutedProjectionKind = substitutedLower.getProjectionKind();
145                assert (substitutedProjectionKind == substitutedUpper.getProjectionKind()) &&
146                       originalProjectionKind == Variance.INVARIANT || originalProjectionKind == substitutedProjectionKind :
147                        "Unexpected substituted projection kind: " + substitutedProjectionKind + "; original: " + originalProjectionKind;
148    
149                KotlinType substitutedFlexibleType = flexibility.getFactory().create(
150                        substitutedLower.getType(), substitutedUpper.getType());
151                return new TypeProjectionImpl(substitutedProjectionKind, substitutedFlexibleType);
152            }
153    
154            if (KotlinBuiltIns.isNothing(type) || type.isError()) return originalProjection;
155    
156            if (replacement != null) {
157                VarianceConflictType varianceConflict = conflictType(originalProjectionKind, replacement.getProjectionKind());
158    
159                // Captured type might be substituted in an opposite projection:
160                // out 'Captured (in Int)' = out Int
161                // in 'Captured (out Int)' = in Int
162                boolean allowVarianceConflict = CapturedTypeConstructorKt.isCaptured(type);
163                if (!allowVarianceConflict) {
164                    //noinspection EnumSwitchStatementWhichMissesCases
165                    switch (varianceConflict) {
166                        case OUT_IN_IN_POSITION:
167                            throw new SubstitutionException("Out-projection in in-position");
168                        case IN_IN_OUT_POSITION:
169                            // todo use the right type parameter variance and upper bound
170                            return new TypeProjectionImpl(Variance.OUT_VARIANCE, type.getConstructor().getBuiltIns().getNullableAnyType());
171                    }
172                }
173                KotlinType substitutedType;
174                CustomTypeVariable typeVariable = TypeCapabilitiesKt.getCustomTypeVariable(type);
175                if (replacement.isStarProjection()) {
176                    return replacement;
177                }
178                else if (typeVariable != null) {
179                    substitutedType = typeVariable.substitutionResult(replacement.getType());
180                }
181                else {
182                    // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we make replacement nullable
183                    substitutedType = TypeUtils.makeNullableIfNeeded(replacement.getType(), type.isMarkedNullable());
184                }
185    
186                // substitutionType.annotations = replacement.annotations ++ type.annotations
187                if (!type.getAnnotations().isEmpty()) {
188                    Annotations typeAnnotations = filterOutUnsafeVariance(substitution.filterAnnotations(type.getAnnotations()));
189                    substitutedType = TypeUtilsKt.replaceAnnotations(
190                            substitutedType,
191                            new CompositeAnnotations(substitutedType.getAnnotations(), typeAnnotations)
192                    );
193                }
194    
195                Variance resultingProjectionKind = varianceConflict == VarianceConflictType.NO_CONFLICT
196                                                   ? combine(originalProjectionKind, replacement.getProjectionKind())
197                                                   : originalProjectionKind;
198                return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
199            }
200            // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
201            return substituteCompoundType(originalProjection, recursionDepth);
202        }
203    
204        @NotNull
205        private static Annotations filterOutUnsafeVariance(@NotNull Annotations annotations) {
206            if (!annotations.hasAnnotation(KotlinBuiltIns.FQ_NAMES.unsafeVariance)) return annotations;
207            return new FilteredAnnotations(annotations, new Function1<FqName, Boolean>() {
208                @Override
209                public Boolean invoke(@NotNull  FqName name) {
210                    return !name.equals(KotlinBuiltIns.FQ_NAMES.unsafeVariance);
211                }
212            });
213        }
214    
215        private TypeProjection substituteCompoundType(
216                TypeProjection originalProjection,
217                int recursionDepth
218        ) throws SubstitutionException {
219            KotlinType type = originalProjection.getType();
220            Variance projectionKind = originalProjection.getProjectionKind();
221            if (type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor) {
222                // substitution can't change type parameter
223                // todo substitute bounds
224                return originalProjection;
225            }
226    
227            List<TypeProjection> substitutedArguments = substituteTypeArguments(
228                    type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
229    
230            KotlinType substitutedType =
231                    TypeSubstitutionKt.replace(type, substitutedArguments, substitution.filterAnnotations(type.getAnnotations()));
232            return new TypeProjectionImpl(projectionKind, substitutedType);
233        }
234    
235        private List<TypeProjection> substituteTypeArguments(
236                List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth
237        ) throws SubstitutionException {
238            List<TypeProjection> substitutedArguments = new ArrayList<TypeProjection>(typeParameters.size());
239            for (int i = 0; i < typeParameters.size(); i++) {
240                TypeParameterDescriptor typeParameter = typeParameters.get(i);
241                TypeProjection typeArgument = typeArguments.get(i);
242    
243                TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
244    
245                switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
246                    case NO_CONFLICT:
247                        // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
248                        if (typeParameter.getVariance() != Variance.INVARIANT && !substitutedTypeArgument.isStarProjection()) {
249                            substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
250                        }
251                        break;
252                    case OUT_IN_IN_POSITION:
253                    case IN_IN_OUT_POSITION:
254                        substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
255                        break;
256                }
257    
258                substitutedArguments.add(substitutedTypeArgument);
259            }
260            return substitutedArguments;
261        }
262    
263        @NotNull
264        public static Variance combine(@NotNull Variance typeParameterVariance, @NotNull TypeProjection typeProjection) {
265            if (typeProjection.isStarProjection()) return Variance.OUT_VARIANCE;
266    
267            return combine(typeParameterVariance, typeProjection.getProjectionKind());
268        }
269    
270        @NotNull
271        public static Variance combine(@NotNull Variance typeParameterVariance, @NotNull Variance projectionKind) {
272            if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
273            if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
274            if (typeParameterVariance == projectionKind) return projectionKind;
275            throw new AssertionError("Variance conflict: type parameter variance '" + typeParameterVariance + "' and " +
276                                     "projection kind '" + projectionKind + "' cannot be combined");
277        }
278    
279        private enum VarianceConflictType {
280            NO_CONFLICT,
281            IN_IN_OUT_POSITION,
282            OUT_IN_IN_POSITION
283        }
284    
285        private static VarianceConflictType conflictType(Variance position, Variance argument) {
286            if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
287                return VarianceConflictType.OUT_IN_IN_POSITION;
288            }
289            if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
290                return VarianceConflictType.IN_IN_OUT_POSITION;
291            }
292            return VarianceConflictType.NO_CONFLICT;
293        }
294    
295        private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
296            if (recursionDepth > MAX_RECURSION_DEPTH) {
297                throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
298            }
299        }
300    
301        private static String safeToString(Object o) {
302            try {
303                return o.toString();
304            }
305            catch (Throwable e) {
306                if (e.getClass().getName().equals("com.intellij.openapi.progress.ProcessCanceledException")) {
307                    //noinspection ConstantConditions
308                    throw (RuntimeException) e;
309                }
310                return "[Exception while computing toString(): " + e + "]";
311            }
312        }
313    }