IDEA-88531 highlighting for inapplicable types of overridden method and implicit accessors

This commit is contained in:
Maxim.Medvedev
2012-08-01 11:55:44 +04:00
parent 4a2db9fee9
commit 00bd790c0b
3 changed files with 156 additions and 3 deletions
@@ -317,3 +317,5 @@ create.inner.class=Create Inner Class {0}
annotation.field.can.only.be.used.within.a.script.body=Annotation @Field can only be used within a script body
annotation.field.can.only.be.used.within.a.script=Annotation @Field can only be used within a script
public.modifier.is.not.allowed.in.interfaces='public' modifier is not allowed in interfaces
return.type.is.incompatible=The return type of {0} in {1} is incompatible with {2} in {3}
anonymous.class.derived.from=anonymous class derived from
@@ -43,9 +43,7 @@ import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.search.searches.SuperMethodsSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
@@ -90,6 +88,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
@@ -130,6 +129,29 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
}
myHolder = null;
}
else {
final PsiElement parent = element.getParent();
if (parent instanceof GrMethod) {
if (element.equals(((GrMethod)parent).getNameIdentifierGroovy()) &&
((GrMethod)parent).getReturnTypeElementGroovy() == null) {
checkMethodReturnType((GrMethod)parent, element, holder);
}
}
else if (parent instanceof GrField) {
final GrField field = (GrField)parent;
if (element.equals(field.getNameIdentifierGroovy())) {
final GrAccessorMethod[] getters = field.getGetters();
for (GrAccessorMethod getter : getters) {
checkMethodReturnType(getter, field.getNameIdentifierGroovy(), holder);
}
final GrAccessorMethod setter = field.getSetter();
if (setter != null) {
checkMethodReturnType(setter, field.getNameIdentifierGroovy(), holder);
}
}
}
}
}
@Override
@@ -777,6 +799,91 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator {
}
}
@Override
public void visitTypeElement(GrTypeElement typeElement) {
super.visitTypeElement(typeElement);
final PsiElement parent = typeElement.getParent();
if (!(parent instanceof GrMethod)) return;
checkMethodReturnType(((GrMethod)parent), typeElement, myHolder);
}
private static void checkMethodReturnType(PsiMethod method, PsiElement toHighlight, AnnotationHolder holder) {
final HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
final List<HierarchicalMethodSignature> superSignatures = signature.getSuperSignatures();
PsiType returnType = signature.getSubstitutor().substitute(method.getReturnType());
for (HierarchicalMethodSignature superMethodSignature : superSignatures) {
PsiMethod superMethod = superMethodSignature.getMethod();
PsiType declaredReturnType = superMethod.getReturnType();
PsiType superReturnType = superMethodSignature.getSubstitutor().substitute(declaredReturnType);
if (superReturnType == PsiType.VOID && method instanceof GrMethod && ((GrMethod)method).getReturnTypeElementGroovy() == null) return;
if (superMethodSignature.isRaw()) superReturnType = TypeConversionUtil.erasure(declaredReturnType);
if (returnType == null || superReturnType == null || method == superMethod) continue;
PsiClass superClass = superMethod.getContainingClass();
if (superClass == null) continue;
String highlightInfo = checkSuperMethodSignature(superMethod, superMethodSignature, superReturnType, method, signature, returnType);
if (highlightInfo != null) {
holder.createErrorAnnotation(toHighlight, highlightInfo);
return;
}
}
}
@Nullable
private static String checkSuperMethodSignature(PsiMethod superMethod,
MethodSignatureBackedByPsiMethod superMethodSignature,
PsiType superReturnType,
PsiMethod method,
MethodSignatureBackedByPsiMethod methodSignature,
PsiType returnType) {
if (superReturnType == null) return null;
PsiType substitutedSuperReturnType;
if (!superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5
PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature,
superMethodSignature);
substitutedSuperReturnType = unifyingSubstitutor == null
? superReturnType
: unifyingSubstitutor.substitute(superMethodSignature.getSubstitutor().substitute(superReturnType));
}
else {
substitutedSuperReturnType = TypeConversionUtil.erasure(superReturnType);
}
if (returnType.equals(substitutedSuperReturnType)) return null;
if (!(returnType instanceof PsiPrimitiveType) &&
substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType &&
TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) {
return null;
}
String qName = getQName(method);
String baseQName = getQName(superMethod);
final String presentation = returnType.getCanonicalText()+" "+GroovyPresentationUtil.getSignaturePresentation(methodSignature);
final String basePresentation = superReturnType.getCanonicalText()+" "+GroovyPresentationUtil.getSignaturePresentation(superMethodSignature);
return GroovyBundle.message("return.type.is.incompatible", presentation, qName, basePresentation, baseQName);
}
@NotNull
private static String getQName(PsiMethod method) {
final PsiClass aClass = method.getContainingClass();
if (aClass instanceof PsiAnonymousClass) {
return GroovyBundle.message("anonymous.class.derived.from") + " " + ((PsiAnonymousClass)aClass).getBaseClassType().getCanonicalText();
}
if (aClass != null) {
final String qname = aClass.getQualifiedName();
if (qname != null) {
return qname;
}
}
return "<null>";
}
private void checkTypeArgForPrimitive(@Nullable GrTypeElement element, String message) {
if (element == null || !(element.getType() instanceof PsiPrimitiveType)) return;
@@ -1244,4 +1244,48 @@ print Component
print List
''')
}
void testIncompatibleTypeOfImplicitGetter() {
testHighlighting('''\
abstract class Base {
abstract String getFoo()
}
class Inheritor extends Base {
final <error descr="The return type of java.lang.Object getFoo() in Inheritor is incompatible with java.lang.String getFoo() in Base">foo</error> = '3'
}''')
}
void testIncompatibleTypeOfInheritedMethod() {
testHighlighting('''\
abstract class Base {
abstract String getFoo()
}
class Inheritor extends Base {
def <error descr="The return type of java.lang.Object getFoo() in Inheritor is incompatible with java.lang.String getFoo() in Base">getFoo</error>() {''}
}''')
}
void testIncompatibleTypeOfInheritedMethod2() {
testHighlighting('''\
abstract class Base {
abstract String getFoo()
}
class Inheritor extends Base {
<error descr="The return type of java.lang.Object getFoo() in Inheritor is incompatible with java.lang.String getFoo() in Base">Object</error> getFoo() {''}
}''')
}
void testIncompatibleTypeOfInheritedMethodInAnonymous() {
testHighlighting('''\
abstract class Base {
abstract String getFoo()
}
new Base() {
<error descr="The return type of java.lang.Object getFoo() in anonymous class derived from Base is incompatible with java.lang.String getFoo() in Base">Object</error> getFoo() {''}
}''')
}
}