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 <[email protected]> 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) ''' }
