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.HashMap;
028 import java.util.List;
029 import java.util.Map;
030
031 public class TypeSubstitutor {
032
033 private static final int MAX_RECURSION_DEPTH = 100;
034
035 public static class MapToTypeSubstitutionAdapter implements TypeSubstitution {
036 private final @NotNull Map<TypeConstructor, TypeProjection> substitutionContext;
037
038 public MapToTypeSubstitutionAdapter(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
039 this.substitutionContext = substitutionContext;
040 }
041
042 @Override
043 public TypeProjection get(TypeConstructor key) {
044 return substitutionContext.get(key);
045 }
046
047 @Override
048 public boolean isEmpty() {
049 return substitutionContext.isEmpty();
050 }
051
052 @Override
053 public String toString() {
054 return substitutionContext.toString();
055 }
056 }
057
058 public static final TypeSubstitutor EMPTY = create(TypeSubstitution.EMPTY);
059
060 private static final class SubstitutionException extends Exception {
061 public SubstitutionException(String message) {
062 super(message);
063 }
064 }
065
066 public static TypeSubstitutor create(@NotNull TypeSubstitution substitution) {
067 return new TypeSubstitutor(substitution);
068 }
069
070 public static TypeSubstitutor create(@NotNull TypeSubstitution... substitutions) {
071 return create(new CompositeTypeSubstitution(substitutions));
072 }
073
074 public static TypeSubstitutor create(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
075 return create(new MapToTypeSubstitutionAdapter(substitutionContext));
076 }
077
078 public static TypeSubstitutor create(@NotNull JetType context) {
079 return create(buildSubstitutionContext(context.getConstructor().getParameters(), context.getArguments()));
080 }
081
082 @NotNull
083 public static Map<TypeConstructor, TypeProjection> buildSubstitutionContext(
084 @NotNull List<TypeParameterDescriptor> parameters,
085 @NotNull List<? extends TypeProjection> contextArguments
086 ) {
087 Map<TypeConstructor, TypeProjection> parameterValues = new HashMap<TypeConstructor, TypeProjection>();
088 if (parameters.size() != contextArguments.size()) {
089 throw new IllegalArgumentException("type parameter count != context arguments: \n" +
090 "parameters=" + parameters + "\n" +
091 "contextArgs=" + contextArguments);
092 }
093 for (int i = 0, size = parameters.size(); i < size; i++) {
094 parameterValues.put(parameters.get(i).getTypeConstructor(), contextArguments.get(i));
095 }
096 return parameterValues;
097 }
098
099 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
100
101 private final @NotNull TypeSubstitution substitution;
102
103 protected TypeSubstitutor(@NotNull TypeSubstitution substitution) {
104 this.substitution = substitution;
105 }
106
107 public boolean inRange(@NotNull TypeConstructor typeConstructor) {
108 return substitution.get(typeConstructor) != null;
109 }
110
111 public boolean isEmpty() {
112 return substitution.isEmpty();
113 }
114
115 @NotNull
116 public TypeSubstitution getSubstitution() {
117 return substitution;
118 }
119
120 @NotNull
121 public JetType safeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
122 if (isEmpty()) {
123 return type;
124 }
125
126 try {
127 return unsafeSubstitute(new TypeProjectionImpl(howThisTypeIsUsed, type), 0).getType();
128 } catch (SubstitutionException e) {
129 return ErrorUtils.createErrorType(e.getMessage());
130 }
131 }
132
133 @Nullable
134 public JetType substitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
135 TypeProjection projection = substitute(new TypeProjectionImpl(howThisTypeIsUsed, type));
136 return projection == null ? null : projection.getType();
137 }
138
139 @Nullable
140 public TypeProjection substitute(@NotNull TypeProjection typeProjection) {
141 if (isEmpty()) {
142 return typeProjection;
143 }
144
145 try {
146 return unsafeSubstitute(typeProjection, 0);
147 } catch (SubstitutionException e) {
148 return null;
149 }
150 }
151
152 @NotNull
153 private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
154 assertRecursionDepth(recursionDepth, originalProjection, substitution);
155 // The type is within the substitution range, i.e. T or T?
156 JetType type = originalProjection.getType();
157 if (KotlinBuiltIns.getInstance().isNothing(type) || type.isError()) return originalProjection;
158
159 TypeProjection replacement = substitution.get(type.getConstructor());
160
161 if (replacement != null) {
162 // It must be a type parameter: only they can be directly substituted for
163 TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
164
165 switch (conflictType(originalProjection.getProjectionKind(), replacement.getProjectionKind())) {
166 case OUT_IN_IN_POSITION:
167 throw new SubstitutionException("Out-projection in in-position");
168 case IN_IN_OUT_POSITION:
169 //noinspection ConstantConditions
170 return TypeUtils.makeStarProjection(typeParameter);
171 case NO_CONFLICT:
172 boolean resultingIsNullable = type.isNullable() || replacement.getType().isNullable();
173 JetType substitutedType = TypeUtils.makeNullableAsSpecified(replacement.getType(), resultingIsNullable);
174 Variance resultingProjectionKind = combine(originalProjection.getProjectionKind(), replacement.getProjectionKind());
175
176 return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
177 default:
178 throw new IllegalStateException();
179 }
180 }
181 else {
182 // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
183 List<TypeProjection> substitutedArguments = substituteTypeArguments(
184 type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
185
186 JetType substitutedType = new JetTypeImpl(type.getAnnotations(), // Old annotations. This is questionable
187 type.getConstructor(), // The same constructor
188 type.isNullable(), // Same nullability
189 substitutedArguments,
190 new SubstitutingScope(type.getMemberScope(), this));
191 return new TypeProjectionImpl(originalProjection.getProjectionKind(), substitutedType);
192 }
193 }
194
195 private List<TypeProjection> substituteTypeArguments(List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth)
196 throws SubstitutionException {
197 List<TypeProjection> substitutedArguments = Lists.newArrayList();
198 for (int i = 0; i < typeParameters.size(); i++) {
199 TypeParameterDescriptor typeParameter = typeParameters.get(i);
200 TypeProjection typeArgument = typeArguments.get(i);
201
202 TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
203
204 switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
205 case NO_CONFLICT:
206 // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
207 if (typeParameter.getVariance() != Variance.INVARIANT) {
208 substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
209 }
210 break;
211 case OUT_IN_IN_POSITION:
212 case IN_IN_OUT_POSITION:
213 substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
214 break;
215 }
216
217 substitutedArguments.add(substitutedTypeArgument);
218 }
219 return substitutedArguments;
220 }
221
222 private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
223 if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
224 if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
225 if (typeParameterVariance == projectionKind) return projectionKind;
226 return Variance.IN_VARIANCE;
227 }
228
229 private enum VarianceConflictType {
230 NO_CONFLICT,
231 IN_IN_OUT_POSITION,
232 OUT_IN_IN_POSITION
233 }
234
235 private static VarianceConflictType conflictType(Variance position, Variance argument) {
236 if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
237 return VarianceConflictType.OUT_IN_IN_POSITION;
238 }
239 if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
240 return VarianceConflictType.IN_IN_OUT_POSITION;
241 }
242 return VarianceConflictType.NO_CONFLICT;
243 }
244
245 private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
246 if (recursionDepth > MAX_RECURSION_DEPTH) {
247 throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
248 }
249 }
250
251 private static String safeToString(Object o) {
252 try {
253 return o.toString();
254 }
255 catch (ProcessCanceledException e) {
256 throw e;
257 }
258 catch (Throwable e) {
259 return "[Exception while computing toString(): " + e + "]";
260 }
261 }
262 }