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.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    
031    public class TypeSubstitutor {
032    
033        private static final int MAX_RECURSION_DEPTH = 100;
034    
035        public static class MapToTypeSubstitutionAdapter implements TypeSubstitution {
036            private final @NotNull Map<TypeConstructor, TypeProjection> substitutionContext;
037    
038            public MapToTypeSubstitutionAdapter(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
039                this.substitutionContext = substitutionContext;
040            }
041    
042            @Override
043            public TypeProjection get(TypeConstructor key) {
044                return substitutionContext.get(key);
045            }
046    
047            @Override
048            public boolean isEmpty() {
049                return substitutionContext.isEmpty();
050            }
051    
052            @Override
053            public String toString() {
054                return substitutionContext.toString();
055            }
056        }
057    
058        public static final TypeSubstitutor EMPTY = create(TypeSubstitution.EMPTY);
059    
060        private static final class SubstitutionException extends Exception {
061            public SubstitutionException(String message) {
062                super(message);
063            }
064        }
065    
066        public static TypeSubstitutor create(@NotNull TypeSubstitution substitution) {
067            return new TypeSubstitutor(substitution);
068        }
069    
070        public static TypeSubstitutor create(@NotNull TypeSubstitution... substitutions) {
071            return create(new CompositeTypeSubstitution(substitutions));
072        }
073    
074        public static TypeSubstitutor create(@NotNull Map<TypeConstructor, TypeProjection> substitutionContext) {
075            return create(new MapToTypeSubstitutionAdapter(substitutionContext));
076        }
077    
078        public static TypeSubstitutor create(@NotNull JetType context) {
079            return create(buildSubstitutionContext(context.getConstructor().getParameters(), context.getArguments()));
080        }
081    
082        @NotNull
083        public static Map<TypeConstructor, TypeProjection> buildSubstitutionContext(
084                @NotNull List<TypeParameterDescriptor> parameters,
085                @NotNull List<? extends TypeProjection> contextArguments
086        ) {
087            Map<TypeConstructor, TypeProjection> parameterValues = new HashMap<TypeConstructor, TypeProjection>();
088            if (parameters.size() != contextArguments.size()) {
089                throw new IllegalArgumentException("type parameter count != context arguments: \n" +
090                                                   "parameters=" + parameters + "\n" +
091                                                   "contextArgs=" + contextArguments);
092            }
093            for (int i = 0, size = parameters.size(); i < size; i++) {
094                parameterValues.put(parameters.get(i).getTypeConstructor(), contextArguments.get(i));
095            }
096            return parameterValues;
097        }
098    
099    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
100    
101        private final @NotNull TypeSubstitution substitution;
102    
103        protected TypeSubstitutor(@NotNull TypeSubstitution substitution) {
104            this.substitution = substitution;
105        }
106    
107        public boolean inRange(@NotNull TypeConstructor typeConstructor) {
108            return substitution.get(typeConstructor) != null;
109        }
110    
111        public boolean isEmpty() {
112            return substitution.isEmpty();
113        }
114    
115        @NotNull
116        public TypeSubstitution getSubstitution() {
117            return substitution;
118        }
119    
120        @NotNull
121        public JetType safeSubstitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
122            if (isEmpty()) {
123                return type;
124            }
125    
126            try {
127                return unsafeSubstitute(new TypeProjectionImpl(howThisTypeIsUsed, type), 0).getType();
128            } catch (SubstitutionException e) {
129                return ErrorUtils.createErrorType(e.getMessage());
130            }
131        }
132    
133        @Nullable
134        public JetType substitute(@NotNull JetType type, @NotNull Variance howThisTypeIsUsed) {
135            TypeProjection projection = substitute(new TypeProjectionImpl(howThisTypeIsUsed, type));
136            return projection == null ? null : projection.getType();
137        }
138    
139        @Nullable
140        public TypeProjection substitute(@NotNull TypeProjection typeProjection) {
141            if (isEmpty()) {
142                return typeProjection;
143            }
144    
145            try {
146                return unsafeSubstitute(typeProjection, 0);
147            } catch (SubstitutionException e) {
148                return null;
149            }
150        }
151    
152        @NotNull
153        private TypeProjection unsafeSubstitute(@NotNull TypeProjection originalProjection, int recursionDepth) throws SubstitutionException {
154            assertRecursionDepth(recursionDepth, originalProjection, substitution);
155            // The type is within the substitution range, i.e. T or T?
156            JetType type = originalProjection.getType();
157            if (KotlinBuiltIns.getInstance().isNothing(type) || type.isError()) return originalProjection;
158    
159            TypeProjection replacement = substitution.get(type.getConstructor());
160    
161            if (replacement != null) {
162                // It must be a type parameter: only they can be directly substituted for
163                TypeParameterDescriptor typeParameter = (TypeParameterDescriptor) type.getConstructor().getDeclarationDescriptor();
164    
165                switch (conflictType(originalProjection.getProjectionKind(), replacement.getProjectionKind())) {
166                    case OUT_IN_IN_POSITION:
167                        throw new SubstitutionException("Out-projection in in-position");
168                    case IN_IN_OUT_POSITION:
169                        //noinspection ConstantConditions
170                        return TypeUtils.makeStarProjection(typeParameter);
171                    case NO_CONFLICT:
172                        boolean resultingIsNullable = type.isNullable() || replacement.getType().isNullable();
173                        JetType substitutedType = TypeUtils.makeNullableAsSpecified(replacement.getType(), resultingIsNullable);
174                        Variance resultingProjectionKind = combine(originalProjection.getProjectionKind(), replacement.getProjectionKind());
175    
176                        return new TypeProjectionImpl(resultingProjectionKind, substitutedType);
177                    default:
178                        throw new IllegalStateException();
179                }
180            }
181            else {
182                // The type is not within the substitution range, i.e. Foo, Bar<T> etc.
183                List<TypeProjection> substitutedArguments = substituteTypeArguments(
184                        type.getConstructor().getParameters(), type.getArguments(), recursionDepth);
185    
186                JetType substitutedType = new JetTypeImpl(type.getAnnotations(),   // Old annotations. This is questionable
187                                                   type.getConstructor(),   // The same constructor
188                                                   type.isNullable(),       // Same nullability
189                                                   substitutedArguments,
190                                                   new SubstitutingScope(type.getMemberScope(), this));
191                return new TypeProjectionImpl(originalProjection.getProjectionKind(), substitutedType);
192            }
193        }
194    
195        private List<TypeProjection> substituteTypeArguments(List<TypeParameterDescriptor> typeParameters, List<TypeProjection> typeArguments, int recursionDepth)
196                throws SubstitutionException {
197            List<TypeProjection> substitutedArguments = Lists.newArrayList();
198            for (int i = 0; i < typeParameters.size(); i++) {
199                TypeParameterDescriptor typeParameter = typeParameters.get(i);
200                TypeProjection typeArgument = typeArguments.get(i);
201    
202                TypeProjection substitutedTypeArgument = unsafeSubstitute(typeArgument, recursionDepth + 1);
203    
204                switch (conflictType(typeParameter.getVariance(), substitutedTypeArgument.getProjectionKind())) {
205                    case NO_CONFLICT:
206                        // if the corresponding type parameter is already co/contra-variant, there's not need for an explicit projection
207                        if (typeParameter.getVariance() != Variance.INVARIANT) {
208                            substitutedTypeArgument = new TypeProjectionImpl(Variance.INVARIANT, substitutedTypeArgument.getType());
209                        }
210                        break;
211                    case OUT_IN_IN_POSITION:
212                    case IN_IN_OUT_POSITION:
213                        substitutedTypeArgument = TypeUtils.makeStarProjection(typeParameter);
214                        break;
215                }
216    
217                substitutedArguments.add(substitutedTypeArgument);
218            }
219            return substitutedArguments;
220        }
221    
222        private static Variance combine(Variance typeParameterVariance, Variance projectionKind) {
223            if (typeParameterVariance == Variance.INVARIANT) return projectionKind;
224            if (projectionKind == Variance.INVARIANT) return typeParameterVariance;
225            if (typeParameterVariance == projectionKind) return projectionKind;
226            return Variance.IN_VARIANCE;
227        }
228    
229        private enum VarianceConflictType {
230            NO_CONFLICT,
231            IN_IN_OUT_POSITION,
232            OUT_IN_IN_POSITION
233        }
234    
235        private static VarianceConflictType conflictType(Variance position, Variance argument) {
236            if (position == Variance.IN_VARIANCE && argument == Variance.OUT_VARIANCE) {
237                return VarianceConflictType.OUT_IN_IN_POSITION;
238            }
239            if (position == Variance.OUT_VARIANCE && argument == Variance.IN_VARIANCE) {
240                return VarianceConflictType.IN_IN_OUT_POSITION;
241            }
242            return VarianceConflictType.NO_CONFLICT;
243        }
244    
245        private static void assertRecursionDepth(int recursionDepth, TypeProjection projection, TypeSubstitution substitution) {
246            if (recursionDepth > MAX_RECURSION_DEPTH) {
247                throw new IllegalStateException("Recursion too deep. Most likely infinite loop while substituting " + safeToString(projection) + "; substitution: " + safeToString(substitution));
248            }
249        }
250    
251        private static String safeToString(Object o) {
252            try {
253                return o.toString();
254            }
255            catch (ProcessCanceledException e) {
256                throw e;
257            }
258            catch (Throwable e) {
259                return "[Exception while computing toString(): " + e + "]";
260            }
261        }
262    }