diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties index 74af94cba321..8c04a8d2f36d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties @@ -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 diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java index 2e3bcbdb7d91..70516de74280 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java @@ -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 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 ""; + } + + private void checkTypeArgForPrimitive(@Nullable GrTypeElement element, String message) { if (element == null || !(element.getType() instanceof PsiPrimitiveType)) return; diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy index 85484e2ad2c4..61bc81c9166e 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy @@ -1244,4 +1244,48 @@ print Component print List ''') } + + void testIncompatibleTypeOfImplicitGetter() { + testHighlighting('''\ +abstract class Base { + abstract String getFoo() +} + +class Inheritor extends Base { + final foo = '3' +}''') + } + + void testIncompatibleTypeOfInheritedMethod() { + testHighlighting('''\ +abstract class Base { + abstract String getFoo() +} + +class Inheritor extends Base { + def getFoo() {''} +}''') + } + + void testIncompatibleTypeOfInheritedMethod2() { + testHighlighting('''\ +abstract class Base { + abstract String getFoo() +} + +class Inheritor extends Base { + Object getFoo() {''} +}''') + } + + void testIncompatibleTypeOfInheritedMethodInAnonymous() { + testHighlighting('''\ +abstract class Base { + abstract String getFoo() +} + +new Base() { + Object getFoo() {''} +}''') + } } \ No newline at end of file