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