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}