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.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        /** 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    }