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 }