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