001    /*
002     * Copyright 2010-2015 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.kotlin.load.java.components;
018    
019    import com.intellij.openapi.diagnostic.Logger;
020    import com.intellij.psi.*;
021    import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
022    import com.intellij.psi.util.MethodSignatureUtil;
023    import com.intellij.psi.util.PsiUtil;
024    import com.intellij.psi.util.TypeConversionUtil;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.kotlin.descriptors.ClassDescriptor;
027    import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
028    import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor;
029    import org.jetbrains.kotlin.load.java.structure.*;
030    import org.jetbrains.kotlin.load.java.structure.impl.JavaMethodImpl;
031    import org.jetbrains.kotlin.resolve.OverrideResolver;
032    import org.jetbrains.kotlin.resolve.OverridingUtil;
033    import org.jetbrains.kotlin.resolve.jvm.kotlinSignature.SignaturesUtil;
034    import org.jetbrains.kotlin.types.ErrorUtils;
035    import org.jetbrains.kotlin.types.SubstitutionUtils;
036    import org.jetbrains.kotlin.types.TypeSubstitution;
037    import org.jetbrains.kotlin.types.TypeSubstitutor;
038    
039    import javax.inject.Inject;
040    import java.util.ArrayList;
041    import java.util.List;
042    
043    public class PsiBasedMethodSignatureChecker implements MethodSignatureChecker {
044        private static final Logger LOG = Logger.getInstance(PsiBasedMethodSignatureChecker.class);
045    
046        private ExternalAnnotationResolver externalAnnotationResolver;
047        private ExternalSignatureResolver externalSignatureResolver;
048    
049        @Inject
050        public void setExternalAnnotationResolver(ExternalAnnotationResolver externalAnnotationResolver) {
051            this.externalAnnotationResolver = externalAnnotationResolver;
052        }
053    
054        @Inject
055        public void setExternalSignatureResolver(ExternalSignatureResolver externalSignatureResolver) {
056            this.externalSignatureResolver = externalSignatureResolver;
057        }
058    
059        private void checkFunctionOverridesCorrectly(
060                @NotNull JavaMethod method,
061                @NotNull FunctionDescriptor function,
062                @NotNull FunctionDescriptor superFunction
063        ) {
064            ClassDescriptor klass = (ClassDescriptor) function.getContainingDeclaration();
065            List<TypeSubstitution> substitutions = new ArrayList<TypeSubstitution>();
066            while (true) {
067                substitutions.add(SubstitutionUtils.buildDeepSubstitutor(klass.getDefaultType()).getSubstitution());
068                if (!klass.isInner()) {
069                    break;
070                }
071                klass = (ClassDescriptor) klass.getContainingDeclaration();
072            }
073            TypeSubstitutor substitutor = TypeSubstitutor.create(substitutions.toArray(new TypeSubstitution[substitutions.size()]));
074            FunctionDescriptor superFunctionSubstituted = superFunction.substitute(substitutor);
075    
076            assert superFunctionSubstituted != null : "Couldn't substitute super function: " + superFunction + ", substitutor = " + substitutor;
077    
078            OverridingUtil.OverrideCompatibilityInfo.Result overridableResult = OverridingUtil.DEFAULT.isOverridableBy(superFunctionSubstituted, function).getResult();
079            boolean paramsOk = overridableResult == OverridingUtil.OverrideCompatibilityInfo.Result.OVERRIDABLE;
080            boolean returnTypeOk = OverrideResolver.isReturnTypeOkForOverride(superFunctionSubstituted, function);
081            if (!paramsOk || !returnTypeOk) {
082                // This should be a LOG.error, but happens a lot of times incorrectly (e.g. on Kotlin project), because somewhere in the
083                // type checker we compare two types which seem the same but have different instances of class descriptors. It happens
084                // probably because JavaDescriptorResolver is not completely thread-safe yet, and one class gets resolved multiple times.
085                // TODO: change to LOG.error when JavaDescriptorResolver becomes thread-safe
086                LOG.warn("Loaded Java method overrides another, but resolved as Kotlin function, doesn't.\n"
087                          + "super function = " + superFunction + "\n"
088                          + "super class = " + superFunction.getContainingDeclaration() + "\n"
089                          + "sub function = " + function + "\n"
090                          + "sub class = " + function.getContainingDeclaration() + "\n"
091                          + "sub method = " + method + "\n"
092                          + "@KotlinSignature = " + SignaturesUtil.getKotlinSignature(externalAnnotationResolver, method));
093            }
094        }
095    
096        private static boolean containsErrorType(@NotNull List<FunctionDescriptor> superFunctions, @NotNull FunctionDescriptor function) {
097            if (ErrorUtils.containsErrorType(function)) {
098                return true;
099            }
100    
101            for (FunctionDescriptor superFunction : superFunctions) {
102                if (ErrorUtils.containsErrorType(superFunction)) {
103                    return true;
104                }
105            }
106    
107            return false;
108        }
109    
110        // Originally from com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil
111        private static boolean isMethodReturnTypeCompatible(@NotNull JavaMethodImpl method) {
112            if (method.isStatic()) return true;
113    
114            HierarchicalMethodSignature methodSignature = method.getPsi().getHierarchicalMethodSignature();
115            List<HierarchicalMethodSignature> superSignatures = methodSignature.getSuperSignatures();
116    
117            PsiType returnType = methodSignature.getSubstitutor().substitute(method.getPsi().getReturnType());
118            if (returnType == null) return true;
119    
120            for (MethodSignatureBackedByPsiMethod superMethodSignature : superSignatures) {
121                PsiMethod superMethod = superMethodSignature.getMethod();
122                PsiType declaredReturnType = superMethod.getReturnType();
123                PsiType superReturnType = superMethodSignature.isRaw() ? TypeConversionUtil.erasure(declaredReturnType) : declaredReturnType;
124                if (superReturnType == null || method == superMethod || superMethod.getContainingClass() == null) continue;
125                if (!areMethodsReturnTypesCompatible(superMethodSignature, superReturnType, methodSignature, returnType)) {
126                    return false;
127                }
128            }
129    
130            return true;
131        }
132    
133        // Originally from com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil
134        private static boolean areMethodsReturnTypesCompatible(
135                @NotNull MethodSignatureBackedByPsiMethod superMethodSignature,
136                @NotNull PsiType superReturnType,
137                @NotNull MethodSignatureBackedByPsiMethod methodSignature,
138                @NotNull PsiType returnType
139        ) {
140            PsiType substitutedSuperReturnType;
141            boolean isJdk15 = PsiUtil.isLanguageLevel5OrHigher(methodSignature.getMethod());
142            if (isJdk15 && !superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5
143                PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature,
144                                                                                                            superMethodSignature);
145                substitutedSuperReturnType = unifyingSubstitutor == null
146                                             ? superReturnType
147                                             : unifyingSubstitutor.substitute(superReturnType);
148            }
149            else {
150                substitutedSuperReturnType = TypeConversionUtil.erasure(superMethodSignature.getSubstitutor().substitute(superReturnType));
151            }
152    
153            if (returnType.equals(substitutedSuperReturnType)) return true;
154            if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) {
155                if (isJdk15 && TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) {
156                    return true;
157                }
158            }
159    
160            return false;
161        }
162    
163        @Override
164        public void checkSignature(
165                @NotNull JavaMethod method,
166                boolean reportSignatureErrors,
167                @NotNull SimpleFunctionDescriptor descriptor,
168                @NotNull List<String> signatureErrors,
169                @NotNull List<FunctionDescriptor> superFunctions
170        ) {
171            // This optimization speed things up because hasRawTypesInHierarchicalSignature() is very expensive
172            if (superFunctions.isEmpty() && (signatureErrors.isEmpty() || !reportSignatureErrors)) return;
173    
174            JavaMethodImpl methodWithPsi = (JavaMethodImpl) method;
175            if (!RawTypesCheck.hasRawTypesInHierarchicalSignature(methodWithPsi) &&
176                isMethodReturnTypeCompatible(methodWithPsi) &&
177                !containsErrorType(superFunctions, descriptor)) {
178                if (signatureErrors.isEmpty()) {
179                    for (FunctionDescriptor superFunction : superFunctions) {
180                        checkFunctionOverridesCorrectly(method, descriptor, superFunction);
181                    }
182                }
183                else if (reportSignatureErrors) {
184                    externalSignatureResolver.reportSignatureErrors(descriptor, signatureErrors);
185                }
186            }
187        }
188    
189        private static class RawTypesCheck {
190            private static boolean isPartiallyRawType(@NotNull JavaType type) {
191                if (type instanceof JavaPrimitiveType) {
192                    return false;
193                }
194                else if (type instanceof JavaClassifierType) {
195                    JavaClassifierType classifierType = (JavaClassifierType) type;
196    
197                    if (classifierType.isRaw()) {
198                        return true;
199                    }
200    
201                    for (JavaType argument : classifierType.getTypeArguments()) {
202                        if (isPartiallyRawType(argument)) {
203                            return true;
204                        }
205                    }
206    
207                    return false;
208                }
209                else if (type instanceof JavaArrayType) {
210                    return isPartiallyRawType(((JavaArrayType) type).getComponentType());
211                }
212                else if (type instanceof JavaWildcardType) {
213                    JavaType bound = ((JavaWildcardType) type).getBound();
214                    return bound != null && isPartiallyRawType(bound);
215                }
216                else {
217                    throw new IllegalStateException("Unexpected type: " + type);
218                }
219            }
220    
221            private static boolean hasRawTypesInSignature(@NotNull JavaMethod method) {
222                JavaType returnType = method.getReturnType();
223                if (returnType != null && isPartiallyRawType(returnType)) {
224                    return true;
225                }
226    
227                for (JavaValueParameter parameter : method.getValueParameters()) {
228                    if (isPartiallyRawType(parameter.getType())) {
229                        return true;
230                    }
231                }
232    
233                for (JavaTypeParameter typeParameter : method.getTypeParameters()) {
234                    for (JavaClassifierType upperBound : typeParameter.getUpperBounds()) {
235                        if (isPartiallyRawType(upperBound)) {
236                            return true;
237                        }
238                    }
239                }
240    
241                return false;
242            }
243    
244            public static boolean hasRawTypesInHierarchicalSignature(@NotNull JavaMethodImpl method) {
245                // This is a very important optimization: package-classes are big and full of static methods
246                // building method hierarchies for such classes takes a very long time
247                if (method.isStatic()) return false;
248    
249                if (hasRawTypesInSignature(method)) {
250                    return true;
251                }
252    
253                for (HierarchicalMethodSignature superSignature : method.getPsi().getHierarchicalMethodSignature().getSuperSignatures()) {
254                    JavaMethod superMethod = new JavaMethodImpl(superSignature.getMethod());
255                    if (superSignature.isRaw() || typeParameterIsErased(method, superMethod) || hasRawTypesInSignature(superMethod)) {
256                        return true;
257                    }
258                }
259    
260                return false;
261            }
262    
263            private static boolean typeParameterIsErased(@NotNull JavaMethod method, @NotNull JavaMethod superMethod) {
264                // Java allows you to write
265                //   <T extends Foo> T foo(), in the superclass and then
266                //   Foo foo(), in the subclass
267                // this is a valid Java override, but in fact it is an erasure
268                return method.getTypeParameters().size() != superMethod.getTypeParameters().size();
269            }
270    
271            private RawTypesCheck() {
272            }
273        }
274    }