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.resolve.java.kotlinSignature;
018
019import org.jetbrains.annotations.NotNull;
020import org.jetbrains.annotations.Nullable;
021import org.jetbrains.annotations.TestOnly;
022import org.jetbrains.asm4.Type;
023import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
024import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
025import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
026import org.jetbrains.jet.lang.descriptors.impl.TypeParameterDescriptorImpl;
027import org.jetbrains.jet.lang.psi.*;
028import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029import org.jetbrains.jet.lang.resolve.TypeResolver;
030import org.jetbrains.jet.lang.resolve.java.JavaToKotlinClassMap;
031import org.jetbrains.jet.lang.resolve.java.JvmClassName;
032import org.jetbrains.jet.lang.resolve.java.KotlinToJavaTypesMap;
033import org.jetbrains.jet.lang.resolve.java.TypeUsage;
034import org.jetbrains.jet.lang.resolve.scopes.JetScope;
035import org.jetbrains.jet.lang.types.*;
036import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
037import org.jetbrains.jet.renderer.DescriptorRenderer;
038
039import java.util.*;
040
041import static org.jetbrains.jet.lang.resolve.java.TypeUsage.TYPE_ARGUMENT;
042import static org.jetbrains.jet.lang.types.Variance.INVARIANT;
043
044public class TypeTransformingVisitor extends JetVisitor<JetType, Void> {
045    private static boolean strictMode = false;
046
047    private final JetType originalType;
048    private final Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> originalToAltTypeParameters;
049
050    private final TypeUsage typeUsage;
051
052    private TypeTransformingVisitor(
053            JetType originalType,
054            Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> originalToAltTypeParameters,
055            TypeUsage typeUsage
056    ) {
057        this.originalType = originalType;
058        this.typeUsage = typeUsage;
059        this.originalToAltTypeParameters = Collections.unmodifiableMap(originalToAltTypeParameters);
060    }
061
062    @NotNull
063    public static JetType computeType(
064            @NotNull JetTypeElement alternativeTypeElement,
065            @NotNull JetType originalType,
066            @NotNull Map<TypeParameterDescriptor, TypeParameterDescriptorImpl> originalToAltTypeParameters,
067            @NotNull TypeUsage typeUsage
068    ) {
069        JetType computedType = alternativeTypeElement.accept(new TypeTransformingVisitor(originalType, originalToAltTypeParameters, typeUsage), null);
070        assert (computedType != null);
071        return computedType;
072    }
073
074    @Override
075    public JetType visitNullableType(JetNullableType nullableType, Void aVoid) {
076        if (!originalType.isNullable() && typeUsage != TYPE_ARGUMENT) {
077            throw new AlternativeSignatureMismatchException("Auto type '%s' is not-null, while type in alternative signature is nullable: '%s'",
078                 DescriptorRenderer.TEXT.renderType(originalType), nullableType.getText());
079        }
080        return TypeUtils.makeNullable(computeType(nullableType.getInnerType(), originalType, originalToAltTypeParameters, typeUsage));
081    }
082
083    @Override
084    public JetType visitFunctionType(JetFunctionType type, Void data) {
085        return visitCommonType(type.getReceiverTypeRef() == null
086                ? KotlinBuiltIns.getInstance().getFunction(type.getParameters().size())
087                : KotlinBuiltIns.getInstance().getExtensionFunction(type.getParameters().size()), type);
088    }
089
090    @Override
091    public JetType visitUserType(JetUserType type, Void data) {
092        JetUserType qualifier = type.getQualifier();
093
094        //noinspection ConstantConditions
095        String shortName = type.getReferenceExpression().getReferencedName();
096        String longName = (qualifier == null ? "" : qualifier.getText() + ".") + shortName;
097
098        return visitCommonType(longName, type);
099    }
100
101    private JetType visitCommonType(@NotNull ClassDescriptor classDescriptor, @NotNull JetTypeElement type) {
102        return visitCommonType(DescriptorUtils.getFQName(classDescriptor).toSafe().asString(), type);
103    }
104
105    private JetType visitCommonType(@NotNull String qualifiedName, @NotNull JetTypeElement type) {
106        TypeConstructor originalTypeConstructor = originalType.getConstructor();
107        ClassifierDescriptor declarationDescriptor = originalTypeConstructor.getDeclarationDescriptor();
108        assert declarationDescriptor != null;
109        String fqName = DescriptorUtils.getFQName(declarationDescriptor).toSafe().asString();
110        ClassDescriptor classFromLibrary = getAutoTypeAnalogWithinBuiltins(qualifiedName);
111        if (!isSameName(qualifiedName, fqName) && classFromLibrary == null) {
112            throw new AlternativeSignatureMismatchException("Alternative signature type mismatch, expected: %s, actual: %s", qualifiedName, fqName);
113        }
114
115        TypeConstructor typeConstructor;
116        if (classFromLibrary != null) {
117            typeConstructor = classFromLibrary.getTypeConstructor();
118        }
119        else {
120            typeConstructor = originalTypeConstructor;
121        }
122        ClassifierDescriptor typeConstructorClassifier = typeConstructor.getDeclarationDescriptor();
123        if (typeConstructorClassifier instanceof TypeParameterDescriptor && originalToAltTypeParameters.containsKey(typeConstructorClassifier)) {
124            typeConstructor = originalToAltTypeParameters.get(typeConstructorClassifier).getTypeConstructor();
125        }
126
127        List<TypeProjection> arguments = originalType.getArguments();
128
129        if (arguments.size() != type.getTypeArgumentsAsTypes().size()) {
130            throw new AlternativeSignatureMismatchException("'%s' type in method signature has %d type arguments, while '%s' in alternative signature has %d of them",
131                 DescriptorRenderer.TEXT.renderType(originalType), arguments.size(), type.getText(),
132                 type.getTypeArgumentsAsTypes().size());
133        }
134
135        List<TypeProjection> altArguments = new ArrayList<TypeProjection>();
136        for (int i = 0, size = arguments.size(); i < size; i++) {
137            altArguments.add(getAltArgument(type, typeConstructor, i, arguments.get(i)));
138        }
139
140        JetScope memberScope;
141        if (typeConstructorClassifier instanceof TypeParameterDescriptor) {
142            memberScope = ((TypeParameterDescriptor) typeConstructorClassifier).getUpperBoundsAsType().getMemberScope();
143        }
144        else if (typeConstructorClassifier instanceof ClassDescriptor) {
145            memberScope = ((ClassDescriptor) typeConstructorClassifier).getMemberScope(altArguments);
146        }
147        else {
148            throw new AssertionError("Unexpected class of type constructor classifier "
149                                     + (typeConstructorClassifier == null ? "null" : typeConstructorClassifier.getClass().getName()));
150        }
151        return new JetTypeImpl(originalType.getAnnotations(), typeConstructor, false,
152                               altArguments, memberScope);
153    }
154
155    @NotNull
156    private TypeProjection getAltArgument(
157            @NotNull JetTypeElement type,
158            @NotNull TypeConstructor typeConstructor,
159            int i,
160            @NotNull TypeProjection originalArgument
161    ) {
162        JetTypeReference typeReference = type.getTypeArgumentsAsTypes().get(i); // process both function type and user type
163
164        if (typeReference == null) {
165            // star projection
166            assert type instanceof JetUserType
167                   && ((JetUserType) type).getTypeArguments().get(i).getProjectionKind() == JetProjectionKind.STAR;
168
169            return originalArgument;
170        }
171
172        JetTypeElement argumentAlternativeTypeElement = typeReference.getTypeElement();
173        assert argumentAlternativeTypeElement != null;
174
175        TypeParameterDescriptor parameter = typeConstructor.getParameters().get(i);
176        JetType alternativeArgumentType = computeType(argumentAlternativeTypeElement, originalArgument.getType(), originalToAltTypeParameters, TYPE_ARGUMENT);
177        Variance projectionKind = originalArgument.getProjectionKind();
178        Variance altProjectionKind;
179        if (type instanceof JetUserType) {
180            JetTypeProjection typeProjection = ((JetUserType) type).getTypeArguments().get(i);
181            altProjectionKind = TypeResolver.resolveProjectionKind(typeProjection.getProjectionKind());
182            if (altProjectionKind != projectionKind && projectionKind != Variance.INVARIANT) {
183                throw new AlternativeSignatureMismatchException("Projection kind mismatch, actual: %s, in alternative signature: %s",
184                                                                projectionKind, altProjectionKind);
185            }
186            if (altProjectionKind != INVARIANT && parameter.getVariance() != INVARIANT) {
187                if (altProjectionKind == parameter.getVariance()) {
188                    if (strictMode) {
189                        throw new AlternativeSignatureMismatchException("Projection kind '%s' is redundant",
190                                altProjectionKind, DescriptorUtils.getFQName(typeConstructor.getDeclarationDescriptor()));
191                    }
192                    else {
193                        altProjectionKind = projectionKind;
194                    }
195                }
196                else {
197                    throw new AlternativeSignatureMismatchException("Projection kind '%s' is conflicting with variance of %s",
198                            altProjectionKind, DescriptorUtils.getFQName(typeConstructor.getDeclarationDescriptor()));
199                }
200            }
201        }
202        else {
203            altProjectionKind = projectionKind;
204        }
205        return new TypeProjection(altProjectionKind, alternativeArgumentType);
206    }
207
208    @Nullable
209    private ClassDescriptor getAutoTypeAnalogWithinBuiltins(String qualifiedName) {
210        Type javaAnalog = KotlinToJavaTypesMap.getInstance().getJavaAnalog(originalType);
211        if (javaAnalog == null || javaAnalog.getSort() != Type.OBJECT)  return null;
212        Collection<ClassDescriptor> descriptors =
213                JavaToKotlinClassMap.getInstance().mapPlatformClass(JvmClassName.byType(javaAnalog).getFqName());
214        for (ClassDescriptor descriptor : descriptors) {
215            String fqName = DescriptorUtils.getFQName(descriptor).asString();
216            if (isSameName(qualifiedName, fqName)) {
217                return descriptor;
218            }
219        }
220        return null;
221    }
222
223    @Override
224    public JetType visitSelfType(JetSelfType type, Void data) {
225        throw new UnsupportedOperationException("Self-types are not supported yet");
226    }
227
228    private static boolean isSameName(String qualifiedName, String fullyQualifiedName) {
229        return fullyQualifiedName.equals(qualifiedName) || fullyQualifiedName.endsWith("." + qualifiedName);
230    }
231
232    @TestOnly
233    public static void setStrictMode(boolean strictMode) {
234        TypeTransformingVisitor.strictMode = strictMode;
235    }
236}