You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/07/10 20:51:51 UTC

(groovy) branch master updated (aa30a69065 -> c5dc1a10f4)

This is an automated email from the ASF dual-hosted git repository.

daniellansun pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


    from aa30a69065 Close per-test GroovyClassLoader in contracts BaseTestClass
     new 63865c6ed9 GROOVY-12145: Re-enable AstNodeToScriptAdapterTest disabled by JUnit 5 migration
     new c5dc1a10f4 GROOVY-12144: Fix AstNodeToScriptAdapter decompiler rendering fidelity

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:
 .../console/ui/AstNodeToScriptAdapter.groovy       |  28 ++-
 .../console/ui/AstNodeToScriptAdapterTest.groovy   | 195 ++++++++++++++++++++-
 2 files changed, 213 insertions(+), 10 deletions(-)


(groovy) 01/02: GROOVY-12145: Re-enable AstNodeToScriptAdapterTest disabled by JUnit 5 migration

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

daniellansun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 63865c6ed9d98c226a786cb3ecebc69b6be81db7
Author: Leonard Brünings <le...@gradle.com>
AuthorDate: Fri Jul 10 12:53:15 2026 +0200

    GROOVY-12145: Re-enable AstNodeToScriptAdapterTest disabled by JUnit 5 migration
    
    The "Convert to JUnit5" migration (commit a1897e0811) removed
    `extends GroovyTestCase` from AstNodeToScriptAdapterTest but did not add
    `@Test` to its `void testXxx()` methods. Because the build uses
    useJUnitPlatform(), JUnit Platform discovers tests only by `@Test`, so all
    63 tests silently stopped executing -- a filtered run reports "No tests
    found for given includes". The `import org.junit.jupiter.api.Test` was also
    mis-inserted inside a triple-quoted script string.
    
    Add `@Test` to every test method, move the import to the top-level import
    block, and refresh two assertions whose expected output had drifted while
    the tests were dark: @Immutable's hashCode now initializes the accumulator
    as Integer rather than Object, and @Lazy now emits @Internal before the
    backing field.
    
    GROOVY-12145 surveys the broader migration fallout (~195 dead methods
    across ~32 files); this change fixes the groovy-console instance, which is
    a prerequisite for the GROOVY-12144 decompiler-fidelity tests to run.
    
    Assisted-by: Claude Code (Opus 4.8)
---
 .../console/ui/AstNodeToScriptAdapterTest.groovy   | 69 +++++++++++++++++++++-
 1 file changed, 66 insertions(+), 3 deletions(-)

diff --git a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
index 7807535bfa..92fa9f4f5b 100644
--- a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
+++ b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
@@ -24,6 +24,7 @@ import org.codehaus.groovy.ast.stmt.BlockStatement
 import org.codehaus.groovy.ast.stmt.DoWhileStatement
 import org.codehaus.groovy.control.CompilePhase
 import org.codehaus.groovy.control.CompilerConfiguration
+import org.junit.jupiter.api.Test
 
 import static org.codehaus.groovy.ast.tools.GeneralUtils.args
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX
@@ -43,6 +44,7 @@ final class AstNodeToScriptAdapterTest {
         new AstNodeToScriptAdapter().compileToScript(script, phase.phaseNumber)
     }
 
