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 }