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 TypeProjection substitutedLower =
160 unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, TypesPackage.flexibility(type).getLowerBound()), recursionDepth + 1);
161 TypeProjection substitutedUpper =
162 unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, TypesPackage.flexibility(type).getUpperBound()), recursionDepth + 1);
163 // todo: projection kind is neglected
164 return new TypeProjectionImpl(originalProjectionKind,
165 DelegatingFlexibleType.OBJECT$.create(
166 substitutedLower.getType(),
167 substitutedUpper.getType()
168 )
169 );
170 }
171
172 if (KotlinBuiltIns.getInstance().isNothing(type) || type.isError()) return originalProjection;
173
174 TypeProjection replacement = substitution.get(type.getConstructor());
175
176 if (replacement != null) {
177 // It must be a type parameter: only they can be directly substituted for
178 TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
179
180 switch (conflictType(originalProjectionKind, replacement.getProjectionKind())) {
181 case OUT_IN_IN_POSITION:
182 throw new SubstitutionException("Out-projection in in-position");
183 case IN_IN_OUT_POSITION:
184 //noinspection ConstantConditions
185 return TypeUtils.makeStarProjection(typeParameter);
186 case NO_CONFLICT:
187 JetType substitutedType;
188 if (TypesPackage.isCustomTypeVariable(type)) {
189 CustomTypeVariable typeVariable = type.getCapability(CustomTypeVariable.class);
190 assert typeVariable != null;
191 substitutedType = typeVariable.substitutionResult(replacement.getType());
192 }
193 else {
194 // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we make replacement nullable
195 substitutedType = type.isNullable() ? TypeUtils.makeNullable(replacement.getType()) : replacement.getType();
196 }
197
198 Variance resultingProjectionKind = combine(originalProjectionKind, replacement.getProjectionKind());
199 return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
200 default:
201 throw new IllegalStateException();
202 }
203 }
204 else {
205 // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
206 List<TypeProjection> substitutedArguments = substituteTypeArguments(
207 type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
208
209 JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable
210 type.getConstructor(), // The same constructor
211 type.isNullable(), // Same nullability
212 substitutedArguments,
213 new SubstitutingScope(type.getMemberScope(), this));
214 return new TypeProjectionImpl(originalProjectionKind, substitutedType);
215 }
216 }
217
218 private List<TypeProjection> substituteTypeArguments(
219 List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth
220 ) throws SubstitutionException {
221 List<TypeProjection> substitutedArguments = new ArrayList<TypeProjection>(typeParameters.size());
222 for (int i = 0; i < typeParameters.size(); i++) {
223 TypeParameterDescriptor typeParameter = typeParameters.get(i);
224 TypeProjection typeArgument = typeArguments.get(i);
225
226 TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
227
228 switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
229 case NO_CONFLICT:
230 // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
231 if (typeParameter.getVariance() != Variance.INVARIANT) {
232 substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
233 }
234 break;
235 case OUT_IN_IN_POSITION:
236 case IN_IN_OUT_POSITION:
237 substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
238 break;
239 }
240
241 substitutedArguments.add(substitutedTypeArgument);
242 }
243 return substitutedArguments;
244 }
245
246 private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
247 if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
248 if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
249 if (typeParameterVariance == projectionKind) return projectionKind;
250 return Variance.IN_VARIANCE;
251 }
252
253 private enum VarianceConflictType {
254 NO_CONFLICT,
255 IN_IN_OUT_POSITION,
256 OUT_IN_IN_POSITION
257 }
258
259 private static VarianceConflictType conflictType(Variance position, Variance argument) {
260 if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
261 return VarianceConflictType.OUT_IN_IN_POSITION;
262 }
263 if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
264 return VarianceConflictType.IN_IN_OUT_POSITION;
265 }
266 return VarianceConflictType.NO_CONFLICT;
267 }
268
269 private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
270 if (recursionDepth > MAX_RECURSION_DEPTH) {
271 throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
272 }
273 }
274
275 private static String safeToString(Object o) {
276 try {
277 return o.toString();
278 }
279 catch (Throwable e) {
280 if (e.getClass().getName().equals("com.intellij.openapi.progress.ProcessCanceledException")) {
281 //noinspection ConstantConditions
282 throw (RuntimeException) e;
283 }
284 return "[Exception while computing toString(): " + e + "]";
285 }
286 }
287 }