You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Eric Milles (Jira)" <ji...@apache.org> on 2023/07/11 18:40:00 UTC
[jira] [Comment Edited] (GROOVY-11118) Partial JEP 445 compatibility
[ https://issues.apache.org/jira/browse/GROOVY-11118?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17742132#comment-17742132 ]
Eric Milles edited comment on GROOVY-11118 at 7/11/23 6:39 PM:
---------------------------------------------------------------
Seems reasonable. I'm not sure if this is relevant or not. I once created an AST transform that would create a psvm that delegates to an instance method, like this:
{code:groovy}
class Foo {
@Main bar() {
println 'baz'
}
}
{code}
Does this seem useful in light of the JEP 445 support? If you or anyone wants to have a look, here it is:
{code:groovy}
package xforms
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod
import java.lang.annotation.*
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformationClass
@GroovyASTTransformationClass('xforms.MainMethodTransform')
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
@interface Main {
}
//------------------------------------------------------------------------------
@AutoFinal @GroovyASTTransformation
class MainMethodTransform implements ASTTransformation {
@Override
void visit(ASTNode[] nodes, SourceUnit source) {
if (!nodes || !nodes[0] || !nodes[1]) return
if (!(nodes[1] instanceof MethodNode)) return
if (!(nodes[0] instanceof AnnotationNode)) return
if (nodes[0].classNode?.name != Main.name) return
try {
MethodNode annotatedMethod = nodes[1]
addGeneratedMethod(annotatedMethod.declaringClass, newMainMethod(annotatedMethod))
} catch (e) {
source.addException(e)
}
}
private static MethodNode newMainMethod(MethodNode target) {
def packageName = target.declaringClass.packageName
def className = target.declaringClass.name
def methodName = target.name
def ast = new AstBuilder().buildFromString(CompilePhase.CANONICALIZATION, false, """
${packageName ? 'package ' + packageName : ''}
class ${target.declaringClass.nameWithoutPackage} {
public static void main(String[] args) {
new ${className}().${methodName}()
}
}
""")
// ast[0] is BlockStatement
return (ast[1] as ClassNode).methods.find { it.name == 'main' }
}
}
{code}
And unit tests:
{code:groovy}
package xforms
import static org.codehaus.groovy.control.CompilePhase.CANONICALIZATION
import org.codehaus.groovy.tools.ast.TransformTestHelper
final class MainXformTests {
@Test
void test() {
def parser = new TransformTestHelper(new MainMethodTransform(), CANONICALIZATION)
def clazz = parser.parse """import ${Main.name}
class ShouldHaveMain {
@Main foo() {
println 'bar'
}
}
"""
clazz.newInstance().main()
}
@Test
void 'direct test'() {
ShouldHaveMain.main()
}
}
class ShouldHaveMain {
@Main foo() {
println 'bar'
}
}
{code}
was (Author: emilles):
It seems reasonable. I'm not sure if this is relevant or not. I once created an AST transform that would create a psvm that delegates to an instance method, like this:
{code:groovy}
class Foo {
@Main bar() {
println 'baz'
}
}
{code}
Does this seem useful in light of the JEP 445 support? If you or anyone wants to have a look, here it is:
{code:groovy}
package xforms
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod
import java.lang.annotation.*
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformationClass
@GroovyASTTransformationClass('xforms.MainMethodTransform')
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
@interface Main {
}
//------------------------------------------------------------------------------
@AutoFinal @GroovyASTTransformation
class MainMethodTransform implements ASTTransformation {
@Override
void visit(ASTNode[] nodes, SourceUnit source) {
if (!nodes || !nodes[0] || !nodes[1]) return
if (!(nodes[1] instanceof MethodNode)) return
if (!(nodes[0] instanceof AnnotationNode)) return
if (nodes[0].classNode?.name != Main.name) return
try {
MethodNode annotatedMethod = nodes[1]
addGeneratedMethod(annotatedMethod.declaringClass, newMainMethod(annotatedMethod))
} catch (e) {
source.addException(e)
}
}
private static MethodNode newMainMethod(MethodNode target) {
def packageName = target.declaringClass.packageName
def className = target.declaringClass.name
def methodName = target.name
def ast = new AstBuilder().buildFromString(CompilePhase.CANONICALIZATION, false, """
${packageName ? 'package ' + packageName : ''}
class ${target.declaringClass.nameWithoutPackage} {
public static void main(String[] args) {
new ${className}().${methodName}()
}
}
""")
// ast[0] is BlockStatement
return (ast[1] as ClassNode).methods.find { it.name == 'main' }
}
}
{code}
And unit tests:
{code:groovy}
package xforms
import static org.codehaus.groovy.control.CompilePhase.CANONICALIZATION
import org.codehaus.groovy.tools.ast.TransformTestHelper
final class MainXformTests {
@Test
void test() {
def parser = new TransformTestHelper(new MainMethodTransform(), CANONICALIZATION)
def clazz = parser.parse """import ${Main.name}
class ShouldHaveMain {
@Main foo() {
println 'bar'
}
}
"""
clazz.newInstance().main()
}
@Test
void 'direct test'() {
ShouldHaveMain.main()
}
}
class ShouldHaveMain {
@Main foo() {
println 'bar'
}
}
{code}
> Partial JEP 445 compatibility
> -----------------------------
>
> Key: GROOVY-11118
> URL: https://issues.apache.org/jira/browse/GROOVY-11118
> Project: Groovy
> Issue Type: New Feature
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
> Labels: GEP, breaking
> Fix For: 5.x
>
>
> [JEP 445: Unnamed Classes and Instance Main Methods|https://openjdk.org/jeps/445] provides features that improve using Java for scripting purposes. While these features offer a subset of the features offered by Groovy for scripting, it would be nice to offer some interworking for cut-n-paste compatibility and leveraging the updated launch protocol.
> (i) This description will contain the most update-to-date proposal for the design but may undergo change during discussions. If needed, a separate GEP section in the wiki will capture the final design.
> h2. Scripting semantics
> ||Source file characteristics||Semantics determined||
> |Contains class declaration|Compiled as a class|
> |Contains uncontained non-declaration statements
> or a static main method|Compiled as a Groovy script|
> |Contains an instance main method, and
> possibly field and other method declarations|Compiled as a JEP 445 compatible class|
> Groovy scripts are wrapped into an encompassing class that extends {{{}groovy.lang.Script{}}}.
> Such classes have a {{public static void main}} added which (summarising) creates a class instance and calls its {{run}} method.
> Uncontained statements within the script can be thought of as belonging within the {{run}} method.
> Declaration statements within the script are treated as local variable definitions (also within the {{run}} method).
> Field definitions can be obtained by annotating declaration statements with the {{@Field}} annotation.
> JEP 445 scripts do not extend {{Script}} and do not have any methods added.
> Declaration statements within the script are treated as field definitions.
> h2. Script runner
> Groovy's runner mechanism will be extended to follow a similar protocol to Java's revised launch protocol. This should be available for JDK11+ and a JEP-445 compatible class could be invoked using JDK21+ with preview enabled (with Groovy on the classpath unless a POJO class is created).
> h2. Breaking changes
> Previously, if a script had a single instance {{main}} method not taking a {{String[]}} parameter and no loose statements, that was previously accepted as a normal Groovy script (and nothing would execute). Now if the method has an Object parameter, that is treated as a JEP-445 script and its contents executed.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)