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

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


The following commit(s) were added to refs/heads/GROOVY_5_0_X by this push:
     new 4992767d83 GROOVY-12144: Fix AstNodeToScriptAdapter decompiler 
rendering fidelity
4992767d83 is described below

commit 4992767d8378d8f51a5f4065794303d62103a1bc
Author: Leonard Brünings <[email protected]>
AuthorDate: Fri Jul 10 13:25:54 2026 +0200

    GROOVY-12144: Fix AstNodeToScriptAdapter decompiler rendering fidelity
    
    GROOVY_5_0_X backport of master commit 4b27025962.
    
    Assisted-by: Claude Code (Opus 4.8)
---
 .../console/ui/AstNodeToScriptAdapter.groovy       | 28 +++++++-
 .../console/ui/AstNodeToScriptAdapterTest.groovy   | 79 ++++++++++++++++++++--
 2 files changed, 100 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 fc6f2ab327..6a5efed2a3 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
@@ -392,6 +392,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
@@ -685,6 +688,7 @@ class AstNodeToScriptVisitor implements 
CompilationUnit.IPrimaryClassNodeOperati
             print '?'
         }
         print '.'
+        visitGenerics expression.genericsTypes
         Expression method = expression.method
         if (method instanceof ConstantExpression) {
             visitConstantExpression(method, true)
@@ -727,7 +731,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 ']'
@@ -765,6 +769,8 @@ class AstNodeToScriptVisitor implements 
CompilationUnit.IPrimaryClassNodeOperati
         if (expression?.parameters) {
             visitParameters expression?.parameters
             print ' ->'
+        } else if (expression?.parameters == null) {
+            print ' ->'
         }
         printLineBreak()
         indented {
@@ -798,7 +804,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 ')'
     }
@@ -811,7 +819,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 {
@@ -837,6 +845,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'
+            }
         }
     }
 
@@ -1044,7 +1064,9 @@ class AstNodeToScriptVisitor implements 
CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitShortTernaryExpression(ElvisOperatorExpression expression) {
-        visitTernaryExpression expression
+        expression?.booleanExpression?.visit this
+        print ' ?: '
+        expression?.falseExpression?.visit this
     }
 
     @Override
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 a0232e9393..4768930b7c 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
@@ -172,8 +172,8 @@ final class AstNodeToScriptAdapterTest extends 
GroovyTestCase {
 
         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)')
@@ -387,7 +387,7 @@ final class AstNodeToScriptAdapterTest extends 
GroovyTestCase {
 
         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')
     }
 
     void testFieldDeclarationWithValue() {
@@ -792,7 +792,7 @@ final class AstNodeToScriptAdapterTest extends 
GroovyTestCase {
                             foo ?: 'y'"""
         String result = compileToScript(script)
         assert result.contains("true || false ? 'y' : 'n'")
-        assert result.contains("foo ? foo : 'y'")
+        assert result.contains("foo ?: 'y'")
     }
 
     void testWhileLoop() {
@@ -1096,4 +1096,75 @@ final class AstNodeToScriptAdapterTest extends 
GroovyTestCase {
 
         assert result.contains('(a as java.lang.String)')
     }
+
+    // GROOVY-12144
+    void testNestedGenerics() {
+        String result = compileToScript('Map<String, List<Integer>> nested() { 
null }')
+        assert result.contains('java.util.Map<String, List<Integer>> nested()')
+    }
+
+    // GROOVY-12144
+    void testRangeExpressionExclusiveBounds() {
+        String result = compileToScript('''def a = 1..5
+            def b = 1..<5
+            def c = 1<..5
+            def d = 1<..<5''')
+        assert result.contains('(1..5)')
+        assert result.contains('(1..<5)')
+        assert result.contains('(1<..5)')
+        assert result.contains('(1<..<5)')
+    }
+
+    // GROOVY-12144
+    void testElvisOperator() {
+        String result = compileToScript('def a = c ?: d')
+        assert result.contains('c ?: d')
+        assert !result.contains('c ? c : d')
+    }
+
+    // GROOVY-12144
+    void testAttributeExpression() {
+        String result = compileToScript('''def a = other.@order
+            def b = foos*.@order
+            def c = other?.@order''')
+        assert result.contains('other.@order')
+        assert result.contains('foos*.@order')
+        assert result.contains('other?.@order')
+    }
+
+    // GROOVY-12144
+    void testClosureParameterArrow() {
+        String result = compileToScript('''def implicitIt = { it * 2 }
+            def noArgs = { -> 'x' }''')
+        assert result.contains('{ ->')
+        assert result =~ /\{\s*it \* 2/
+    }
+
+    // GROOVY-12144
+    void testSafeIndexAccess() {
+        String result = compileToScript('''def a = list?[0]
+            def b = map?['key']
+            list?[1] = 42''')
+        assert result.contains('list?[0]')
+        assert result.contains("map?['key']")
+        assert result.contains('list?[1] = 42')
+    }
+
+    // GROOVY-12144
+    void testNumericLiteralSuffixes() {
+        String result = compileToScript('''def a = 42L
+            def b = 2.5f
+            def c = 3.5d
+            def d = 10G''')
+        assert result.contains('= 42L')
+        assert result.contains('= 2.5F')
+        assert result.contains('= 3.5D')
+        assert result.contains('= 10G')
+    }
+
+    // GROOVY-12144
+    void testExplicitMethodTypeArguments() {
+        String result = compileToScript('def a = 
Collections.<String>emptyList()')
+        assert result.contains('java.util.Collections.<String>emptyList()')
+    }
 }

Reply via email to