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.resolve.java.kotlinSignature;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.annotations.TestOnly;
022    import org.jetbrains.asm4.Type;
023    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
024    import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
025    import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor;
026    import org.jetbrains.jet.lang.descriptors.impl.TypeParameterDescriptorImpl;
027    import org.jetbrains.jet.lang.psi.*;
028    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
029    import org.jetbrains.jet.lang.resolve.TypeResolver;
030    import org.jetbrains.jet.lang.resolve.java.JavaToKotlinClassMap;
031    import org.jetbrains.jet.lang.resolve.java.JvmClassName;
032    import org.jetbrains.jet.lang.resolve.java.KotlinToJavaTypesMap;
033    import org.jetbrains.jet.lang.resolve.java.TypeUsage;
034    import org.jetbrains.jet.lang.resolve.scopes.JetScope;
035    import org.jetbrains.jet.lang.types.*;
036    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
037    import org.jetbrains.jet.renderer.DescriptorRenderer;
038    
039    import java.util.*;
040    
041    import static org.jetbrains.jet.lang.resolve.java.TypeUsage.TYPE_ARGUMENT;
042    import static org.jetbrains.jet.lang.types.Variance.INVARIANT;
043    
044    public 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    }