IDEA-94481 Groovy: Don't highlight as warning category methods of Class type invoked on class references

This commit is contained in:
Max Medvedev
2012-11-12 13:48:10 +04:00
parent 0674867186
commit bc5147120c
5 changed files with 65 additions and 37 deletions
@@ -72,6 +72,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.*;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrReferenceResolveUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
@@ -816,9 +817,13 @@ public class GroovyAssignabilityCheckInspection extends BaseInspection {
if (method instanceof GrGdkMethod && place instanceof GrReferenceExpression) {
final PsiMethod staticMethod = ((GrGdkMethod)method).getStaticMethod();
final PsiType qualifierType = inferQualifierTypeByPlace((GrReferenceExpression)place);
final GrExpression qualifier = PsiImplUtil.getRuntimeQualifier((GrReferenceExpression)place);
//check methods processed by @Category(ClassWhichProcessMethod) annotation
if (qualifierType != null &&
!GdkMethodUtil.isCategoryMethod(staticMethod, qualifierType, methodResolveResult.getSubstitutor()) &&
!checkCategoryQualifier((GrReferenceExpression)place, staticMethod, methodResolveResult.getSubstitutor())) {
!GdkMethodUtil.isCategoryMethod(staticMethod, qualifierType, qualifier, methodResolveResult.getSubstitutor()) &&
!checkCategoryQualifier((GrReferenceExpression)place, qualifier, staticMethod, methodResolveResult.getSubstitutor())) {
registerError(((GrReferenceExpression)place).getReferenceNameElement(), GroovyInspectionBundle
.message("category.method.0.cannot.be.applied.to.1", method.getName(), qualifierType.getCanonicalText()));
return false;
@@ -861,13 +866,16 @@ public class GroovyAssignabilityCheckInspection extends BaseInspection {
}
}
private static boolean checkCategoryQualifier(GrReferenceExpression place, PsiMethod gdkMethod, PsiSubstitutor substitutor) {
PsiClass categoryAnnotationOwner = inferCategoryAnnotationOwner(place, place.getQualifier());
private static boolean checkCategoryQualifier(GrReferenceExpression place,
GrExpression qualifier,
PsiMethod gdkMethod,
PsiSubstitutor substitutor) {
PsiClass categoryAnnotationOwner = inferCategoryAnnotationOwner(place, qualifier);
if (categoryAnnotationOwner != null) {
PsiClassType categoryType = GdkMethodUtil.getCategoryType(categoryAnnotationOwner);
if (categoryType != null) {
return GdkMethodUtil.isCategoryMethod(gdkMethod, categoryType, substitutor);
return GdkMethodUtil.isCategoryMethod(gdkMethod, categoryType, qualifier, substitutor);
}
}
@@ -877,7 +885,7 @@ public class GroovyAssignabilityCheckInspection extends BaseInspection {
private static PsiClass inferCategoryAnnotationOwner(GrReferenceExpression place, GrExpression qualifier) {
if (qualifier == null) {
GrMethod container = PsiTreeUtil.getParentOfType(place, GrMethod.class, true, GrMember.class);
if (container != null && !container.hasModifierProperty(PsiModifier.STATIC)) { //only instance classes can be qualified by category class
if (container != null && !container.hasModifierProperty(PsiModifier.STATIC)) { //only instance methods can be qualified by category class
return container.getContainingClass();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ public class AccessorMethodReferencesSearcher extends QueryExecutorBase<PsiRefer
final PsiMethod method = queryParameters.getMethod();
final String propertyName;
if (GdkMethodUtil.isCategoryMethod(method, null, PsiSubstitutor.EMPTY)) {
if (GdkMethodUtil.isCategoryMethod(method, null, null, PsiSubstitutor.EMPTY)) {
final GrGdkMethod cat = GrGdkMethodImpl.createGdkMethod(method, false, null);
propertyName = GroovyPropertyUtils.getPropertyName((PsiMethod)cat);
}
@@ -16,15 +16,17 @@
package org.jetbrains.plugins.groovy.lang.psi.util;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
import com.intellij.psi.scope.DelegatingScopeProcessor;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
@@ -123,7 +125,7 @@ public class GdkMethodUtil {
final DelegatingScopeProcessor delegate = new DelegatingScopeProcessor(processor) {
@Override
public boolean execute(@NotNull PsiElement element, ResolveState delegateState) {
if (isCategoryMethod(element, null, null)) {
if (element instanceof PsiMethod && isCategoryMethod((PsiMethod)element, null, null, null)) {
PsiMethod method = (PsiMethod)element;
return processor.execute(GrGdkMethodImpl.createGdkMethod(method, false, generateOriginInfo(method)), delegateState);
}
@@ -133,14 +135,6 @@ public class GdkMethodUtil {
return categoryClass.processDeclarations(delegate, state, null, place);
}
private static boolean acceptClass(@Nullable PsiType type, @NotNull PsiClass aClass) {
if (!(type instanceof PsiClassType)) return false;
PsiClass resolved = ((PsiClassType)type).resolve();
return InheritanceUtil.isInheritorOrSelf(aClass, resolved, true);
}
public static boolean withIteration(GrClosableBlock block, final PsiScopeProcessor processor) {
GrMethodCall call = checkMethodCall(block, WITH);
if (call == null) {
@@ -217,16 +211,17 @@ public class GdkMethodUtil {
for (GrStatement statement : statements) {
if (statement == lastParent) break;
final Pair<PsiClassType, PsiClass> result = getMixinTypes(statement);
final Trinity<PsiClassType, GrReferenceExpression, PsiClass> result = getMixinTypes(statement);
if (result != null) {
final PsiClassType subjectType = result.first;
final PsiClass mixin = result.second;
final GrReferenceExpression qualifier = result.second;
final PsiClass mixin = result.third;
final DelegatingScopeProcessor delegate = new DelegatingScopeProcessor(processor) {
@Override
public boolean execute(@NotNull PsiElement element, ResolveState delegateState) {
if (isCategoryMethod(element, subjectType, null)) {
if (element instanceof PsiMethod && isCategoryMethod((PsiMethod)element, subjectType, qualifier, null)) {
PsiMethod method = (PsiMethod)element;
return processor.execute(GrGdkMethodImpl.createGdkMethod(method, false, generateOriginInfo(method)), delegateState);
}
@@ -244,14 +239,14 @@ public class GdkMethodUtil {
}
@Nullable
private static Pair<PsiClassType, PsiClass> getMixinTypes(GrStatement statement) {
private static Trinity<PsiClassType, GrReferenceExpression, PsiClass> getMixinTypes(GrStatement statement) {
if (statement instanceof GrMethodCall) {
GrMethodCall call = (GrMethodCall)statement;
PsiClassType original = getTypeToMixIn(call);
Pair<PsiClassType, GrReferenceExpression> original = getTypeToMixIn(call);
PsiClass mix = getTypeToMix(call);
if (original != null && mix != null) {
return new Pair<PsiClassType, PsiClass>(original, mix);
return new Trinity<PsiClassType, GrReferenceExpression, PsiClass>(original.first, original.second, mix);
}
}
@@ -287,13 +282,13 @@ public class GdkMethodUtil {
}
@Nullable
private static PsiClassType getTypeToMixIn(GrMethodCall methodCall) {
private static Pair<PsiClassType, GrReferenceExpression> getTypeToMixIn(GrMethodCall methodCall) {
GrExpression invoked = methodCall.getInvokedExpression();
if (invoked instanceof GrReferenceExpression) {
PsiElement resolved = ((GrReferenceExpression)invoked).resolve();
if (resolved instanceof PsiMethod && isMixinMethod((PsiMethod)resolved)) {
GrExpression qualifier = ((GrReferenceExpression)invoked).getQualifier();
PsiClassType type = getPsiClassFromReference(qualifier);
Pair<PsiClassType, GrReferenceExpression> type = getPsiClassFromReference(qualifier);
if (type != null) {
return type;
}
@@ -306,7 +301,7 @@ public class GdkMethodUtil {
if (qualifier instanceof GrReferenceExpression) {
GrExpression qqualifier = ((GrReferenceExpression)qualifier).getQualifier();
if (qqualifier != null) {
PsiClassType type1 = getPsiClassFromReference(qqualifier);
Pair<PsiClassType, GrReferenceExpression> type1 = getPsiClassFromReference(qqualifier);
if (type1 != null) {
return type1;
}
@@ -314,7 +309,7 @@ public class GdkMethodUtil {
else {
PsiType qtype = GrReferenceResolveUtil.getQualifierType((GrReferenceExpression)qualifier);
if (qtype instanceof PsiClassType && ((PsiClassType)qtype).resolve() != null) {
return (PsiClassType)qtype;
return Pair.create((PsiClassType)qtype, (GrReferenceExpression)qualifier);
}
}
}
@@ -339,22 +334,21 @@ public class GdkMethodUtil {
return mixinRef instanceof GrReferenceExpression && "class".equals(((GrReferenceExpression)mixinRef).getReferenceName());
}
private static PsiClassType getPsiClassFromReference(GrExpression ref) {
@Nullable
private static Pair<PsiClassType, GrReferenceExpression> getPsiClassFromReference(GrExpression ref) {
if (isClassRef(ref)) ref = ((GrReferenceExpression)ref).getQualifier();
if (ref instanceof GrReferenceExpression) {
PsiElement resolved = ((GrReferenceExpression)ref).resolve();
if (resolved instanceof PsiClass) {
PsiType type = ref.getType();
LOG.assertTrue(type instanceof PsiClassType, "reference resolved into PsiClass should have PsiClassType");
return ((PsiClassType)type);
return Pair.create((PsiClassType)type, (GrReferenceExpression)ref);
}
}
return null;
}
public static boolean isCategoryMethod(@Nullable PsiElement element, @Nullable PsiType qualifierType, @Nullable PsiSubstitutor substitutor) {
if (!(element instanceof PsiMethod)) return false;
PsiMethod method = (PsiMethod)element;
public static boolean isCategoryMethod(@NotNull PsiMethod method, @Nullable PsiType qualifierType, @Nullable PsiElement place, @Nullable PsiSubstitutor substitutor) {
if (!method.hasModifierProperty(PsiModifier.STATIC)) return false;
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) return false;
@@ -369,7 +363,18 @@ public class GdkMethodUtil {
if (substitutor != null) {
selfType = substitutor.substitute(selfType);
}
return TypesUtil.isAssignable(selfType, qualifierType, element.getManager(), element.getResolveScope());
final GlobalSearchScope scope = method.getResolveScope();
final Project project = method.getProject();
final PsiManager manager = method.getManager();
if (selfType instanceof PsiClassType &&
((PsiClassType)selfType).rawType().equalsToText(CommonClassNames.JAVA_LANG_CLASS) &&
place instanceof GrReferenceExpression &&
((GrReferenceExpression)place).resolve() instanceof PsiClass) { // ClassType.categoryMethod() where categoryMethod(Class<> cl, ...)
return TypesUtil.isAssignable(selfType, TypesUtil.createJavaLangClassType(qualifierType, project, scope), manager, scope);
}
return TypesUtil.isAssignable(selfType, qualifierType, manager, scope);
}
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +40,7 @@ public class MixinMemberContributor extends NonCodeMembersContributor {
@Override
public void processDynamicElements(@NotNull final PsiType qualifierType,
@NotNull PsiScopeProcessor processor,
@NotNull GroovyPsiElement place,
@NotNull final GroovyPsiElement place,
@NotNull ResolveState state) {
if (!(qualifierType instanceof PsiClassType)) return;
if (isInAnnotation(place)) return;
@@ -70,7 +70,7 @@ public class MixinMemberContributor extends NonCodeMembersContributor {
if (!mixin.processDeclarations(new DelegatingScopeProcessor(processor) {
@Override
public boolean execute(@NotNull PsiElement element, ResolveState state) {
if (GdkMethodUtil.isCategoryMethod(element, qualifierType, state.get(PsiSubstitutor.KEY))) {
if (element instanceof PsiMethod && GdkMethodUtil.isCategoryMethod((PsiMethod)element, qualifierType, place, state.get(PsiSubstitutor.KEY))) {
PsiMethod method = (PsiMethod)element;
String originInfo = getOriginInfo(method);
return super.execute(GrGdkMethodImpl.createGdkMethod(method, false, originInfo), state);
@@ -411,6 +411,21 @@ class EvenSieve {
add<warning descr="'add' in 'java.util.List<E>' cannot be applied to '(java.lang.Integer, java.lang.Integer)'">(2, 3)</warning>
}
}
''')
}
void testAssignabilityOfCategoryMethod() {
testHighlighting('''
class Cat {
static foo(Class c, int x) {}
}
class X {}
use(Cat) {
X.foo(1)
}
''')
}
}