Skip to content

Commit 0814117

Browse files
committed
GROOVY-11888: STC: method resolution fails for UnionTypeClassNode due to premature covariant elimination
1 parent 3b4d02f commit 0814117

2 files changed

Lines changed: 142 additions & 2 deletions

File tree

src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -953,8 +953,8 @@ public static List<MethodNode> chooseBestMethod(final ClassNode receiver, Collec
953953

954954
// GROOVY-8965: type disjunction
955955
boolean duckType = receiver instanceof UnionTypeClassNode;
956-
if (methods.size() > 1 && !first(methods).isConstructor())
957-
methods = removeCovariantsAndInterfaceEquivalents(methods, duckType);
956+
if (!duckType && methods.size() > 1 && !first(methods).isConstructor())
957+
methods = removeCovariantsAndInterfaceEquivalents(methods, false);
958958

959959
if (!duckType && argumentTypes == null) {
960960
return asList(methods); // GROOVY-11683: no covariants or equivalents
@@ -965,6 +965,8 @@ public static List<MethodNode> chooseBestMethod(final ClassNode receiver, Collec
965965
var view = methods;
966966
if (duckType) {
967967
view = methods.stream().filter(m -> implementsInterfaceOrSubclassOf(rcvr, m.getDeclaringClass())).toList();
968+
if (view.size() > 1)
969+
view = removeCovariantsAndInterfaceEquivalents(view, true);
968970
}
969971
view = chooseBestMethods(rcvr, view, argumentTypes);
970972
if (view.isEmpty()) {

src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,144 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
431431
'Incompatible instanceof types: java.lang.Integer and java.lang.Long'
432432
}
433433

434+
// GROOVY-7971: nested && within || — method calls on narrowed types within
435+
// each && branch should work; the || produces a union for the body
436+
@Test
437+
void testInstanceOf18() {
438+
assertScript '''
439+
int test(Object x) {
440+
if (x instanceof String && x.length() > 0 || x instanceof List && x.size() > 0) {
441+
return 1
442+
}
443+
return 0
444+
}
445+
assert test('hello') == 1
446+
assert test([1, 2, 3]) == 1
447+
assert test('') == 0
448+
assert test([]) == 0
449+
assert test(42) == 0
450+
'''
451+
}
452+
453+
// GROOVY-7971: ternary with || instanceof in condition
454+
@Test
455+
void testInstanceOf19() {
456+
assertScript '''
457+
String test(Object x) {
458+
(x instanceof String || x instanceof Integer) ? x.toString() : 'other'
459+
}
460+
assert test('hi') == 'hi'
461+
assert test(42) == '42'
462+
assert test(3.14) == 'other'
463+
'''
464+
}
465+
466+
// GROOVY-7971: negated || instanceof
467+
@Test
468+
void testInstanceOf20() {
469+
assertScript '''
470+
void test(Object x) {
471+
if (!(x instanceof String || x instanceof Integer)) {
472+
assert x != null
473+
}
474+
}
475+
test('hello')
476+
test(42)
477+
test(3.14)
478+
'''
479+
}
480+
481+
// GROOVY-7971: chained || with 3+ instanceof checks
482+
@Test
483+
void testInstanceOf21() {
484+
assertScript '''
485+
void test(Object x) {
486+
if (x instanceof String || x instanceof Integer || x instanceof List) {
487+
assert "$x" != null // should be String|Integer|List
488+
}
489+
}
490+
test('hello')
491+
test(42)
492+
test([1, 2])
493+
'''
494+
}
495+
496+
// GROOVY-7971: RHS of || should not see LHS instanceof narrowing
497+
@Test
498+
void testInstanceOf22() {
499+
assertScript '''
500+
void test(Number n) {
501+
if (n instanceof Integer || n.doubleValue() > 0) {
502+
assert "$n" != null // n should be Integer|Number
503+
}
504+
}
505+
test(42)
506+
test(1.5)
507+
'''
508+
}
509+
510+
// GROOVY-7971: closure shared variable with || instanceof
511+
@Test
512+
void testInstanceOf23() {
513+
assertScript '''
514+
void test(Object x) {
515+
if (x instanceof String || x instanceof Integer) {
516+
def c = { -> x.toString() }
517+
assert c() != null
518+
}
519+
}
520+
test('hello')
521+
test(42)
522+
'''
523+
}
524+
525+
// GROOVY-11888: method resolution on union type — toString() is on Object
526+
// and should be found via (String|List) union
527+
@Test
528+
void testInstanceOf24() {
529+
assertScript '''
530+
void test(Object x) {
531+
if (x instanceof String || x instanceof List) {
532+
assert x.toString() != null
533+
}
534+
}
535+
test('hello')
536+
test([1, 2])
537+
'''
538+
}
539+
540+
// GROOVY-7971: negated || instanceof — re-check instanceof in else branch
541+
@Test @org.junit.jupiter.api.Disabled('requires instanceof compatibility fix for UnionTypeClassNode')
542+
void testInstanceOf25() {
543+
assertScript '''
544+
void test(Object x) {
545+
if (!(x instanceof String || x instanceof Integer)) {
546+
assert x != null
547+
} else {
548+
assert x instanceof String || x instanceof Integer
549+
}
550+
}
551+
test('hello')
552+
test(42)
553+
test(3.14)
554+
'''
555+
}
556+
557+
// GROOVY-11888: method resolution on union type — intValue() is on Number
558+
// and should be found via (Integer|Number) union
559+
@Test
560+
void testInstanceOf26() {
561+
assertScript '''
562+
void test(Number n) {
563+
if (n instanceof Integer || n.intValue() > 0) {
564+
assert n.intValue() >= 0
565+
}
566+
}
567+
test(42)
568+
test(1.5)
569+
'''
570+
}
571+
434572
// GROOVY-5226
435573
@Test
436574
void testNestedInstanceOf1() {

0 commit comments

Comments
 (0)