This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY_3_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 39917b8b3ac89fbe7bf15139ed09058aee780d76 Author: Eric Milles <[email protected]> AuthorDate: Fri Apr 9 14:31:37 2021 -0500 GROOVY-10027: NamedParam: check against declared/inferred argument type --- .../transform/stc/StaticTypeCheckingVisitor.java | 15 ++-- src/test/groovy/NamedParameterTest.groovy | 80 ++++++++++++++++++---- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 41256b3..7be2912 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -2783,21 +2783,16 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { } if (!entries.containsKey(name)) { if (required) { - addStaticTypeError("required named arg '" + name + "' not found.", expression); + addStaticTypeError("required named param '" + name + "' not found.", expression); } - } else { - Expression supplied = entries.get(name); - if (isCompatibleType(expectedType, expectedType != null, supplied.getType())) { - addStaticTypeError("parameter for named arg '" + name + "' has type '" + prettyPrintType(supplied.getType()) + - "' but expected '" + prettyPrintType(expectedType) + "'.", expression); + } else if (expectedType != null) { + ClassNode argumentType = getDeclaredOrInferredType(entries.get(name)); + if (!isAssignableTo(argumentType, expectedType)) { + addStaticTypeError("argument for named param '" + name + "' has type '" + prettyPrintType(argumentType) + "' but expected '" + prettyPrintType(expectedType) + "'.", expression); } } } - private boolean isCompatibleType(final ClassNode expectedType, final boolean b, final ClassNode type) { - return b && !isAssignableTo(type, expectedType); - } - /** * This method is responsible for performing type inference on closure argument types whenever code like this is * found: <code>foo.collect { it.toUpperCase() }</code>. diff --git a/src/test/groovy/NamedParameterTest.groovy b/src/test/groovy/NamedParameterTest.groovy index 4ff1553..cc0af57 100644 --- a/src/test/groovy/NamedParameterTest.groovy +++ b/src/test/groovy/NamedParameterTest.groovy @@ -25,12 +25,12 @@ import groovy.transform.TypeChecked import static groovy.NamedParameterHelper.myJavaMethod -class NamedParameterTest extends GroovyTestCase { +final class NamedParameterTest extends GroovyTestCase { void testPassingNamedParametersToMethod() { someMethod(name:"gromit", eating:"nice cheese", times:2) } - + protected void someMethod(args) { assert args.name == "gromit" assert args.eating == "nice cheese" @@ -70,12 +70,15 @@ class NamedParameterTest extends GroovyTestCase { import groovy.transform.TypeChecked import static groovy.NamedParameterTest.myMethod + int getAnswer() { 42 } + @TypeChecked - def method() { - assert myMethod(foo: 'FOO', bar: 'BAR') == 'foo = FOO, bar = BAR' - assert myMethod(bar: 'BAR') == 'foo = null, bar = BAR' - assert myMethod(foo: 'FOO', bar: 45, 442) == 'foo = FOO, bar = 45, num = 442' - assert myMethod(foo: 'FOO', 542) == 'foo = FOO, bar = null, num = 542' + def method() { + assert myMethod(foo: 'FOO', bar: 'BAR') == 'foo = FOO, bar = BAR' + assert myMethod(bar: 'BAR') == 'foo = null, bar = BAR' + assert myMethod(foo: 'FOO', bar: 45, 442) == 'foo = FOO, bar = 45, num = 442' + assert myMethod(foo: 'FOO', 542) == 'foo = FOO, bar = null, num = 542' + assert myMethod(foo: 'string', bar: answer, 666) == 'foo = string, bar = 42, num = 666' // GROOVY-10027 } method() ''' @@ -87,12 +90,12 @@ class NamedParameterTest extends GroovyTestCase { import static groovy.NamedParameterTest.myMethod @TypeChecked - def method() { + def method() { myMethod(foo: 'FOO') } method() ''' - assert message.contains("required named arg 'bar' not found") + assert message.contains("required named param 'bar' not found") } void testUnknownName() { @@ -101,7 +104,7 @@ class NamedParameterTest extends GroovyTestCase { import static groovy.NamedParameterTest.myMethod @TypeChecked - def method() { + def method() { myMethod(bar: 'BAR', baz: 'BAZ') } method() @@ -109,20 +112,67 @@ class NamedParameterTest extends GroovyTestCase { assert message.contains("unexpected named arg: baz") } - void testInvalidType() { + // GROOVY-10027 + void testFlowType() { + assertScript ''' + import groovy.transform.TypeChecked + import static groovy.NamedParameterTest.myMethod + + @TypeChecked + def method(arg) { + if (arg instanceof Integer) { + myMethod(foo: 'x', bar: arg, 123) + } + } + assert method(42) == 'foo = x, bar = 42, num = 123' + ''' + } + + void testInvalidType1() { def message = shouldFail ''' import groovy.transform.TypeChecked import static groovy.NamedParameterTest.myMethod @TypeChecked - def method() { - myMethod(foo: 42, 42) + def method() { + myMethod(foo:42, -1) + } + ''' + assert message.contains("argument for named param 'foo' has type 'int' but expected 'java.lang.String'") + } + + void testInvalidType2() { + def message = shouldFail ''' + import groovy.transform.TypeChecked + import static groovy.NamedParameterTest.myMethod + + @TypeChecked + def method() { + int answer = 42 + myMethod(foo:answer, -1) + } + ''' + assert message.contains("argument for named param 'foo' has type 'int' but expected 'java.lang.String'") + } + + // GROOVY-10027 + void testInvalidType3() { + def message = shouldFail ''' + import groovy.transform.TypeChecked + import static groovy.NamedParameterTest.myMethod + + int getAnswer() { 42 } + + @TypeChecked + def method() { + myMethod(foo:answer, -1) } - method() ''' - assert message.contains("parameter for named arg 'foo' has type 'int' but expected 'java.lang.String'") + assert message.contains("argument for named param 'foo' has type 'int' but expected 'java.lang.String'") } + //-------------------------------------------------------------------------- + static String myMethod(@NamedParams([ @NamedParam(value = "foo"), @NamedParam(value = "bar", type = String, required = true)
