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 =
101                    substitute(new TypeProjectionImpl(howThisTypeIsUsed, getSubstitution().prepareTopLevelType(type, howThisTypeIsUsed)));
102            return projection == null ? null : projection.getType();
103        }
104    
105        @Nullable
106        public TypeProjection substitute(@NotNull TypeProjection typeProjection) {
107            TypeProjection substitutedTypeProjection = substituteWithoutApproximation(typeProjection);
108            if (!substitution.approximateCapturedTypes() && !substitution.approximateContravariantCapturedTypes()) {
109                return substitutedTypeProjection;
110            }
111            return CapturedTypeApproximationKt.approximateCapturedTypesIfNecessary(
112                    substitutedTypeProjection, substitution.approximateContravariantCapturedTypes());
113        }
114    
115        @Nullable
116        public TypeProjection substituteWithoutApproximation(@NotNull TypeProjection typeProjection) {
117            if (isEmpty()) {
118                return typeProjection;
119            }
120    
121            try {
122                return unsafeSubstitute(typeProjection, 0);
123            } catch (SubstitutionException e) {
124                return null;
125            }
126        }
127    
128        @NotNull
129        private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
130            assertRecursionDepth(recursionDepth, originalProjection, substitution);
131    
132            if (originalProjection.isStarProjection()) return originalProjection;
133    
134            // The type is within the substitution range, i.e. T or T?
135            KotlinType type = originalProjection.getType();
136            if (DynamicTypesKt.isDynamic(type) || type.unwrap() instanceof RawType) {
137                return originalProjection; // todo investigate
138            }
139    
140            TypeProjection replacement = substitution.get(type);
141            Variance originalProjectionKind = originalProjection.getProjectionKind();
142            if (replacement == null && FlexibleTypesKt.isFlexible(type) && !TypeCapabilitiesKt.isCustomTypeVariable(type)) {
143                FlexibleType flexibleType = FlexibleTypesKt.asFlexibleType(type);
144                TypeProjection substitutedLower =
145                        unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibleType.getLowerBound()), recursionDepth + 1);
146                TypeProjection substitutedUpper =
147                        unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibleType.getUpperBound()), recursionDepth + 1);
148    
149                Variance substitutedProjectionKind = substitutedLower.getProjectionKind();
150                assert (substitutedProjectionKind == substitutedUpper.getProjectionKind()) &&
151                       originalProjectionKind == Variance.INVARIANT || originalProjectionKind == substitutedProjectionKind :
152                        "Unexpected substituted projection kind: " + substitutedProjectionKind + "; original: " + originalProjectionKind;
153    
154                KotlinType substitutedFlexibleType = KotlinTypeFactory.flexibleType(
155                        TypeSubstitutionKt.asSimpleType(substitutedLower.getType()), TypeSubstitutionKt.asSimpleType(substitutedUpper.getType()));
156                return new TypeProjectionImpl(substitutedProjectionKind, substitutedFlexibleType);
157            }
158    
159            if (KotlinBuiltIns.isNothing(type) || type.isError()) return originalProjection;
160    
161            if (replacement != null) {
162                VarianceConflictType varianceConflict = conflictType(originalProjectionKind, replacement.getProjectionKind());
163    
164                // Captured type might be substituted in an opposite projection:
165                // out 'Captured (in Int)' = out Int
166                // in 'Captured (out Int)' = in Int
167                boolean allowVarianceConflict = CapturedTypeConstructorKt.isCaptured(type);
168                if (!allowVarianceConflict) {
169                    //noinspection EnumSwitchStatementWhichMissesCases
170                    switch (varianceConflict) {
171                        case OUT_IN_IN_POSITION:
172                            throw new SubstitutionException("Out-projection in in-position");
173                        case IN_IN_OUT_POSITION:
174                            // todo use the right type parameter variance and upper bound
175                            return new TypeProjectionImpl(Variance.OUT_VARIANCE, type.getConstructor().getBuiltIns().getNullableAnyType());
176                    }
177                }
178                KotlinType substitutedType;
179                CustomTypeVariable typeVariable = TypeCapabilitiesKt.getCustomTypeVariable(type);
180                if (replacement.isStarProjection()) {
181                    return replacement;
182                }
183                else if (typeVariable != null) {
184                    substitutedType = typeVariable.substitutionResult(replacement.getType());
185                }
186                else {
187                    // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we make replacement nullable
188                    substitutedType = TypeUtils.makeNullableIfNeeded(replacement.getType(), type.isMarkedNullable());
189                }
190    
191                // substitutionType.annotations = replacement.annotations ++ type.annotations
192                if (!type.getAnnotations().isEmpty()) {
193                    Annotations typeAnnotations = filterOutUnsafeVariance(substitution.filterAnnotations(type.getAnnotations()));
194                    substitutedType = TypeUtilsKt.replaceAnnotations(
195                            substitutedType,
196                            new CompositeAnnotations(substitutedType.getAnnotations(), typeAnnotations)
197                    );
198                }
199    
200                Variance resultingProjectionKind = varianceConflict == VarianceConflictType.NO_CONFLICT
201                                                   ? combine(originalProjectionKind, replacement.getProjectionKind())
202                                                   : originalProjectionKind;
203                return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
204            }
205            // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
206            return substituteCompoundType(originalProjection, recursionDepth);
207        }
208    
209        @NotNull
210        private static Annotations filterOutUnsafeVariance(@NotNull Annotations annotations) {
211            if (!annotations.hasAnnotation(KotlinBuiltIns.FQ_NAMES.unsafeVariance)) return annotations;
212            return new FilteredAnnotations(annotations, new Function1<FqName, Boolean>() {
213                @Override
214                public Boolean invoke(@NotNull  FqName name) {
215                    return !name.equals(KotlinBuiltIns.FQ_NAMES.unsafeVariance);
216                }
217            });
218        }
219    
220        private TypeProjection substituteCompoundType(
221                TypeProjection originalProjection,
222                int recursionDepth
223        ) throws SubstitutionException {
224            KotlinType type = originalProjection.getType();
225            Variance projectionKind = originalProjection.getProjectionKind();
226            if (type.getConstructor().getDeclarationDescriptor() instanceof TypeParameterDescriptor) {
227                // substitution can't change type parameter
228                // todo substitute bounds
229                return originalProjection;
230            }
231    
232            KotlinType substitutedAbbreviation = null;
233            SimpleType abbreviation = SpecialTypesKt.getAbbreviation(type);
234            if (abbreviation != null) {
235                substitutedAbbreviation = substitute(abbreviation, Variance.INVARIANT);
236            }
237    
238            List<TypeProjection> substitutedArguments = substituteTypeArguments(
239                    type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
240    
241            KotlinType substitutedType =
242                    TypeSubstitutionKt.replace(type, substitutedArguments, substitution.filterAnnotations(type.getAnnotations()));
243            if (substitutedType instanceof SimpleType && substitutedAbbreviation instanceof SimpleType) {
244                substitutedType = SpecialTypesKt.withAbbreviation((SimpleType) substitutedType, (SimpleType) substitutedAbbreviation);
245            }
246    
247            return new TypeProjectionImpl(projectionKind, substitutedType);
248        }
249    
250        private List<TypeProjection> substituteTypeArguments(
251                List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth
252        ) throws SubstitutionException {
253            List<TypeProjection> substitutedArguments = new ArrayList<TypeProjection>(typeParameters.size());
254            for (int i = 0; i < typeParameters.size(); i++) {
255                TypeParameterDescriptor typeParameter = typeParameters.get(i);
256                TypeProjection typeArgument = typeArguments.get(i);
257    
258                TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
259    
260                switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
261                    case NO_CONFLICT:
262                        // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
263                        if (typeParameter.getVariance() != Variance.INVARIANT && !substitutedTypeArgument.isStarProjection()) {
264                            substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
265                        }
266                        break;
267                    case OUT_IN_IN_POSITION:
268                    case IN_IN_OUT_POSITION:
269                        substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
270                        break;
271                }
272    
273                substitutedArguments.add(substitutedTypeArgument);
274            }
275            return substitutedArguments;
276        }
277    
278        @NotNull
279        public static Variance combine(@NotNull Variance typeParameterVariance, @NotNull TypeProjection typeProjection) {
280            if (typeProjection.isStarProjection()) return Variance.OUT_VARIANCE;
281    
282            return combine(typeParameterVariance, typeProjection.getProjectionKind());
283        }
284    
285        @NotNull
286        public static Variance combine(@NotNull Variance typeParameterVariance, @NotNull Variance projectionKind) {
287            if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
288            if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
289            if (typeParameterVariance == projectionKind) return projectionKind;
290            throw new AssertionError("Variance conflict: type parameter variance '" + typeParameterVariance + "' and " +
291                                     "projection kind '" + projectionKind + "' cannot be combined");
292        }
293    
294        private enum VarianceConflictType {
295            NO_CONFLICT,
296            IN_IN_OUT_POSITION,
297            OUT_IN_IN_POSITION
298        }
299    
300        private static VarianceConflictType conflictType(Variance position, Variance argument) {
301            if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
302                return VarianceConflictType.OUT_IN_IN_POSITION;
303            }
304            if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
305                return VarianceConflictType.IN_IN_OUT_POSITION;
306            }
307            return VarianceConflictType.NO_CONFLICT;
308        }
309    
310        private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
311            if (recursionDepth > MAX_RECURSION_DEPTH) {
312                throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
313            }
314        }
315    
316        private static String safeToString(Object o) {
317            try {
318                return o.toString();
319            }
320            catch (Throwable e) {
321                if (e.getClass().getName().equals("com.intellij.openapi.progress.ProcessCanceledException")) {
322                    //noinspection ConstantConditions
323                    throw (RuntimeException) e;
324                }
325                return "[Exception while computing toString(): " + e + "]";
326            }
327        }
328    }