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 }