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.TEXT.renderType(originalType), nullableType.getText());
078            }
079            return TypeUtils.makeNullable(computeType(nullableType.getInnerType(), originalType, originalToAltTypeParameters, typeUsage));
080        }
081    
082        @Override
083        public JetType visitFunctionType(@NotNull JetFunctionType type, Void data) {
084            return visitCommonType(type.getReceiverTypeRef() == null
085                    ? KotlinBuiltIns.getInstance().getFunction(type.getParameters().size())
086                    : KotlinBuiltIns.getInstance().getExtensionFunction(type.getParameters().size()), type);
087        }
088    
089        @Override
090        public JetType visitUserType(@NotNull JetUserType type, Void data) {
091            JetUserType qualifier = type.getQualifier();
092    
093            //noinspection ConstantConditions
094            String shortName = type.getReferenceExpression().getReferencedName();
095            String longName = (qualifier == null ? "" : qualifier.getText() + ".") + shortName;
096    
097            return visitCommonType(longName, type);
098        }
099    
100        private JetType visitCommonType(@NotNull ClassDescriptor classDescriptor, @NotNull JetTypeElement type) {
101            return visitCommonType(DescriptorUtils.getFqNameSafe(classDescriptor).asString(), type);
102        }
103    
104        @NotNull
105        private JetType visitCommonType(@NotNull String qualifiedName, @NotNull JetTypeElement type) {
106            if (originalType.isError()) {
107                return originalType;
108            }
109            TypeConstructor originalTypeConstructor = originalType.getConstructor();
110            ClassifierDescriptor declarationDescriptor = originalTypeConstructor.getDeclarationDescriptor();
111            assert declarationDescriptor != null;
112            FqName originalClassFqName = DescriptorUtils.getFqNameSafe(declarationDescriptor);
113            ClassDescriptor classFromLibrary = getAutoTypeAnalogWithinBuiltins(originalClassFqName, qualifiedName);
114            if (!isSameName(qualifiedName, originalClassFqName.asString()) && classFromLibrary == null) {
115                throw new AlternativeSignatureMismatchException("Alternative signature type mismatch, expected: %s, actual: %s",
116                                                                qualifiedName, originalClassFqName);
117            }
118    
119            TypeConstructor typeConstructor;
120            if (classFromLibrary != null) {
121                typeConstructor = classFromLibrary.getTypeConstructor();
122            }
123            else {
124                typeConstructor = originalTypeConstructor;
125            }
126            ClassifierDescriptor typeConstructorClassifier = typeConstructor.getDeclarationDescriptor();
127            if (typeConstructorClassifier instanceof TypeParameterDescriptor && originalToAltTypeParameters.containsKey(typeConstructorClassifier)) {
128                typeConstructor = originalToAltTypeParameters.get(typeConstructorClassifier).getTypeConstructor();
129            }
130    
131            List<TypeProjection> arguments = originalType.getArguments();
132    
133            if (arguments.size() != type.getTypeArgumentsAsTypes().size()) {
134                throw new AlternativeSignatureMismatchException("'%s' type in method signature has %d type arguments, while '%s' in alternative signature has %d of them",
135                     DescriptorRenderer.TEXT.renderType(originalType), arguments.size(), type.getText(),
136                     type.getTypeArgumentsAsTypes().size());
137            }
138    
139            List<TypeProjection> altArguments = new ArrayList<TypeProjection>();
140            for (int i = 0, size = arguments.size(); i < size; i++) {
141                altArguments.add(getAltArgument(type, typeConstructor, i, arguments.get(i)));
142            }
143    
144            JetScope memberScope;
145            if (typeConstructorClassifier instanceof TypeParameterDescriptor) {
146                memberScope = ((TypeParameterDescriptor) typeConstructorClassifier).getUpperBoundsAsType().getMemberScope();
147            }
148            else if (typeConstructorClassifier instanceof ClassDescriptor) {
149                memberScope = ((ClassDescriptor) typeConstructorClassifier).getMemberScope(altArguments);
150            }
151            else {
152                throw new AssertionError("Unexpected class of type constructor classifier "
153                                         + (typeConstructorClassifier == null ? "null" : typeConstructorClassifier.getClass().getName()));
154            }
155            return new JetTypeImpl(originalType.getAnnotations(), typeConstructor, false,
156                                   altArguments, memberScope);
157        }
158    
159        @NotNull
160        private TypeProjection getAltArgument(
161                @NotNull JetTypeElement type,
162                @NotNull TypeConstructor typeConstructor,
163                int i,
164                @NotNull TypeProjection originalArgument
165        ) {
166            JetTypeReference typeReference = type.getTypeArgumentsAsTypes().get(i); // process both function type and user type
167    
168            if (typeReference == null) {
169                // star projection
170                assert type instanceof JetUserType
171                       && ((JetUserType) type).getTypeArguments().get(i).getProjectionKind() == JetProjectionKind.STAR;
172    
173                return originalArgument;
174            }
175    
176            JetTypeElement argumentAlternativeTypeElement = typeReference.getTypeElement();
177            assert argumentAlternativeTypeElement != null;
178    
179            TypeParameterDescriptor parameter = typeConstructor.getParameters().get(i);
180            JetType alternativeArgumentType = computeType(argumentAlternativeTypeElement, originalArgument.getType(), originalToAltTypeParameters, TYPE_ARGUMENT);
181            Variance projectionKind = originalArgument.getProjectionKind();
182            Variance altProjectionKind;
183            if (type instanceof JetUserType) {
184                JetTypeProjection typeProjection = ((JetUserType) type).getTypeArguments().get(i);
185                altProjectionKind = TypeResolver.resolveProjectionKind(typeProjection.getProjectionKind());
186                if (altProjectionKind != projectionKind && projectionKind != Variance.INVARIANT) {
187                    throw new AlternativeSignatureMismatchException("Projection kind mismatch, actual: %s, in alternative signature: %s",
188                                                                    projectionKind, altProjectionKind);
189                }
190                if (altProjectionKind != INVARIANT && parameter.getVariance() != INVARIANT) {
191                    if (altProjectionKind == parameter.getVariance()) {
192                        if (strictMode) {
193                            throw new AlternativeSignatureMismatchException("Projection kind '%s' is redundant",
194                                    altProjectionKind, DescriptorUtils.getFqName(typeConstructor.getDeclarationDescriptor()));
195                        }
196                        else {
197                            altProjectionKind = projectionKind;
198                        }
199                    }
200                    else {
201                        throw new AlternativeSignatureMismatchException("Projection kind '%s' is conflicting with variance of %s",
202                                altProjectionKind, DescriptorUtils.getFqName(typeConstructor.getDeclarationDescriptor()));
203                    }
204                }
205            }
206            else {
207                altProjectionKind = projectionKind;
208            }
209            return new TypeProjectionImpl(altProjectionKind, alternativeArgumentType);
210        }
211    
212        @Nullable
213        private static ClassDescriptor getAutoTypeAnalogWithinBuiltins(@NotNull FqName originalClassFqName, @NotNull String qualifiedName) {
214            FqName javaFqName = KotlinToJavaTypesMap.getInstance().getKotlinToJavaFqName(originalClassFqName);
215            if (javaFqName == null) return null;
216    
217            Collection<ClassDescriptor> descriptors = JavaToKotlinClassMap.getInstance().mapPlatformClass(javaFqName);
218            for (ClassDescriptor descriptor : descriptors) {
219                String fqName = DescriptorUtils.getFqName(descriptor).asString();
220                if (isSameName(qualifiedName, fqName)) {
221                    return descriptor;
222                }
223            }
224            return null;
225        }
226    
227        @Override
228        public JetType visitSelfType(@NotNull JetSelfType type, Void data) {
229            throw new UnsupportedOperationException("Self-types are not supported yet");
230        }
231    
232        private static boolean isSameName(String qualifiedName, String fullyQualifiedName) {
233            return fullyQualifiedName.equals(qualifiedName) || fullyQualifiedName.endsWith("." + qualifiedName);
234        }
235    
236        @TestOnly
237        public static void setStrictMode(boolean strictMode) {
238            TypeTransformingVisitor.strictMode = strictMode;
239        }
240    }