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