+    @Test
     void testScript() {
         String result = compileToScript('true')
 
@@ -52,6 +54,7 @@ final class AstNodeToScriptAdapterTest {
         assert result =~ /public java.lang.Object run\(\) \{\s*true\s*\}/
     }
 
+    @Test
     void testStringEscaping() {
         String script = ''' if (_out.toString().endsWith('\\n\\n')) { }  '''
         String result = compileToScript(script)
@@ -59,6 +62,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("_out.toString().endsWith('\\n\\n')")
     }
 
+    @Test
     void testPackage() {
         String script = ''' package foo.bar
                             true '''
@@ -67,6 +71,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('package foo.bar\n')
     }
 
+    @Test
     void testSubScriptOperator() {
         String script = '''
             def file = new File((String)args[0])
@@ -78,6 +83,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('file = new java.io.File(((java.lang.String) args[0]))')
     }
 
+    @Test
     void testMethods() {
         String script = '''
             def method1() {}
@@ -105,6 +111,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('public java.lang.Integer[] method10(java.lang.String[] parm1, java.lang.Object[] parm2)')
     }
 
+    @Test
     void testGenerics() {
         String script = '''
             public class MyList<E> extends AbstractList<E> implements List<E> {
@@ -117,6 +124,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('public class MyList<E> extends java.util.AbstractList<E> implements java.util.List<E> {')
     }
 
+    @Test
     void testGenericBoundsOnClass() {
         String script = '''import java.util.concurrent.Callable
             abstract class MyClass<T extends String & Callable<String>, U extends Integer> extends AbstractList<String> implements Callable<? super Number> { }
@@ -128,6 +136,7 @@ final class AstNodeToScriptAdapterTest {
                 'implements java.util.concurrent.Callable<? super java.lang.Number> {')
     }
 
+    @Test
     void testGenericsInVariables() {
         // you think you know Java generics? Just contemplate a load of this mess:
         String script = '''
@@ -153,6 +162,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('b.addBranch(new Tree<java.lang.Double>(java.lang.Math.PI))')
     }
 
+    @Test
     void testGenericsInMethods() {
         // you think you know Java generics? Just contemplate a load of this mess:
         String script = '''
@@ -180,6 +190,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('void addBranch(Tree<? extends java.lang.Object<V>> branch)')
     }
 
+    @Test
     void testBitwiseNegation() {
         String script = '''
             def foo = { it }
@@ -191,6 +202,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('~(foo.call(~( bar ) ))')
     }
 
+    @Test
     void testRangeExpression() {
         String script = '''
             (1..4).each { println it }
@@ -202,6 +214,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("('1'..'4').collect({ java.lang.Object it ->")
     }
 
+    @Test
     void testUnaryOperators() {
         String script = '''
             def x = 1
@@ -233,6 +246,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('+x')
     }
 
+    @Test
     void testArrayHandling() {
         String script = '''
             String[] x = [] as String[]
@@ -247,6 +261,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('arr = new java.lang.String[0]')
     }
 
+    @Test
     void testArrayHandling_Multidimension() {
         String script = '''
             String[][] x = [] as String[][]
@@ -263,6 +278,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('arr = new java.lang.String[this.xSize(), 3]')
     }
 
+    @Test
     void testForLoop() {
         String script = '''
             for (int x = 0; x < 10;( x )++) {
@@ -276,6 +292,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('continue')
     }
 
+    @Test
     void testForLoopEmptyParameter() {
         String script = '''
             int y = 0
@@ -290,6 +307,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('for (;;) {')
     }
 
+    @Test
     void testPreAndPostFix() {
         String script = '''
             def x = 1, y = 2
@@ -300,6 +318,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('x++ + --y - --(x++)')
     }
 
+    @Test
     void testMultipleAssignments() {
         String script = '''
             def (a, b, c, d) = [1, 2, 3, 4]
@@ -316,6 +335,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('java.lang.Object z = [1, 2]')
     }
 
+    @Test
     void testListsAndMaps() {
         String script = '''
             def (bar, x, bif, qux) = [1, 2, 3, 4]
@@ -330,6 +350,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("['foo': 'bar', 'baz': bif + qux ]")
     }
 
+    @Test
     void testForEachLoop() {
         String script = '''
             for (int x : (1..10)) {
@@ -341,6 +362,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('for (int x : (1..10)) {')
     }
 
+    @Test
     void testSwitchStatements() {
         String script = '''import java.awt.Color
             switch (someMethod()) {
@@ -373,6 +395,7 @@ final class AstNodeToScriptAdapterTest {
         assert !result.contains('default:')
     }
 
+    @Test
     void testLogAnnotation() {
         String script = '''
             @groovy.util.logging.Log
@@ -389,6 +412,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('return log.isLoggable(java.util.logging.Level.FINE) ? log.fine(this.someMethod()) : null')
     }
 
+    @Test
     void testFieldDeclarationWithValue() {
         String script = '''
            class Event {
@@ -404,6 +428,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("bar = '123'")
     }
 
+    @Test
     void testClassAndProperties() {
         String script = '''
             class Event {
@@ -418,6 +443,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('metaClass = /*BytecodeExpression*/')
     }
 
+    @Test
     void testClassAnnotations() {
         String script = '''import org.codehaus.groovy.transform.*
             @SuppressWarnings
@@ -443,6 +469,7 @@ final class AstNodeToScriptAdapterTest {
                 result.contains('includeNames = false')
     }
 
+    @Test
     void testMethodAnnotations() {
         String script = '''import org.codehaus.groovy.transform.*
             class Event {
@@ -469,6 +496,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('@MyAnnotation(') && result.contains('p2 = false') && result.contains('p1 = true')
     }
 
+    @Test
     void testPropertyAnnotations() {
         String script = '''import org.codehaus.groovy.transform.*
             class Event {
@@ -495,6 +523,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('@MyAnnotation(') && result.contains('p2 = false') && result.contains('p1 = true')
     }
 
+    @Test
     void testPackageAnnotations() {
         String script = '''
             @SuppressWarnings
@@ -522,6 +551,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('package foo.bar.baz')
     }
 
+    @Test
     void testImportStatements() {
         String script = '''
             @SuppressWarnings
@@ -567,6 +597,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('import java.util.concurrent.*')
     }
 
+    @Test
     void testParameterAnnotations() {
         String script = '''
             def method(@SuppressWarnings @SuppressWarnings('foo') parameter) {
@@ -577,6 +608,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("public java.lang.Object method(@java.lang.SuppressWarnings @java.lang.SuppressWarnings(value = 'foo') java.lang.Object parameter) {")
     }
 
+    @Test
     void testParenthesisInArgumentList() {
         String script = '''
             public java.lang.String toString() {
@@ -593,6 +625,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("_result.append(')')")
     }
 
+    @Test
     void testStaticMethods() {
         String script = "Math.min(5, Math.min('str', 2))"
         String result = compileToScript(script)
@@ -600,6 +633,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("java.lang.Math.min(5, java.lang.Math.min('str', 2))")
     }
 
+    @Test
     void testAtImmutableClass() {
         String script = '@groovy.transform.Immutable class Event { }'
         String result = compileToScript(script, CompilePhase.CANONICALIZATION)
@@ -613,6 +647,7 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains('return $to$string')
     }
 
+    @Test
     void testToStringClassAndStaticMethodCallExpression() {
         String script = '@groovy.transform.ToString class Event { Date when }'
         String result = compileToScript(script, CompilePhase.CANONICALIZATION)
@@ -621,10 +656,10 @@ final class AstNodeToScriptAdapterTest {
         assert result.contains("_result.append(org.codehaus.groovy.runtime.FormatHelper.toString(this.getWhen())")
     }
 
+    @Test
     void testAtImmutableClassWithProperties() {
         String script = '''
             import groovy.transform.Immutable
-import org.junit.jupiter.api.Test
             @Immutable class Event {
                 String title
                 Date when
@@ -639,7 +674,7 @@ import org.junit.jupiter.api.Test
 
         // assert hashCode
         assert result.contains('public int hashCode()')
-        assert result.contains('java.lang.Object _result = org.codehaus.groovy.util.HashCodeHelper.initHash()')
+        assert result.contains('java.lang.Integer _result = org.codehaus.groovy.util.HashCodeHelper.initHash()')
         assert result.contains("_result = org.codehaus.groovy.util.HashCodeHelper.updateHash(_result, this.getTitle())")
         assert result.contains("_result = org.codehaus.groovy.util.HashCodeHelper.updateHash(_result, this.getWhen())")
         assert result.contains("_result = org.codehaus.groovy.util.HashCodeHelper.updateHash(_result, this.getColor())")
@@ -652,6 +687,7 @@ import org.junit.jupiter.api.Test
         }
     }
 
+    @Test
     void testAnonymousInnerClass() {
         String script = '''
             new Object() {
@@ -665,6 +701,7 @@ import org.junit.jupiter.api.Test
         assert result =~ /public java\.lang\.String toString\(\)/
     }
 
+    @Test
     void testLazyAnnotation() {
         String script = '''
             class Event {
@@ -673,12 +710,13 @@ import org.junit.jupiter.api.Test
         '''
         String result = compileToScript(script, CompilePhase.CANONICALIZATION)
 
-        assert result =~ /Lazy\s*private java\.util\.ArrayList .*speakers /
+        assert result =~ /Lazy\s*(?:@groovy\.transform\.Internal\s*)?private java\.util\.ArrayList .*speakers /
         assert result.contains('public java.util.ArrayList getSpeakers() {')
         assert result.contains('if ($speakers != null') || result.contains('if (null != $speakers')
         assert result.contains('$speakers = new java.util.ArrayList()')
     }
 
+    @Test
     void testDelegateClass() {
         String script = '''
             class Event {
@@ -693,6 +731,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('public java.util.Date getWhen() {')
     }
 
+    @Test
     void testAnnotationWithValueClass() {
         String script = '''
             class Event {
@@ -707,6 +746,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('private java.util.Date when ')
     }
 
+    @Test
     void testTryCatch() {
         String script = '''\
             try {
@@ -738,6 +778,7 @@ import org.junit.jupiter.api.Test
         assert !result.contains('finally {')
     }
 
+    @Test
     void testSuperAndThisCalls() {
         String script = '''
             class MyClass {
@@ -756,6 +797,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('public MyClass(java.lang.Object foo) {')
     }
 
+    @Test
     void testTryCatchFinally() {
         String script = '''\
             try {
@@ -769,6 +811,7 @@ import org.junit.jupiter.api.Test
         assert result =~ /try\s*\{\s*false\s*\}\s*finally\s*\{\s*true\s*\}/
     }
 
+    @Test
     void testSynchronizedBlock() {
         String script = '''\
             synchronized(this) {
@@ -787,6 +830,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('}')
     }
 
+    @Test
     void testTernaryOperaters() {
         String script = """true || false ? 'y' : 'n'
                             foo ?: 'y'"""
@@ -795,6 +839,7 @@ import org.junit.jupiter.api.Test
         assert result.contains("foo ? foo : 'y'")
     }
 
+    @Test
     void testWhileLoop() {
         String script = '''while ("foo") println 5
                             while ("foo") {println 5; println 5; break; }'''
@@ -803,6 +848,7 @@ import org.junit.jupiter.api.Test
         assert result.contains("while ('foo') {")
     }
 
+    @Test
     void testDoWhileLoop() {
         def doWhile = new DoWhileStatement(
                 new BooleanExpression(constX(true)),
@@ -819,6 +865,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('} while (true)')
     }
 
+    @Test
     void testAssertStatement() {
         String script = '''
             assert 1 == 1; assert true == foo() : 'message'
@@ -829,6 +876,7 @@ import org.junit.jupiter.api.Test
         assert result.contains("assert true == this.foo() : 'message'")
     }
 
+    @Test
     void testMethodPointer() {
         String script = '''
             class Event {
@@ -855,6 +903,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('assert 1 == m3.call(6) : null')
     }
 
+    @Test
     void testAssertRegexExpression() {
         String script = '''
             assert "abc.def" =~ /[a-z]b[a-z]\\.def/
@@ -866,6 +915,7 @@ import org.junit.jupiter.api.Test
         assert result.contains("assert 'cheesecheese' =~ 'cheese' : null")
     }
 
+    @Test
     void testArrayExpression() {
         String script = '''
             def x = [4, 5, 6] as String[]
@@ -877,6 +927,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('[1, 2, 3] << new java.lang.Integer[x.length]')
     }
 
+    @Test
     void testSpreadDot() {
         String script = '''
             def x = [ ['a':11, 'b':12], ['a':21, 'b':22] ]
@@ -890,6 +941,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('assert x*.a == [11, 21] : null')
     }
 
+    @Test
     void testSpreadNotationNullHandling() {
         String script = '''
             def x = [ ['a':11, 'b':12], ['a':21, 'b':22], null ]
@@ -905,6 +957,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('}) : null')
     }
 
+    @Test
     void testSpreadNotationAdvanced() {
         String script = '''
             class MyClass{ def getA(){ 'abc' } }
@@ -917,6 +970,7 @@ import org.junit.jupiter.api.Test
         assert result.contains("assert x*.a == [21, null, 'abc'] : null")
     }
 
+    @Test
     void testSpreadNotationForMethodsOnLists() {
         String script = '''
             class MyClass{ def getA(){ 'abc' } }
@@ -933,6 +987,7 @@ import org.junit.jupiter.api.Test
         assert result.contains("assert [c1, c2]*.getA() == ['abc', 'abc'] : null")
     }
 
+    @Test
     void testVisitSafeMethodCall() {
         String script = 'someMethod()?.somethingElse()*.sum()'
         String result = compileToScript(script)
@@ -940,6 +995,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('someMethod()?.somethingElse()*.sum()')
     }
 
+    @Test
     void testSpreadNotationInMapDefinition() {
         String script = '''
             assert ['z':900, *:['a':100, 'b':200], 'a':300] == ['a':300, 'b':200, 'z':900]
@@ -951,6 +1007,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('assert [*: [3: 3, *: [5: 5]], 7: 7] == [3: 3, 5: 5, 7: 7] : null')
     }
 
+    @Test
     void testSpreadNotationInClosure() {
         String script = '''
             def f(){ [ 1:'u', 2:'v', 3:'w' ] }
@@ -969,6 +1026,7 @@ import org.junit.jupiter.api.Test
     }
 
     // GROOVY-4636
+    @Test
     void testObjectInitializers() {
         String script = '''
             class A {
@@ -985,6 +1043,7 @@ import org.junit.jupiter.api.Test
         assert result =~ /(?s)public A\(\) \{.*?v = 2\s*v = 1\s*\}/
     }
 
+    @Test
     void testStatementLabels() {
         String script = '''
             label1:
@@ -1039,6 +1098,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('label6:\nsynchronized')
     }
 
+    @Test
     void testStatementLabelsForDoWhileLoop() {
         def doWhile = new DoWhileStatement(
                 new BooleanExpression(constX(true)),
@@ -1056,6 +1116,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('} while (true)')
     }
 
+    @Test
     void testNestedObjectInitializers() {
         String script = '''
             class A {
@@ -1077,6 +1138,7 @@ import org.junit.jupiter.api.Test
         assert result =~ /(?s)oi:.*?\{.*?v \+= 2.*?\}/
     }
 
+    @Test
     void testVisitIfElse() {
         String script = '''
             String a = 'foo'
@@ -1087,6 +1149,7 @@ import org.junit.jupiter.api.Test
         assert result.contains('if (a instanceof java.lang.String) {')
     }
 
+    @Test
     void testVisitCastExpression() {
         String script = '''
             String a = 'foo'


(groovy) 02/02: GROOVY-12144: Fix AstNodeToScriptAdapter decompiler rendering fidelity

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

daniellansun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit c5dc1a10f4c0c05a14fe43c2f24d1777c15a479d
Author: Leonard Brünings <le...@gradle.com>
AuthorDate: Fri Jul 10 12:53:49 2026 +0200

    GROOVY-12144: Fix AstNodeToScriptAdapter decompiler rendering fidelity
    
    AstNodeToScriptVisitor rendered several constructs incorrectly, producing
    source that did not round-trip -- it re-parsed to a different type or with
    changed semantics. Fix eight independent defects, each localized to a
    single visitor method:
    
      1. nested generic type arguments dropped (Map<String, List<Integer>>
         rendered Map<String, List>) -- visitGenerics recurses into concrete args
      2. range bound exclusions dropped (1..<5 / 1<..5 / 1<..<5 all rendered
         1..5) -- visitRangeExpression honors isExclusiveLeft/isExclusiveRight
      3. Elvis rendered as a duplicated ternary (c ?: d rendered c ? c : d,
         evaluating c twice) -- visitShortTernaryExpression emits ?:
      4. attribute access lost the at-sign (obj.@f rendered obj.f) --
         visitPropertyExpression emits .@ for an AttributeExpression
      5. explicit zero-arg closures lost the arrow ({ -> ... } rendered
         { ... }, gaining an implicit it) -- visitClosureExpression treats a
         null parameter list distinctly from Parameter.EMPTY_ARRAY
      6. safe index access lost safe navigation (list?[0] rendered list[0]) --
         visitBinaryExpression honors isSafe()
      7. numeric literal type suffixes dropped (42L/2.5f/3.5d/10G rendered as
         42/2.5/3.5/10) -- visitConstantExpression re-appends L/F/D/G
      8. explicit method-call type arguments dropped
         (Collections.<String>emptyList()) -- visitMethodCallExpression renders them
    
    Add one regression test per defect. Fix 4 makes AttributeExpression render
    faithfully everywhere: @Log builds Level.FINE as an AttributeExpression, so
    it now renders Level.@FINE (testLogAnnotation updated). Two existing tests
    are updated to the corrected output (testTernaryOperaters; testGenericsInMethods,
    whose comments had asked whether the nested type args should be dropped).
    
    These fidelity issues were found and fixed downstream in Spock's transpiler
    (derived from this class); this upstreams them.
    
    Assisted-by: Claude Code (Opus 4.8)
---
 .../console/ui/AstNodeToScriptAdapter.groovy       |  28 ++++-
 .../console/ui/AstNodeToScriptAdapterTest.groovy   | 126 ++++++++++++++++++++-
 2 files changed, 147 insertions(+), 7 deletions(-)

diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
index 9f13c48a29..c86ccfb312 100644
--- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
+++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/AstNodeToScriptAdapter.groovy
@@ -446,6 +446,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
                 }
                 first = false
                 print it.name
+                if (!it.placeholder && !it.wildcard) {
+                    visitGenerics it.type?.genericsTypes
+                }
                 if (it.upperBounds) {
                     print ' extends '
                     boolean innerFirst = true
@@ -754,6 +757,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
             print '?'
         }
         print '.'
+        visitGenerics expression.genericsTypes
         Expression method = expression.method
         if (method instanceof ConstantExpression) {
             visitConstantExpression(method, true)
@@ -799,7 +803,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         boolean isAssign = expression?.operation?.type == Types.ASSIGN
         boolean isAccess = expression?.operation?.type == Types.LEFT_SQUARE_BRACKET
         if (!isAssign || expression?.rightExpression !instanceof EmptyExpression) {
-            print isAccess ? '[' : " ${expression?.operation?.text} "
+            print isAccess ? (expression.safe ? '?[' : '[') : " ${expression?.operation?.text} "
             expression?.rightExpression?.visit this
             if (isAccess) {
                 print ']'
@@ -840,6 +844,8 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         if (expression?.parameters) {
             visitParameters expression?.parameters
             print ' ->'
+        } else if (expression?.parameters == null) {
+            print ' ->'
         }
         printLineBreak()
         indented {
@@ -876,7 +882,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     void visitRangeExpression(RangeExpression expression) {
         print '('
         expression?.from?.visit this
+        if (expression.exclusiveLeft) print '<'
         print '..'
+        if (expression.exclusiveRight) print '<'
         expression?.to?.visit this
         print ')'
     }
@@ -890,7 +898,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         } else if (expression?.isSafe()) {
             print '?'
         }
-        print '.'
+        print expression instanceof AttributeExpression ? '.@' : '.'
         if (expression?.property instanceof ConstantExpression) {
             visitConstantExpression((ConstantExpression) expression?.property, true)
         } else {
@@ -919,6 +927,18 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
             print "'$escaped'"
         } else {
             print expression.value
+            // re-append the literal's type suffix so re-parsing yields the same type
+            // (Integer and BigDecimal are the literal defaults and need no suffix)
+            def value = expression.value
+            if (value instanceof Long) {
+                print 'L'
+            } else if (value instanceof Float) {
+                print 'F'
+            } else if (value instanceof Double) {
+                print 'D'
+            } else if (value instanceof BigInteger) {
+                print 'G'
+            }
         }
     }
 
@@ -1145,7 +1165,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     /** Renders an Elvis expression. */
     @Override
     void visitShortTernaryExpression(ElvisOperatorExpression expression) {
-        visitTernaryExpression expression
+        expression?.booleanExpression?.visit this
+        print ' ?: '
+        expression?.falseExpression?.visit this
     }
 
     /** Renders a boolean expression. */
diff --git a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
index 92fa9f4f5b..0de3b0a0bd 100644
--- a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
+++ b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/AstNodeToScriptAdapterTest.groovy
@@ -181,8 +181,8 @@ final class AstNodeToScriptAdapterTest {
 
         assert result.contains('public class Tree<V> extends java.lang.Object implements groovy.lang.GroovyObject')
         assert result.contains('private java.lang.Object<V> value') // todo: is Object<V> correct? How do you know?
-        assert result.contains('private java.util.List<Tree> branches') // should the <? extends V> be dropped?
-        assert result.contains('branches = new java.util.ArrayList<Tree>()') // should the <? extends V> be dropped?
+        assert result.contains('private java.util.List<Tree<? extends java.lang.Object<V>>> branches')
+        assert result.contains('branches = new java.util.ArrayList<Tree<? extends java.lang.Object<V>>>()')
         assert result.contains('public Tree(java.lang.Object<V> value)') // again, is this correct?
         assert result.contains(' public java.lang.Object<V> getValue()') // is this correct?
         assert result.contains('public void setValue(java.lang.Object<V> value)')
@@ -409,7 +409,7 @@ final class AstNodeToScriptAdapterTest {
 
         assert result.contains('private static final transient java.util.logging.Logger log')
         assert result.contains("log = java.util.logging.Logger.getLogger('Event')")
-        assert result.contains('return log.isLoggable(java.util.logging.Level.FINE) ? log.fine(this.someMethod()) : null')
+        assert result.contains('return log.isLoggable(java.util.logging.Level.@FINE) ? log.fine(this.someMethod()) : null')
     }
 
     @Test
@@ -836,7 +836,7 @@ final class AstNodeToScriptAdapterTest {
                             foo ?: 'y'"""
         String result = compileToScript(script)
         assert result.contains("true || false ? 'y' : 'n'")
-        assert result.contains("foo ? foo : 'y'")
+        assert result.contains("foo ?: 'y'")
     }
 
     @Test
@@ -1159,4 +1159,122 @@ final class AstNodeToScriptAdapterTest {
 
         assert result.contains('(a as java.lang.String)')
     }
+
+    // GROOVY-12144
+    @Test
+    void testNestedGenerics() {
+        String script = '''
+            Map<String, List<Integer>> nested() { null }
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('java.util.Map<String, List<Integer>> nested()')
+    }
+
+    // GROOVY-12144
+    @Test
+    void testRangeExpressionExclusiveBounds() {
+        String script = '''
+            def a = 1..5
+            def b = 1..<5
+            def c = 1<..5
+            def d = 1<..<5
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('(1..5)')
+        assert result.contains('(1..<5)')
+        assert result.contains('(1<..5)')
+        assert result.contains('(1<..<5)')
+    }
+
+    // GROOVY-12144
+    @Test
+    void testElvisOperator() {
+        String script = '''
+            def a = c ?: d
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('c ?: d')
+        assert !result.contains('c ? c : d')
+    }
+
+    // GROOVY-12144
+    @Test
+    void testAttributeExpression() {
+        String script = '''
+            def a = other.@order
+            def b = foos*.@order
+            def c = other?.@order
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('other.@order')
+        assert result.contains('foos*.@order')
+        assert result.contains('other?.@order')
+    }
+
+    // GROOVY-12144
+    @Test
+    void testClosureParameterArrow() {
+        String script = '''
+            def implicitIt = { it * 2 }
+            def noArgs = { -> 'x' }
+        '''
+        String result = compileToScript(script)
+
+        // explicit zero-argument closure keeps its arrow (otherwise it would gain an implicit 'it')
+        assert result.contains('{ ->')
+        // an implicit-'it' closure has no arrow
+        assert result =~ /\{\s*it \* 2/
+    }
+
+    // GROOVY-12144
+    @Test
+    void testSafeIndexAccess() {
+        String script = '''
+            def a = list?[0]
+            def b = map?['key']
+            list?[1] = 42
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('list?[0]')
+        assert result.contains("map?['key']")
+        assert result.contains('list?[1] = 42')
+    }
+
+    // GROOVY-12144
+    @Test
+    void testNumericLiteralSuffixes() {
+        String script = '''
+            def a = 42L
+            def b = 2.5f
+            def c = 3.5d
+            def d = 10G
+            def e = 42
+            def f = 2.5
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('= 42L')
+        assert result.contains('= 2.5F')
+        assert result.contains('= 3.5D')
+        assert result.contains('= 10G')
+        // Integer and BigDecimal are the literal defaults and carry no suffix
+        assert result.contains('= 42\n')
+        assert result.contains('= 2.5\n')
+    }
+
+    // GROOVY-12144
+    @Test
+    void testExplicitMethodTypeArguments() {
+        String script = '''
+            def a = Collections.<String>emptyList()
+        '''
+        String result = compileToScript(script)
+
+        assert result.contains('java.util.Collections.<String>emptyList()')
+    }
 }