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