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 }