You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2021/08/17 16:48:50 UTC

[groovy] 03/04: GROOVY-10027: NamedParam: check against declared/inferred argument type

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 <er...@thomsonreuters.com>
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)