This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 3a4978d09ff7d3c79e918f94dc523e5c3b43a929
Author: Paul King <[email protected]>
AuthorDate: Mon Mar 30 11:09:48 2026 +1000

    GROOVY-11888: STC: method resolution fails for UnionTypeClassNode due to 
premature covariant elimination
---
 .../transform/stc/StaticTypeCheckingSupport.java   |   6 +-
 .../transform/stc/TypeInferenceSTCTest.groovy      | 138 +++++++++++++++++++++
 2 files changed, 142 insertions(+), 2 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index cb84c11b1c..c7676a2ba4 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -953,8 +953,8 @@ public abstract class StaticTypeCheckingSupport {
 
         // GROOVY-8965: type disjunction
         boolean duckType = receiver instanceof UnionTypeClassNode;
-        if (methods.size() > 1 && !first(methods).isConstructor())
-            methods = removeCovariantsAndInterfaceEquivalents(methods, 
duckType);
+        if (!duckType && methods.size() > 1 && !first(methods).isConstructor())
+            methods = removeCovariantsAndInterfaceEquivalents(methods, false);
 
         if (!duckType && argumentTypes == null) {
             return asList(methods); // GROOVY-11683: no covariants or 
equivalents
@@ -965,6 +965,8 @@ public abstract class StaticTypeCheckingSupport {
             var view = methods;
             if (duckType) {
                 view = methods.stream().filter(m -> 
implementsInterfaceOrSubclassOf(rcvr, m.getDeclaringClass())).toList();
+                if (view.size() > 1 && !first(view).isConstructor())
+                    view = removeCovariantsAndInterfaceEquivalents(view, true);
             }
             view = chooseBestMethods(rcvr, view, argumentTypes);
             if (view.isEmpty()) {
diff --git a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy 
b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
index c6a9a17c1b..4adea4b051 100644
--- a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -431,6 +431,144 @@ class TypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
         'Incompatible instanceof types: java.lang.Integer and java.lang.Long'
     }
 
+    // GROOVY-7971: nested && within || — method calls on narrowed types within
+    // each && branch should work; the || produces a union for the body
+    @Test
+    void testInstanceOf18() {
+        assertScript '''
+            int test(Object x) {
+                if (x instanceof String && x.length() > 0 || x instanceof List 
&& x.size() > 0) {
+                    return 1
+                }
+                return 0
+            }
+            assert test('hello') == 1
+            assert test([1, 2, 3]) == 1
+            assert test('') == 0
+            assert test([]) == 0
+            assert test(42) == 0
+        '''
+    }
+
+    // GROOVY-7971: ternary with || instanceof in condition
+    @Test
+    void testInstanceOf19() {
+        assertScript '''
+            String test(Object x) {
+                (x instanceof String || x instanceof Integer) ? x.toString() : 
'other'
+            }
+            assert test('hi') == 'hi'
+            assert test(42) == '42'
+            assert test(3.14) == 'other'
+        '''
+    }
+
+    // GROOVY-7971: negated || instanceof
+    @Test
+    void testInstanceOf20() {
+        assertScript '''
+            void test(Object x) {
+                if (!(x instanceof String || x instanceof Integer)) {
+                    assert x != null
+                }
+            }
+            test('hello')
+            test(42)
+            test(3.14)
+        '''
+    }
+
+    // GROOVY-7971: chained || with 3+ instanceof checks
+    @Test
+    void testInstanceOf21() {
+        assertScript '''
+            void test(Object x) {
+                if (x instanceof String || x instanceof Integer || x 
instanceof List) {
+                    assert "$x" != null // should be String|Integer|List
+                }
+            }
+            test('hello')
+            test(42)
+            test([1, 2])
+        '''
+    }
+
+    // GROOVY-7971: RHS of || should not see LHS instanceof narrowing
+    @Test
+    void testInstanceOf22() {
+        assertScript '''
+            void test(Number n) {
+                if (n instanceof Integer || n.doubleValue() > 0) {
+                    assert "$n" != null // n should be Integer|Number
+                }
+            }
+            test(42)
+            test(1.5)
+        '''
+    }
+
+    // GROOVY-7971: closure shared variable with || instanceof
+    @Test
+    void testInstanceOf23() {
+        assertScript '''
+            void test(Object x) {
+                if (x instanceof String || x instanceof Integer) {
+                    def c = { -> x.toString() }
+                    assert c() != null
+                }
+            }
+            test('hello')
+            test(42)
+        '''
+    }
+
+    // GROOVY-11888: method resolution on union type — toString() is on Object
+    // and should be found via (String|List) union
+    @Test
+    void testInstanceOf24() {
+        assertScript '''
+            void test(Object x) {
+                if (x instanceof String || x instanceof List) {
+                    assert x.toString() != null
+                }
+            }
+            test('hello')
+            test([1, 2])
+        '''
+    }
+
+    // GROOVY-7971: negated || instanceof — re-check instanceof in else branch
+    @Test @org.junit.jupiter.api.Disabled('requires instanceof compatibility 
fix for UnionTypeClassNode')
+    void testInstanceOf25() {
+        assertScript '''
+            void test(Object x) {
+                if (!(x instanceof String || x instanceof Integer)) {
+                    assert x != null
+                } else {
+                    assert x instanceof String || x instanceof Integer
+                }
+            }
+            test('hello')
+            test(42)
+            test(3.14)
+        '''
+    }
+
+    // GROOVY-11888: method resolution on union type — intValue() is on Number
+    // and should be found via (Integer|Number) union
+    @Test
+    void testInstanceOf26() {
+        assertScript '''
+            void test(Number n) {
+                if (n instanceof Integer || n.intValue() > 0) {
+                    assert n.intValue() >= 0
+                }
+            }
+            test(42)
+            test(1.5)
+        '''
+    }
+
     // GROOVY-5226
     @Test
     void testNestedInstanceOf1() {

Reply via email to