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