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 017package org.jetbrains.jet.lang.types; 018 019import com.google.common.collect.Lists; 020import com.intellij.openapi.progress.ProcessCanceledException; 021import org.jetbrains.annotations.NotNull; 022import org.jetbrains.annotations.Nullable; 023import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor; 024import org.jetbrains.jet.lang.resolve.scopes.SubstitutingScope; 025import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 026 027import java.util.List; 028import java.util.Map; 029 030public 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}