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    }