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