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:21 UTC
(groovy) branch master updated (ccb5e48319 -> 9b3eeb9e22)
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
from ccb5e48319 GROOVY-7971, GROOVY-8337, GROOVY-8965: logical-or `instanceof` guards
new af72676fda GROOVY-10702: STC: multiple `instanceof` and sparse method call
new 9b3eeb9e22 GROOVY-10702: STC: multiple `instanceof` and sparse property access
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../transform/stc/StaticTypeCheckingSupport.java | 22 +++-
.../transform/stc/StaticTypeCheckingVisitor.java | 13 ++-
.../transform/stc/TypeInferenceSTCTest.groovy | 124 ++++++++++++++-------
3 files changed, 113 insertions(+), 46 deletions(-)
(groovy) 02/02: GROOVY-10702: STC: multiple `instanceof` and sparse property access
Posted by em...@apache.org.
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 9b3eeb9e22da47c5650a187fcb8e1b23936321e5
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat Dec 27 15:09:20 2025 -0600
GROOVY-10702: STC: multiple `instanceof` and sparse property access
---
.../transform/stc/StaticTypeCheckingVisitor.java | 13 ++++-
.../transform/stc/TypeInferenceSTCTest.groovy | 65 ++++++++++++++--------
2 files changed, 53 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 5b5e709a82..fec219cd57 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1653,9 +1653,18 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0
List<Receiver<String>> receivers = new ArrayList<>();
addReceivers(receivers, makeOwnerList(objectExpression), pexp.isImplicitThis());
- for (Receiver<String> receiver : receivers) {
+ on: for (Receiver<String> receiver : receivers) {
ClassNode receiverType = receiver.getType();
-
+ if (receiverType instanceof UnionTypeClassNode ut) {
+ for (ClassNode type : ut.getDelegates()) { // GROOVY-8965, GROOVY-10702
+ var copy = (PropertyExpression) pexp.transformExpression(ex -> ex);
+ copy.setObjectExpression(varX("_", type));
+ if (!existsProperty(copy, readMode, null))
+ continue on;
+ }
+ pexp.putNodeMetaData(DYNAMIC_RESOLUTION, Boolean.TRUE);
+ return true;
+ }
if (receiverType.isArray() && propertyName.equals("length")) {
pexp.putNodeMetaData(READONLY_PROPERTY, Boolean.TRUE);
storeType(pexp, int_TYPE);
diff --git a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
index 94d38ad2c0..884423da79 100644
--- a/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/TypeInferenceSTCTest.groovy
@@ -403,17 +403,17 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
int foo() { 1 }
}
class B {
- int foo2() { 2 }
+ int bar() { 2 }
}
def o = new A()
- int result = o instanceof A ? o.foo() : (o instanceof B ? o.foo2() : 3)
+ int result = o instanceof A ? o.foo() : (o instanceof B ? o.bar() : 3)
assert result == 1
o = new B()
- result = o instanceof A ? o.foo() : (o instanceof B ? o.foo2() : 3)
+ result = o instanceof A ? o.foo() : (o instanceof B ? o.bar() : 3)
assert result == 2
o = new Object()
- result = o instanceof A ? o.foo() : (o instanceof B ? o.foo2() : 3)
+ result = o instanceof A ? o.foo() : (o instanceof B ? o.bar() : 3)
assert result == 3
'''
}
@@ -421,22 +421,43 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
void testMultipleInstanceOf1() {
assertScript '''
class A {
- void bar() {
+ void m() {
}
}
class B {
- void bar() {
+ void m() {
}
}
void test(o) {
if (o instanceof A || o instanceof B) {
- o.bar()
+ o.m()
}
}
test(new A())
test(new B())
'''
+
+ assertScript '''
+ class A {
+ float getP() {
+ return 1.0
+ }
+ }
+ class B {
+ short getP() {
+ return 2
+ }
+ }
+
+ def foo(o) {
+ if (o instanceof A || o instanceof B) {
+ return o.p
+ }
+ }
+ assert foo(new A()) == 1
+ assert foo(new B()) == 2
+ '''
}
void testMultipleInstanceOf2() {
@@ -465,19 +486,17 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-8965
void testMultipleInstanceOf4() {
- for (o in ['o', '((Number) o)']) {
- assertScript """
- def foo(o) {
- if (o instanceof Integer || o instanceof Double) {
- ${o}.floatValue() // ClassCastException
- }
+ assertScript '''
+ def foo(o) {
+ if (o instanceof Integer || o instanceof Double) {
+ o.floatValue() // ClassCastException
}
- def bar = foo(1.1d)
- assert bar == 1.1f
- def baz = foo(1)
- assert baz == 1
- """
- }
+ }
+ def bar = foo(1.1d)
+ assert bar == 1.1f
+ def baz = foo(1)
+ assert baz == 1
+ '''
}
// GROOVY-11559
@@ -1312,7 +1331,7 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
}
''',
'No such property: canonicalName for class: java.lang.Object'
-/*
+
shouldFailWithMessages '''
void test(something) {
switch (something) {
@@ -1320,12 +1339,12 @@ class TypeInferenceSTCTest extends StaticTypeCheckingTestCase {
case File:
something.toString()
default:
- something.canonicalName // TODO: GROOVY-10702
+ something.canonicalName // GROOVY-10702
}
}
''',
- 'No such property: canonicalName for class: java.lang.Object'
-*/
+ 'No such property: canonicalName for class: (java.lang.Class | java.io.File | java.lang.Object)'
+
shouldFailWithMessages '''
void test(something) {
switch (something) {
(groovy) 01/02: GROOVY-10702: STC: multiple `instanceof` and sparse method call
Posted by em...@apache.org.
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)
'''
}