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 }