From ae7e4d24907f6366e5904ab2b7a2bee73d8f072c Mon Sep 17 00:00:00 2001 From: Shil Sinha Date: Tue, 29 Dec 2015 00:43:50 -0500 Subject: [PATCH] GROOVY-7036: An interface implementation (override) with a method including a default parameter value does not compile --- .../groovy/classgen/ExtendedVerifier.java | 78 ++++++++++++------- .../codehaus/groovy/classgen/Verifier.java | 2 + src/test/groovy/OverrideTest.groovy | 32 ++++++++ 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java b/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java index 07478f3e588..fce40637950 100644 --- a/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java +++ b/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java @@ -172,40 +172,23 @@ private void visitDeprecation(AnnotatedNode node, AnnotationNode visited) { private void visitOverride(AnnotatedNode node, AnnotationNode visited) { ClassNode annotationClassNode = visited.getClassNode(); if (annotationClassNode.isResolved() && annotationClassNode.getName().equals("java.lang.Override")) { - if (node instanceof MethodNode) { + if (node instanceof MethodNode && !Boolean.TRUE.equals(node.getNodeMetaData(Verifier.DEFAULT_PARAMETER_GENERATED))) { + boolean override = false; MethodNode origMethod = (MethodNode) node; - ClassNode cNode = node.getDeclaringClass(); - ClassNode next = cNode; - outer: - while (next != null) { - Map genericsSpec = createGenericsSpec(next); - MethodNode mn = correctToGenericsSpec(genericsSpec, origMethod); - if (next != cNode) { - ClassNode correctedNext = correctToGenericsSpecRecurse(genericsSpec, next); - MethodNode found = getDeclaredMethodCorrected(genericsSpec, mn, correctedNext); - if (found != null) break; - } - List ifaces = new ArrayList(); - ifaces.addAll(Arrays.asList(next.getInterfaces())); - Map updatedGenericsSpec = new HashMap(genericsSpec); - while (!ifaces.isEmpty()) { - ClassNode origInterface = ifaces.remove(0); - if (!origInterface.equals(ClassHelper.OBJECT_TYPE)) { - updatedGenericsSpec = createGenericsSpec(origInterface, updatedGenericsSpec); - ClassNode iNode = correctToGenericsSpecRecurse(updatedGenericsSpec, origInterface); - MethodNode found2 = getDeclaredMethodCorrected(updatedGenericsSpec, mn, iNode); - if (found2 != null) break outer; - ifaces.addAll(Arrays.asList(iNode.getInterfaces())); + ClassNode cNode = origMethod.getDeclaringClass(); + if (origMethod.hasDefaultValue()) { + List variants = cNode.getDeclaredMethods(origMethod.getName()); + for (MethodNode m : variants) { + if (m.getAnnotations().contains(visited) && isOverrideMethod(m)) { + override = true; + break; } } - ClassNode superClass = next.getUnresolvedSuperClass(); - if (superClass!=null) { - next = correctToGenericsSpecRecurse(updatedGenericsSpec, superClass); - } else { - next = null; - } + } else { + override = isOverrideMethod(origMethod); } - if (next == null) { + + if (!override) { addError("Method '" + origMethod.getName() + "' from class '" + cNode.getName() + "' does not override " + "method from its superclass or interfaces but is annotated with @Override.", visited); } @@ -213,6 +196,41 @@ private void visitOverride(AnnotatedNode node, AnnotationNode visited) { } } + private boolean isOverrideMethod(MethodNode method) { + ClassNode cNode = method.getDeclaringClass(); + ClassNode next = cNode; + outer: + while (next != null) { + Map genericsSpec = createGenericsSpec(next); + MethodNode mn = correctToGenericsSpec(genericsSpec, method); + if (next != cNode) { + ClassNode correctedNext = correctToGenericsSpecRecurse(genericsSpec, next); + MethodNode found = getDeclaredMethodCorrected(genericsSpec, mn, correctedNext); + if (found != null) break; + } + List ifaces = new ArrayList(); + ifaces.addAll(Arrays.asList(next.getInterfaces())); + Map updatedGenericsSpec = new HashMap(genericsSpec); + while (!ifaces.isEmpty()) { + ClassNode origInterface = ifaces.remove(0); + if (!origInterface.equals(ClassHelper.OBJECT_TYPE)) { + updatedGenericsSpec = createGenericsSpec(origInterface, updatedGenericsSpec); + ClassNode iNode = correctToGenericsSpecRecurse(updatedGenericsSpec, origInterface); + MethodNode found2 = getDeclaredMethodCorrected(updatedGenericsSpec, mn, iNode); + if (found2 != null) break outer; + ifaces.addAll(Arrays.asList(iNode.getInterfaces())); + } + } + ClassNode superClass = next.getUnresolvedSuperClass(); + if (superClass != null) { + next = correctToGenericsSpecRecurse(updatedGenericsSpec, superClass); + } else { + next = null; + } + } + return next != null; + } + private MethodNode getDeclaredMethodCorrected(Map genericsSpec, MethodNode mn, ClassNode correctedNext) { for (MethodNode orig : correctedNext.getDeclaredMethods(mn.getName())) { MethodNode method = correctToGenericsSpec(genericsSpec, orig); diff --git a/src/main/org/codehaus/groovy/classgen/Verifier.java b/src/main/org/codehaus/groovy/classgen/Verifier.java index e462d607d28..0c5f1fb1262 100644 --- a/src/main/org/codehaus/groovy/classgen/Verifier.java +++ b/src/main/org/codehaus/groovy/classgen/Verifier.java @@ -80,6 +80,7 @@ public class Verifier implements GroovyClassVisitor, Opcodes { public static final String STATIC_METACLASS_BOOL = "__$stMC"; public static final String SWAP_INIT = "__$swapInit"; public static final String INITIAL_EXPRESSION = "INITIAL_EXPRESSION"; + public static final String DEFAULT_PARAMETER_GENERATED = "DEFAULT_PARAMETER_GENERATED"; // NOTE: timeStamp constants shouldn't belong to Verifier but kept here // for binary compatibility @@ -809,6 +810,7 @@ public void visitVariableExpression(VariableExpression expression) { } addPropertyMethod(newMethod); newMethod.setGenericsTypes(method.getGenericsTypes()); + newMethod.putNodeMetaData(DEFAULT_PARAMETER_GENERATED, true); } }); } diff --git a/src/test/groovy/OverrideTest.groovy b/src/test/groovy/OverrideTest.groovy index ac67195678b..78ff192ff3a 100644 --- a/src/test/groovy/OverrideTest.groovy +++ b/src/test/groovy/OverrideTest.groovy @@ -142,4 +142,36 @@ def d = new Derived() """ assert message.contains("Method 'methodTakesObject' from class 'HasMethodWithBadArgType' does not override method from its superclass or interfaces but is annotated with @Override.") } + + void testOverrideOnMethodWithDefaultParameters() { + assertScript ''' + interface TemplatedInterface { + String execute(Map argument) + } + + class TemplatedInterfaceImplementation implements TemplatedInterface { + @Override + String execute(Map argument = [:]) { + return null + } + } + new TemplatedInterfaceImplementation() + ''' + } + + void testOverrideOnMethodWithDefaultParametersVariant() { + assertScript ''' + interface TemplatedInterface { + String execute(Map argument) + } + + class TemplatedInterfaceImplementation implements TemplatedInterface { + @Override + String execute(Map argument, String foo = null) { + return foo + } + } + new TemplatedInterfaceImplementation() + ''' + } }