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()')
+ }
}