001 /*
002 * Copyright 2010-2013 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.jet.lang.types;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.annotations.Nullable;
021 import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
022 import org.jetbrains.jet.lang.resolve.scopes.SubstitutingScope;
023 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
024
025 import java.util.ArrayList;
026 import java.util.HashMap;
027 import java.util.List;
028 import java.util.Map;
029
030 public class TypeSubstitutor {
031
032 private static final int MAX_RECURSION_DEPTH = 100;
033
034 public static class MapToTypeSubstitutionAdapter implements 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 public static TypeSubstitutor create(@NotNull TypeSubstitution substitution) {
066 return new TypeSubstitutor(substitution);
067 }
068
069 public static TypeSubstitutor create(@NotNull TypeSubstitution... substitutions) {
070 return create(new CompositeTypeSubstitution(substitutions));
071 }
072
073 public static TypeSubstitutor create(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
074 return create(new MapToTypeSubstitutionAdapter(substitutionContext));
075 }
076
077 public static TypeSubstitutor create(@NotNull JetType context) {
078 return create(buildSubstitutionContext(context.getConstructor().getParameters(), context.getArguments()));
079 }
080
081 @NotNull
082 public static Map<TypeConstructor, TypeProjection> buildSubstitutionContext(
083 @NotNull List<TypeParameterDescriptor> parameters,
084 @NotNull List<? extends TypeProjection> contextArguments
085 ) {
086 Map<TypeConstructor, TypeProjection> parameterValues = new HashMap<TypeConstructor, TypeProjection>();
087 if (parameters.size() != contextArguments.size()) {
088 throw new IllegalArgumentException("type parameter count != context arguments: \n" +
089 "parameters=" + parameters + "\n" +
090 "contextArgs=" + contextArguments);
091 }
092 for (int i = 0, size = parameters.size(); i < size; i++) {
093 parameterValues.put(parameters.get(i).getTypeConstructor(), contextArguments.get(i));
094 }
095 return parameterValues;
096 }
097
098 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
099
100 private final @NotNull TypeSubstitution substitution;
101
102 protected TypeSubstitutor(@NotNull TypeSubstitution substitution) {
103 this.substitution = substitution;
104 }
105
106 public boolean inRange(@NotNull TypeConstructor typeConstructor) {
107 return substitution.get(typeConstructor) != null;
108 }
109
110 public boolean isEmpty() {
111 return substitution.isEmpty();
112 }
113
114 @NotNull
115 public TypeSubstitution getSubstitution() {
116 return substitution;
117 }
118
119 @NotNull
120 public JetType safeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
121 if (isEmpty()) {
122 return type;
123 }
124
125 try {
126 return unsafeSubstitute(new TypeProjectionImpl(howThisTypeIsUsed, type), 0).getType();
127 } catch (SubstitutionException e) {
128 return ErrorUtils.createErrorType(e.getMessage());
129 }
130 }
131
132 @Nullable
133 public JetType substitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
134 TypeProjection projection = substitute(new TypeProjectionImpl(howThisTypeIsUsed, type));
135 return projection == null ? null : projection.getType();
136 }
137
138 @Nullable
139 public TypeProjection substitute(@NotNull TypeProjection typeProjection) {
140 if (isEmpty()) {
141 return typeProjection;
142 }
143
144 try {
145 return unsafeSubstitute(typeProjection, 0);
146 } catch (SubstitutionException e) {
147 return null;
148 }
149 }
150
151 @NotNull
152 private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
153 assertRecursionDepth(recursionDepth, originalProjection, substitution);
154
155 // The type is within the substitution range, i.e. T or T?
156 JetType type = originalProjection.getType();
157 Variance originalProjectionKind = originalProjection.getProjectionKind();
158 if (TypesPackage.isFlexible(type) && !TypesPackage.isCustomTypeVariable(type)) {
159 Flexibility flexibility = TypesPackage.flexibility(type);
160 TypeProjection substitutedLower =
161 unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibility.getLowerBound()), recursionDepth + 1);
162 TypeProjection substitutedUpper =
163 unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibility.getUpperBound()), recursionDepth + 1);
164 // todo: projection kind is neglected
165 return new TypeProjectionImpl(originalProjectionKind,
166 DelegatingFlexibleType.create(
167 substitutedLower.getType(),
168 substitutedUpper.getType(),
169 flexibility.getExtraCapabilities()
170 )
171 );
172 }
173
174 if (KotlinBuiltIns.getInstance().isNothing(type) || type.isError()) return originalProjection;
175
176 TypeProjection replacement = substitution.get(type.getConstructor());
177
178 if (replacement != null) {
179 // It must be a type parameter: only they can be directly substituted for
180 TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
181
182 switch (conflictType(originalProjectionKind, replacement.getProjectionKind())) {
183 case OUT_IN_IN_POSITION:
184 throw new SubstitutionException("Out-projection in in-position");
185 case IN_IN_OUT_POSITION:
186 //noinspection ConstantConditions
187 return TypeUtils.makeStarProjection(typeParameter);
188 case NO_CONFLICT:
189 JetType substitutedType;
190 CustomTypeVariable typeVariable = TypesPackage.getCustomTypeVariable(type);
191 if (typeVariable != null) {
192 substitutedType = typeVariable.substitutionResult(replacement.getType());
193 }
194 else {
195 // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we make replacement nullable
196 substitutedType = type.isNullable() ? TypeUtils.makeNullable(replacement.getType()) : replacement.getType();
197 }
198
199 Variance resultingProjectionKind = combine(originalProjectionKind, replacement.getProjectionKind());
200 return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
201 default:
202 throw new IllegalStateException();
203 }
204 }
205 else {
206 // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
207 List<TypeProjection> substitutedArguments = substituteTypeArguments(
208 type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
209
210 JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable
211 type.getConstructor(), // The same constructor
212 type.isNullable(), // Same nullability
213 substitutedArguments,
214 new SubstitutingScope(type.getMemberScope(), this));
215 return new TypeProjectionImpl(originalProjectionKind, substitutedType);
216 }
217 }
218
219 private List<TypeProjection> substituteTypeArguments(
220 List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth
221 ) throws SubstitutionException {
222 List<TypeProjection> substitutedArguments = new ArrayList<TypeProjection>(typeParameters.size());
223 for (int i = 0; i < typeParameters.size(); i++) {
224 TypeParameterDescriptor typeParameter = typeParameters.get(i);
225 TypeProjection typeArgument = typeArguments.get(i);
226
227 TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
228
229 switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
230 case NO_CONFLICT:
231 // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
232 if (typeParameter.getVariance() != Variance.INVARIANT) {
233 substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
234 }
235 break;
236 case OUT_IN_IN_POSITION:
237 case IN_IN_OUT_POSITION:
238 substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
239 break;
240 }
241
242 substitutedArguments.add(substitutedTypeArgument);
243 }
244 return substitutedArguments;
245 }
246
247 private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
248 if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
249 if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
250 if (typeParameterVariance == projectionKind) return projectionKind;
251 return Variance.IN_VARIANCE;
252 }
253
254 private enum VarianceConflictType {
255 NO_CONFLICT,
256 IN_IN_OUT_POSITION,
257 OUT_IN_IN_POSITION
258 }
259
260 private static VarianceConflictType conflictType(Variance position, Variance argument) {
261 if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
262 return VarianceConflictType.OUT_IN_IN_POSITION;
263 }
264 if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
265 return VarianceConflictType.IN_IN_OUT_POSITION;
266 }
267 return VarianceConflictType.NO_CONFLICT;
268 }
269
270 private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
271 if (recursionDepth > MAX_RECURSION_DEPTH) {
272 throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
273 }
274 }
275
276 private static String safeToString(Object o) {
277 try {
278 return o.toString();
279 }
280 catch (Throwable e) {
281 if (e.getClass().getName().equals("com.intellij.openapi.progress.ProcessCanceledException")) {
282 //noinspection ConstantConditions
283 throw (RuntimeException) e;
284 }
285 return "[Exception while computing toString(): " + e + "]";
286 }
287 }
288 }