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 2025/12/27 21:58:22 UTC
(groovy) 01/02: GROOVY-10702: STC: multiple `instanceof` and sparse method call
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit af72676fda22f601c7197260b8df9bd17ff301a3
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat Dec 27 11:32:27 2025 -0600
GROOVY-10702: STC: multiple `instanceof` and sparse method call
---
.../transform/stc/StaticTypeCheckingSupport.java | 22 ++++++--
.../transform/stc/TypeInferenceSTCTest.groovy | 59 +++++++++++++++-------
2 files changed, 60 insertions(+), 21 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 9eb04af018..519cf395f9 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -129,6 +129,7 @@ import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory;
import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asList;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.first;
import static org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport.closeQuietly;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL;
@@ -870,6 +871,13 @@ public abstract class StaticTypeCheckingSupport {
if (type.isArray()) {
return prettyPrintTypeName(type.getComponentType()) + "[]";
}
+ if (type instanceof UnionTypeClassNode union) {
+ var sj = new StringJoiner(" | ", "(", ")");
+ for (ClassNode cn : union.getDelegates()) {
+ sj.add(prettyPrintTypeName(cn));
+ }
+ return sj.toString();
+ }
return type.isGenericsPlaceHolder() ? type.getUnresolvedName() : type.getText();
}
@@ -1036,16 +1044,24 @@ public abstract class StaticTypeCheckingSupport {
// GROOVY-8965: type disjunction
boolean duckType = receiver instanceof UnionTypeClassNode;
- if (methods.size() > 1 && !methods.iterator().next().isConstructor())
+ if (methods.size() > 1 && !first(methods).isConstructor())
methods = removeCovariantsAndInterfaceEquivalents(methods, duckType);
- if (argumentTypes == null) {
+ if (!duckType && argumentTypes == null) {
return asList(methods); // GROOVY-11683: no covariants or equivalents
}
Set<MethodNode> bestMethods = new HashSet<>(); // choose best method(s) for each possible receiver
for (ClassNode rcvr : duckType ? ((UnionTypeClassNode) receiver).getDelegates() : new ClassNode[]{receiver}) {
- bestMethods.addAll(chooseBestMethods(rcvr, methods, argumentTypes));
+ var view = methods;
+ if (duckType) {
+ view = methods.stream().filter(m -> implementsInterfaceOrSubclassOf(rcvr, m.getDeclaringClass())).toList();
+ }
+ view = chooseBestMethods(rcvr, view, argumentTypes);
+ if (view.isEmpty()) {
+ return Collections.emptyList(); // GROOVY-10702
+ }
+ bestMethods.addAll(view);
}
return new LinkedList<>(bestMethods); // assumes caller wants remove to be inexpensive
}
diff --git a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
index 54f870a331..94d38ad2c0 100644
--- a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -542,25 +542,48 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-10668
void testMultipleInstanceOf8() {
- for (string in ['(value as String)', 'value.toString()', 'value']) {
+ for (string in ['(value as String)', 'value.toString()']) {
assertScript """
def toArray(Object value) {
def array
- if (value instanceof List)
+ if (value instanceof List) {
array = value.toArray()
- else if (value instanceof String || value instanceof GString)
+ } else if (value instanceof String || value instanceof GString) {
array = ${string}.split(',')
- else
+ } else {
throw new Exception('not supported')
-
+ }
return array
}
- toArray([1,2,3])
- toArray('1,2,3')
+ assert toArray([1,2,3]) == new Object[]{1,2,3}
+ assert toArray('1,2,3') == new String[]{'1','2','3'}
+ assert toArray("\${1}") == new String[]{'1'}
"""
}
}
+ // GROOVY-10702
+ void testMultipleInstanceOf9() {
+ shouldFailWithMessages '''
+ def toArray(value) {
+ def array
+ if (value instanceof String || value instanceof GString) {
+ array = value.split(',') // split(String) not declared for GString/CharSequence
+ }
+ array
+ }
+ ''',
+ 'Cannot find matching method (java.lang.String | groovy.lang.GString)#split(java.lang.String)'
+
+ shouldFailWithMessages '''
+ def test(o) {
+ (o instanceof List || o instanceof Map) ? o.entrySet() : null
+ }
+ test([])
+ ''',
+ 'Cannot find matching method (java.util.List | java.util.Map)#entrySet()'
+ }
+
// GROOVY-6429
void testNotInstanceof1() {
String types = '''
@@ -1603,46 +1626,46 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
'''
}
- // GROOVY-
+ // GROOVY-6207
void testGetAnnotationFails() {
assertScript '''
import groovy.transform.*
import java.lang.annotation.*
@Retention(RetentionPolicy.RUNTIME)
- @Target([ElementType.FIELD])
- @interface Ann1 {}
+ @Target(ElementType.FIELD)
+ @interface Tag1 {}
@Retention(RetentionPolicy.RUNTIME)
- @Target([ElementType.FIELD])
- @interface Ann2 {}
+ @Target(ElementType.FIELD)
+ @interface Tag2 {}
class A {
- @Ann2
+ @Tag2
String field
}
@ASTTest(phase=INSTRUCTION_SELECTION, value={
lookup('second').each {
- assert it.expression.getNodeMetaData(INFERRED_TYPE).name == 'Ann2'
+ assert it.expression.getNodeMetaData(INFERRED_TYPE).name == 'Tag2'
}
})
def doit(obj, String propName) {
def field = obj.getClass().getDeclaredField(propName)
if (field) {
@ASTTest(phase=INSTRUCTION_SELECTION, value={
- assert node.getNodeMetaData(INFERRED_TYPE).name == 'Ann1'
+ assert node.getNodeMetaData(INFERRED_TYPE).name == 'Tag1'
})
- def annotation = field.getAnnotation Ann1
+ def annotation = field.getAnnotation Tag1
if(true) {
- second: annotation = field.getAnnotation Ann2
+ second: annotation = field.getAnnotation Tag2
}
return annotation
}
return null
}
- assert Ann2.isAssignableFrom(doit(new A(), "field").class)
+ assert Tag2.isAssignableFrom(doit(new A(), 'field').class)
'''
}