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                Flexibility flexibility = TypesPackage.flexibility(type);
160                TypeProjection substitutedLower =
161                        unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibility.getLowerBound()), recursionDepth + 1);
162                TypeProjection substitutedUpper =
163                        unsafeSubstitute(new TypeProjectionImpl(originalProjectionKind, flexibility.getUpperBound()), recursionDepth + 1);
164                // todo: projection kind is neglected
165                return new TypeProjectionImpl(originalProjectionKind,
166                                              DelegatingFlexibleType.create(
167                                                      substitutedLower.getType(),
168                                                      substitutedUpper.getType(),
169                                                      flexibility.getExtraCapabilities()
170                                              )
171                );
172            }
173    
174            if (KotlinBuiltIns.getInstance().isNothing(type) || type.isError()) return originalProjection;
175    
176            TypeProjection replacement = substitution.get(type.getConstructor());
177    
178            if (replacement != null) {
179                // It must be a type parameter: only they can be directly substituted for
180                TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
181    
182                switch (conflictType(originalProjectionKind, replacement.getProjectionKind())) {
183                    case OUT_IN_IN_POSITION:
184                        throw new SubstitutionException("Out-projection in in-position");
185                    case IN_IN_OUT_POSITION:
186                        //noinspection ConstantConditions
187                        return TypeUtils.makeStarProjection(typeParameter);
188                    case NO_CONFLICT:
189                        JetType substitutedType;
190                        CustomTypeVariable typeVariable = TypesPackage.getCustomTypeVariable(type);
191                        if (typeVariable != null) {
192                            substitutedType = typeVariable.substitutionResult(replacement.getType());
193                        }
194                        else {
195                            // this is a simple type T or T?: if it's T, we should just take replacement, if T? - we make replacement nullable
196                            substitutedType = type.isNullable() ? TypeUtils.makeNullable(replacement.getType()) : replacement.getType();
197                        }
198    
199                        Variance resultingProjectionKind = combine(originalProjectionKind, replacement.getProjectionKind());
200                        return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
201                    default:
202                        throw new IllegalStateException();
203                }
204            }
205            else {
206                // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
207                List<TypeProjection> substitutedArguments = substituteTypeArguments(
208                        type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
209    
210                JetType substitutedType = new JetTypeImpl(type.getAnnotations(),   // Old annotations. This is questionable
211                                                   type.getConstructor(),   // The same constructor
212                                                   type.isNullable(),       // Same nullability
213                                                   substitutedArguments,
214                                                   new SubstitutingScope(type.getMemberScope(), this));
215                return new TypeProjectionImpl(originalProjectionKind, substitutedType);
216            }
217        }
218    
219        private List<TypeProjection> substituteTypeArguments(
220                List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth
221        ) throws SubstitutionException {
222            List<TypeProjection> substitutedArguments = new ArrayList<TypeProjection>(typeParameters.size());
223            for (int i = 0; i < typeParameters.size(); i++) {
224                TypeParameterDescriptor typeParameter = typeParameters.get(i);
225                TypeProjection typeArgument = typeArguments.get(i);
226    
227                TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
228    
229                switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
230                    case NO_CONFLICT:
231                        // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
232                        if (typeParameter.getVariance() != Variance.INVARIANT) {
233                            substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
234                        }
235                        break;
236                    case OUT_IN_IN_POSITION:
237                    case IN_IN_OUT_POSITION:
238                        substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
239                        break;
240                }
241    
242                substitutedArguments.add(substitutedTypeArgument);
243            }
244            return substitutedArguments;
245        }
246    
247        private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
248            if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
249            if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
250            if (typeParameterVariance == projectionKind) return projectionKind;
251            return Variance.IN_VARIANCE;
252        }
253    
254        private enum VarianceConflictType {
255            NO_CONFLICT,
256            IN_IN_OUT_POSITION,
257            OUT_IN_IN_POSITION
258        }
259    
260        private static VarianceConflictType conflictType(Variance position, Variance argument) {
261            if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
262                return VarianceConflictType.OUT_IN_IN_POSITION;
263            }
264            if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
265                return VarianceConflictType.IN_IN_OUT_POSITION;
266            }
267            return VarianceConflictType.NO_CONFLICT;
268        }
269    
270        private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
271            if (recursionDepth > MAX_RECURSION_DEPTH) {
272                throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
273            }
274        }
275    
276        private static String safeToString(Object o) {
277            try {
278                return o.toString();
279            }
280            catch (Throwable e) {
281                if (e.getClass().getName().equals("com.intellij.openapi.progress.ProcessCanceledException")) {
282                    //noinspection ConstantConditions
283                    throw (RuntimeException) e;
284                }
285                return "[Exception while computing toString(): " + e + "]";
286            }
287        }
288    }