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 2022/01/30 22:16:38 UTC
[groovy] branch GROOVY_3_0_X updated: GROOVY-10457: support `@CompileStatic` class with `@CompileDynamic` ctor
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
new 80bca46 GROOVY-10457: support `@CompileStatic` class with `@CompileDynamic` ctor
80bca46 is described below
commit 80bca46a3794ffb37ae082ac719c08e6b93eb697
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Jan 21 13:45:48 2022 -0600
GROOVY-10457: support `@CompileStatic` class with `@CompileDynamic` ctor
Conflicts:
src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
---
.../transform/sc/StaticCompilationVisitor.java | 57 ++++++----
.../classgen/asm/sc/CompileDynamicTest.groovy | 40 +++++--
.../asm/sc/MixedModeStaticCompilationTest.groovy | 14 +++
.../asm/sc/StaticCompileConstructorsTest.groovy | 126 +++++++++------------
4 files changed, 131 insertions(+), 106 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
index 4b064ae..a626f73 100644
--- a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -211,37 +211,44 @@ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
return false;
}
- /**
- * If we are in a constructor, that is static compiled, but in a class, that
- * is not, it may happen that init code from object initializers, fields
- * or properties is added into the constructor code. The backend assumes
- * a purely static constructor, so it may fail if it encounters dynamic
- * code here. Thus we make this kind of code fail
- */
- private void checkForConstructorWithCSButClassWithout(final MethodNode node) {
- if (!(node instanceof ConstructorNode)) return;
- if (!Boolean.TRUE.equals(node.getNodeMetaData(STATIC_COMPILE_NODE))) return;
- ClassNode outerClass = typeCheckingContext.getEnclosingClassNode();
- if (Boolean.TRUE.equals(outerClass.getNodeMetaData(STATIC_COMPILE_NODE))) return;
- if (outerClass.getObjectInitializerStatements().isEmpty()
- && outerClass.getFields().isEmpty() && outerClass.getProperties().isEmpty()) {
- return;
+ private void visitConstructorOrMethod(final MethodNode node) {
+ boolean isSkipped = isSkipMode(node); // @CompileDynamic
+ boolean isSC = !isSkipped && isStaticallyCompiled(node);
+ if (isSkipped) {
+ node.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.FALSE);
+ }
+ if (node instanceof ConstructorNode) {
+ super.visitConstructor((ConstructorNode) node);
+ ClassNode declaringClass = node.getDeclaringClass();
+ if (isSC && !isStaticallyCompiled(declaringClass)) {
+ // In a constructor that is statically compiled within a class that is
+ // not, it may happen that init code from object initializers, fields or
+ // properties is added into the constructor code. The backend assumes a
+ // purely static constructor, so it may fail if it encounters dynamic
+ // code here. Thus we make this kind of code fail.
+ if (!declaringClass.getFields().isEmpty()
+ || !declaringClass.getProperties().isEmpty()
+ || !declaringClass.getObjectInitializerStatements().isEmpty()) {
+ addStaticTypeError("Cannot statically compile constructor implicitly including non-static elements from fields, properties or initializers", node);
+ }
+ }
+ } else {
+ super.visitMethod(node);
}
+ if (isSC) {
+ ClassNode declaringClass = node.getDeclaringClass();
+ addDynamicOuterClassAccessorsCallback(declaringClass);
+ }
+ }
- addStaticTypeError("Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields.",node);
+ @Override
+ public void visitConstructor(final ConstructorNode node) {
+ visitConstructorOrMethod(node);
}
@Override
public void visitMethod(final MethodNode node) {
- if (isSkipMode(node)) {
- node.putNodeMetaData(STATIC_COMPILE_NODE, Boolean.FALSE);
- }
- super.visitMethod(node);
- checkForConstructorWithCSButClassWithout(node);
- if (isStaticallyCompiled(node)) {
- ClassNode declaringClass = node.getDeclaringClass();
- addDynamicOuterClassAccessorsCallback(declaringClass);
- }
+ visitConstructorOrMethod(node);
}
/**
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/CompileDynamicTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/CompileDynamicTest.groovy
index c6db3f6..22bdb30 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/CompileDynamicTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/CompileDynamicTest.groovy
@@ -19,21 +19,47 @@
package org.codehaus.groovy.classgen.asm.sc
import groovy.transform.stc.StaticTypeCheckingTestCase
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
/**
* Test case for {@link groovy.transform.CompileDynamic}.
*/
-class CompileDynamicTest extends StaticTypeCheckingTestCase implements StaticCompilationTestSupport {
+final class CompileDynamicTest extends StaticTypeCheckingTestCase implements StaticCompilationTestSupport {
- void testCompileDynamic() {
- assertScript '''import groovy.transform.CompileDynamic
- class Foo {
+ @Override
+ protected void setUp() {
+ super.setUp()
+ def customizers = config.compilationCustomizers
+ // ASTTransformationCustomizer(CompileStatic) only uses visitMethod
+ customizers.removeAll { it instanceof ASTTransformationCustomizer }
+ customizers[0].addImports('groovy.transform.CompileDynamic', 'groovy.transform.CompileStatic')
+ }
+
+ void testCompileDynamicMethod() {
+ assertScript '''
+ @CompileStatic
+ class C {
+ @CompileDynamic
+ void skip() {
+ int i = 'cannot assign string to int'
+ }
+ }
+ new C()
+ '''
+ }
+
+ // GROOVY-10457
+ void testCompileDynamicConstructor() {
+ assertScript '''
+ @CompileStatic
+ class C {
@CompileDynamic
- void skipped() {
- int i = 'should not pass'
+ C() {
+ String result = new StringReader('works').text
+ assert result == 'works'
}
}
- new Foo()
+ new C()
'''
}
}
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/MixedModeStaticCompilationTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/MixedModeStaticCompilationTest.groovy
index ebd2b63..0de8d56 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/MixedModeStaticCompilationTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/MixedModeStaticCompilationTest.groovy
@@ -289,6 +289,20 @@ final class MixedModeStaticCompilationTest extends StaticTypeCheckingTestCase im
}
+ void testDynamicClassWithStaticConstructorAndInitialization() {
+ shouldFailWithMessages '''
+ class A {
+ }
+ class B {
+ A a = new A() // may require dynamic support...
+ @groovy.transform.CompileStatic
+ B() {
+ }
+ }
+ ''',
+ 'Cannot statically compile constructor implicitly including non-static elements from fields, properties or initializers'
+ }
+
void testSCClosureCanAccessPrivateFieldsOfNonSCEnclosingClass() {
assertScript '''
class Test {
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
index e44cec6..dce86d3 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy
@@ -26,95 +26,73 @@ import groovy.transform.stc.ConstructorsSTCTest
class StaticCompileConstructorsTest extends ConstructorsSTCTest implements StaticCompilationTestSupport {
void testMapConstructorError() {
- assertScript '''import groovy.transform.Canonical
-
- class WTF {
- public static void main(String[] args) {
- new Person(name:"First")
- first(new Person(name:"First"))
+ assertScript '''
+ class C {
+ static void test() {
+ new Person(name:"First")
+ first(new Person(name:"First"))
+ }
+ static Person first(Person p) {
+ p
+ }
}
-
- static Person first(Person p) {
- p
+ @groovy.transform.Canonical
+ class Person {
+ String name
}
- }
-
- @Canonical
- class Person {
- String name
- }
- WTF.main()
+ C.test()
'''
}
-
- void testMixedDynamicStaticConstructor() {
- shouldFailWithMessages("""
- class A{}
- class B {
- A a = new A();
- @groovy.transform.CompileStatic
- B(){}
- }
- """, "Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields")
- }
void testPrivateConstructorFromClosure() {
- try {
- assertScript '''
- class Foo {
- String s
- private Foo(String s) { this.s = s }
- static Foo makeFoo(String s) {
- def cl = { new Foo(s) }
- cl()
- }
+ assertScript '''
+ class C {
+ String s
+ private C(String s) {
+ this.s = s
+ }
+ static C make(String s) {
+ def cl = { new C(s) }
+ cl()
}
- assert Foo.makeFoo('pls').s == 'pls'
- '''
- } finally {
- //println astTrees
- }
+ }
+ assert C.make('pls').s == 'pls'
+ '''
}
void testPrivateConstructorFromNestedClass() {
- try {
- assertScript '''
- class Foo {
- String s
- private Foo(String s) { this.s = s }
- static class Bar {
- static Foo makeFoo(String s) { new Foo(s) }
- }
-
+ assertScript '''
+ class Foo {
+ String s
+ private Foo(String s) {
+ this.s = s
+ }
+ static class Bar {
+ static Foo makeFoo(String s) { new Foo(s) }
}
- assert Foo.Bar.makeFoo('pls').s == 'pls'
- '''
- } finally {
- //println astTrees
- }
+
+ }
+ assert Foo.Bar.makeFoo('pls').s == 'pls'
+ '''
}
void testPrivateConstructorFromAIC() {
- try {
- assertScript '''
- class Foo {
- String s
- private Foo(String s) { this.s = s }
- static Foo makeFoo(String s) {
- return new Object() {
- Foo makeFoo(String x) {
- new Foo(x)
- }
- }.makeFoo(s)
- }
+ assertScript '''
+ class Foo {
+ String s
+ private Foo(String s) {
+ this.s = s
}
- assert Foo.makeFoo('pls').s == 'pls'
- '''
- } finally {
- //println astTrees
- }
+ static Foo makeFoo(String s) {
+ new Object() {
+ Foo makeFoo(String x) {
+ new Foo(x)
+ }
+ }.makeFoo(s)
+ }
+ }
+ assert Foo.makeFoo('pls').s == 'pls'
+ '''
}
-
}
-