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 com.google.common.collect.Lists;
020 import com.intellij.openapi.progress.ProcessCanceledException;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023 import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
024 import org.jetbrains.jet.lang.resolve.scopes.SubstitutingScope;
025 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
026
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 /** No assertion for immediate recursion */
074 public static TypeSubstitutor createUnsafe(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
075 Map<TypeConstructor, TypeProjection> cleanContext = SubstitutionUtils.removeTrivialSubstitutions(substitutionContext);
076 return create(new MapToTypeSubstitutionAdapter(cleanContext));
077 }
078
079 public static TypeSubstitutor create(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
080 Map<TypeConstructor, TypeProjection> cleanContext = SubstitutionUtils.removeTrivialSubstitutions(substitutionContext);
081 //SubstitutionUtils.assertNotImmediatelyRecursive(cleanContext);
082 return createUnsafe(cleanContext);
083 }
084
085 public static TypeSubstitutor create(@NotNull JetType context) {
086 return create(SubstitutionUtils.buildSubstitutionContext(context));
087 }
088
089 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
090
091 private final @NotNull TypeSubstitution substitution;
092
093 protected TypeSubstitutor(@NotNull TypeSubstitution substitution) {
094 this.substitution = substitution;
095 }
096
097 public boolean inRange(@NotNull TypeConstructor typeConstructor) {
098 return substitution.get(typeConstructor) != null;
099 }
100
101 public boolean isEmpty() {
102 return substitution.isEmpty();
103 }
104
105 @NotNull
106 public TypeSubstitution getSubstitution() {
107 return substitution;
108 }
109
110 @NotNull
111 public JetType safeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
112 if (isEmpty()) {
113 return type;
114 }
115
116 try {
117 return unsafeSubstitute(new TypeProjection(howThisTypeIsUsed, type), 0).getType();
118 } catch (SubstitutionException e) {
119 return ErrorUtils.createErrorType(e.getMessage());
120 }
121 }
122
123 @Nullable
124 public JetType substitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
125 TypeProjection projection = substitute(new TypeProjection(howThisTypeIsUsed, type));
126 return projection == null ? null : projection.getType();
127 }
128
129 @Nullable
130 public TypeProjection substitute(@NotNull TypeProjection typeProjection) {
131 if (isEmpty()) {
132 return typeProjection;
133 }
134
135 try {
136 return unsafeSubstitute(typeProjection, 0);
137 } catch (SubstitutionException e) {
138 return null;
139 }
140 }
141
142 @NotNull
143 private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
144 assertRecursionDepth(recursionDepth, originalProjection, substitution);
145 // The type is within the substitution range, i.e. T or T?
146 JetType type = originalProjection.getType();
147 if (KotlinBuiltIns.getInstance().isNothing(type) || ErrorUtils.isErrorType(type)) return originalProjection;
148
149 TypeProjection replacement = substitution.get(type.getConstructor());
150
151 if (replacement != null) {
152 // It must be a type parameter: only they can be directly substituted for
153 TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
154
155 switch (conflictType(originalProjection.getProjectionKind(), replacement.getProjectionKind())) {
156 case OUT_IN_IN_POSITION:
157 throw new SubstitutionException("Out-projection in in-position");
158 case IN_IN_OUT_POSITION:
159 return SubstitutionUtils.makeStarProjection(typeParameter);
160 case NO_CONFLICT:
161 boolean resultingIsNullable = type.isNullable() || replacement.getType().isNullable();
162 JetType substitutedType = TypeUtils.makeNullableAsSpecified(replacement.getType(), resultingIsNullable);
163 Variance resultingProjectionKind = combine(originalProjection.getProjectionKind(), replacement.getProjectionKind());
164
165 return new TypeProjection(resultingProjectionKind, substitutedType);
166 default:
167 throw new IllegalStateException();
168 }
169 }
170 else {
171 // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
172 List<TypeProjection> substitutedArguments = substituteTypeArguments(
173 type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
174
175 JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable
176 type.getConstructor(), // The same constructor
177 type.isNullable(), // Same nullability
178 substitutedArguments,
179 new SubstitutingScope(type.getMemberScope(), this));
180 return new TypeProjection(originalProjection.getProjectionKind(), substitutedType);
181 }
182 }
183
184 private List<TypeProjection> substituteTypeArguments(List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth)
185 throws SubstitutionException {
186 List<TypeProjection> substitutedArguments = Lists.newArrayList();
187 for (int i = 0; i < typeParameters.size(); i++) {
188 TypeParameterDescriptor typeParameter = typeParameters.get(i);
189 TypeProjection typeArgument = typeArguments.get(i);
190
191 TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
192
193 switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
194 case NO_CONFLICT:
195 // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
196 if (typeParameter.getVariance() != Variance.INVARIANT) {
197 substitutedTypeArgument = new TypeProjection(Variance.INVARIANT, substitutedTypeArgument.getType());
198 }
199 break;
200 case OUT_IN_IN_POSITION:
201 case IN_IN_OUT_POSITION:
202 substitutedTypeArgument = SubstitutionUtils.makeStarProjection(typeParameter);
203 break;
204 }
205
206 substitutedArguments.add(substitutedTypeArgument);
207 }
208 return substitutedArguments;
209 }
210
211 private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
212 if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
213 if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
214 if (typeParameterVariance == projectionKind) return projectionKind;
215 return Variance.IN_VARIANCE;
216 }
217
218 private enum VarianceConflictType {
219 NO_CONFLICT,
220 IN_IN_OUT_POSITION,
221 OUT_IN_IN_POSITION;
222 }
223
224 private static VarianceConflictType conflictType(Variance position, Variance argument) {
225 if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
226 return VarianceConflictType.OUT_IN_IN_POSITION;
227 }
228 if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
229 return VarianceConflictType.IN_IN_OUT_POSITION;
230 }
231 return VarianceConflictType.NO_CONFLICT;
232 }
233
234 private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
235 if (recursionDepth > MAX_RECURSION_DEPTH) {
236 throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
237 }
238 }
239
240 private static String safeToString(Object o) {
241 try {
242 return o.toString();
243 }
244 catch (ProcessCanceledException e) {
245 throw e;
246 }
247 catch (Throwable e) {
248 return "[Exception while computing toString(): " + e + "]";
249 }
250 }
251 }