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 2023/01/10 23:05:15 UTC

[groovy] branch master updated: visit annotation(s) of multiple-variable declaration

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 29de7e506e visit annotation(s) of multiple-variable declaration
29de7e506e is described below

commit 29de7e506ec44c5a899e2c98aa536eb3ae5360ff
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Jan 10 17:04:30 2023 -0600

    visit annotation(s) of multiple-variable declaration
---
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 154 ++++-------
 .../codehaus/groovy/classgen/ExtendedVerifier.java |  12 +-
 .../groovy/parser/antlr4/util/AstDumper.groovy     | 291 +++++++++++----------
 .../console/ui/AstNodeToScriptAdapter.groovy       | 191 ++++++--------
 .../console/ui/AstNodeToScriptAdapterTest.groovy   |   6 +-
 .../console/ui/ScriptToTreeNodeAdapterTest.groovy  |   2 +-
 6 files changed, 307 insertions(+), 349 deletions(-)

diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index d35969cc80..33e66eb473 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -35,7 +35,6 @@ import org.antlr.v4.runtime.misc.Interval;
 import org.antlr.v4.runtime.misc.ParseCancellationException;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.TerminalNode;
-import org.apache.groovy.parser.antlr4.GroovyParser.*;
 import org.apache.groovy.parser.antlr4.internal.DescriptiveErrorStrategy;
 import org.apache.groovy.parser.antlr4.internal.atnmanager.AtnManager;
 import org.apache.groovy.parser.antlr4.util.StringUtils;
@@ -132,6 +131,7 @@ import org.objectweb.asm.Opcodes;
 
 import java.io.BufferedReader;
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -148,34 +148,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static groovy.lang.Tuple.tuple;
-import static org.apache.groovy.parser.antlr4.GroovyParser.ADD;
-import static org.apache.groovy.parser.antlr4.GroovyParser.ARROW;
-import static org.apache.groovy.parser.antlr4.GroovyParser.AS;
-import static org.apache.groovy.parser.antlr4.GroovyParser.CASE;
-import static org.apache.groovy.parser.antlr4.GroovyParser.DEC;
-import static org.apache.groovy.parser.antlr4.GroovyParser.DEF;
-import static org.apache.groovy.parser.antlr4.GroovyParser.DEFAULT;
-import static org.apache.groovy.parser.antlr4.GroovyParser.FINAL;
-import static org.apache.groovy.parser.antlr4.GroovyParser.GE;
-import static org.apache.groovy.parser.antlr4.GroovyParser.GT;
-import static org.apache.groovy.parser.antlr4.GroovyParser.IN;
-import static org.apache.groovy.parser.antlr4.GroovyParser.INC;
-import static org.apache.groovy.parser.antlr4.GroovyParser.INSTANCEOF;
-import static org.apache.groovy.parser.antlr4.GroovyParser.LE;
-import static org.apache.groovy.parser.antlr4.GroovyParser.LT;
-import static org.apache.groovy.parser.antlr4.GroovyParser.NON_SEALED;
-import static org.apache.groovy.parser.antlr4.GroovyParser.NOT_IN;
-import static org.apache.groovy.parser.antlr4.GroovyParser.NOT_INSTANCEOF;
-import static org.apache.groovy.parser.antlr4.GroovyParser.PRIVATE;
-import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_EXCLUSIVE_FULL;
-import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_EXCLUSIVE_LEFT;
-import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_EXCLUSIVE_RIGHT;
-import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_INCLUSIVE;
-import static org.apache.groovy.parser.antlr4.GroovyParser.SAFE_INDEX;
-import static org.apache.groovy.parser.antlr4.GroovyParser.SEALED;
-import static org.apache.groovy.parser.antlr4.GroovyParser.STATIC;
-import static org.apache.groovy.parser.antlr4.GroovyParser.SUB;
-import static org.apache.groovy.parser.antlr4.GroovyParser.VAR;
+import static org.apache.groovy.parser.antlr4.GroovyParser.*;
 import static org.apache.groovy.parser.antlr4.util.PositionConfigureUtils.configureAST;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
@@ -367,7 +340,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         if (hasStatic) {
             if (hasStar) { // e.g. import static java.lang.Math.*
                 String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
-                ClassNode type = ClassHelper.make(qualifiedName);
+                ClassNode type = makeClassNode(qualifiedName);
                 configureAST(type, ctx);
 
                 moduleNode.addStaticStarImport(type.getText(), type, annotationNodeList);
@@ -378,7 +351,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
                 int identifierListSize = identifierList.size();
                 String name = identifierList.get(identifierListSize - 1).getText();
                 ClassNode classNode =
-                        ClassHelper.make(
+                        makeClassNode(
                                 identifierList.stream()
                                         .limit(identifierListSize - 1)
                                         .map(ParseTree::getText)
@@ -402,7 +375,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             } else { // e.g. import java.util.Map
                 String qualifiedName = this.visitQualifiedName(ctx.qualifiedName());
                 String name = last(ctx.qualifiedName().qualifiedNameElement()).getText();
-                ClassNode classNode = ClassHelper.make(qualifiedName);
+                ClassNode classNode = makeClassNode(qualifiedName);
                 String alias = hasAlias
                         ? ctx.alias.getText()
                         : name;
@@ -417,6 +390,18 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         return configureAST(importNode, ctx);
     }
 
+    private static AnnotationNode makeAnnotationNode(final Class<? extends Annotation> type) {
+        AnnotationNode node = new AnnotationNode(ClassHelper.make(type));
+        // TODO: source offsets
+        return node;
+    }
+
+    private static ClassNode makeClassNode(final String name) {
+        ClassNode node = ClassHelper.make(name);
+        // TODO: shared instances
+        return node;
+    }
+
     // statement { -------------------------------------------------------------
 
     @Override
@@ -1020,8 +1005,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     }
     private int switchExpressionVariableSeq;
 
-    @Override
-    @SuppressWarnings("unchecked")
+    @Override @SuppressWarnings("unchecked")
     public Tuple3<List<Statement>, Boolean, Boolean> visitSwitchBlockStatementExpressionGroup(SwitchBlockStatementExpressionGroupContext ctx) {
         int labelCnt = ctx.switchExpressionLabel().size();
         List<Token> firstLabelHolder = new ArrayList<>(1);
@@ -1212,7 +1196,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
         String packageName = Optional.ofNullable(moduleNode.getPackageName()).orElse("");
         String className = this.visitIdentifier(ctx.identifier());
-        if (VAR_STR.equals(className)) {
+        if ("var".equals(className)) {
             throw createParsingFailedException("var cannot be used for type declarations", ctx.identifier());
         }
 
@@ -1333,7 +1317,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         boolean isInterfaceWithDefaultMethods = (isInterface && this.containsDefaultMethods(ctx));
 
         if (isSealed) {
-            AnnotationNode sealedAnnotationNode = new AnnotationNode(ClassHelper.makeCached(Sealed.class));
+            AnnotationNode sealedAnnotationNode = makeAnnotationNode(Sealed.class);
             if (asBoolean(ctx.ps)) {
                 ListExpression permittedSubclassesListExpression =
                         listX(Arrays.stream(this.visitTypeList(ctx.ps))
@@ -1345,15 +1329,15 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             }
             classNode.addAnnotation(sealedAnnotationNode);
         } else if (isNonSealed) {
-            classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(NonSealed.class)));
+            classNode.addAnnotation(makeAnnotationNode(NonSealed.class));
         }
         if (isInterfaceWithDefaultMethods || asBoolean(ctx.TRAIT())) {
-            classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(Trait.class)));
+            classNode.addAnnotation(makeAnnotationNode(Trait.class));
         }
         classNode.addAnnotations(modifierManager.getAnnotations());
         if (isRecord && classNode.getAnnotations().stream().noneMatch(a ->
                         a.getClassNode().getName().equals(RECORD_TYPE_NAME))) {
-            classNode.addAnnotation(new AnnotationNode(ClassHelper.makeWithoutCaching(RECORD_TYPE_NAME)));
+            classNode.addAnnotation(new AnnotationNode(ClassHelper.makeWithoutCaching(RECORD_TYPE_NAME))); // TODO: makeAnnotationNode(RecordType.class)
         }
 
         if (isInterfaceWithDefaultMethods) {
@@ -1424,7 +1408,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         return classNode;
     }
 
-    private void transformRecordHeaderToProperties(ClassDeclarationContext ctx, ClassNode classNode) {
+    private void transformRecordHeaderToProperties(final ClassDeclarationContext ctx, final ClassNode classNode) {
         Parameter[] parameters = this.visitFormalParameters(ctx.formalParameters());
         classNode.putNodeMetaData(RECORD_HEADER, parameters);
 
@@ -1433,9 +1417,8 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             Parameter parameter = parameters[i];
             FormalParameterContext parameterCtx = parameter.getNodeMetaData(PARAMETER_CONTEXT);
             ModifierManager parameterModifierManager = parameter.getNodeMetaData(PARAMETER_MODIFIER_MANAGER);
-            ClassNode originType = parameter.getOriginType();
-            PropertyNode propertyNode = declareProperty(parameterCtx, parameterModifierManager, originType,
-                    classNode, i, parameter, parameter.getName(), parameter.getModifiers(), parameter.getInitialExpression());
+            PropertyNode propertyNode = declareProperty(parameterCtx, parameterModifierManager, parameter.getType(), classNode, i,
+                    parameter, parameter.getName(), parameter.getModifiers() | Opcodes.ACC_FINAL, parameter.getInitialExpression());
             propertyNode.getField().putNodeMetaData(IS_RECORD_GENERATED, Boolean.TRUE);
         }
     }
@@ -1598,7 +1581,6 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             this.visitMemberDeclaration(ctx.memberDeclaration());
         } else if (asBoolean(ctx.block())) {
             Statement statement = this.visitBlock(ctx.block());
-
             if (asBoolean(ctx.STATIC())) { // e.g. static { }
                 classNode.addStaticInitializerStatements(Collections.singletonList(statement), false);
             } else { // e.g. { }
@@ -1749,14 +1731,11 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             }
         });
 
-        ClassNode tupleConstructor = ClassHelper.makeCached(TupleConstructor.class);
-        List<AnnotationNode> annos = classNode.getAnnotations(tupleConstructor);
+        List<AnnotationNode> annos = classNode.getAnnotations(ClassHelper.make(TupleConstructor.class));
+        AnnotationNode tupleConstructor = annos.isEmpty() ? makeAnnotationNode(TupleConstructor.class) : annos.get(0);
+        tupleConstructor.setMember("pre", closureX(code));
         if (annos.isEmpty()) {
-            AnnotationNode tcAnnotation = new AnnotationNode(tupleConstructor);
-            tcAnnotation.setMember("pre", closureX(code));
-            classNode.addAnnotation(tcAnnotation);
-        } else {
-            annos.get(0).setMember("pre", closureX(code));
+            classNode.addAnnotation(tupleConstructor);
         }
 
         return null;
@@ -1987,31 +1966,17 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     }
 
     private DeclarationListStatement createMultiAssignmentDeclarationListStatement(final VariableDeclarationContext ctx, final ModifierManager modifierManager) {
-        /*
-        if (!modifierManager.contains(DEF)) {
-            throw createParsingFailedException("keyword def is required to declare tuple, e.g. def (int a, int b) = [1, 2]", ctx);
-        }
-        */
+        List<Expression> elist = this.visitTypeNamePairs(ctx.typeNamePairs());
+        for (Expression e : elist)
+            modifierManager.processVariableExpression((VariableExpression) e);
 
-        return configureAST(
-                new DeclarationListStatement(
-                        configureAST(
-                                modifierManager.attachAnnotations(
-                                        new DeclarationExpression(
-                                                new ArgumentListExpression(
-                                                        this.visitTypeNamePairs(ctx.typeNamePairs()).stream()
-                                                                .peek(e -> modifierManager.processVariableExpression((VariableExpression) e))
-                                                                .collect(Collectors.toList())
-                                                ),
-                                                this.createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN),
-                                                this.visitVariableInitializer(ctx.variableInitializer())
-                                        )
-                                ),
-                                ctx
-                        )
-                ),
-                ctx
-        );
+        DeclarationExpression de = new DeclarationExpression(
+                configureAST(new TupleExpression(elist), ctx.typeNamePairs()),
+                createGroovyTokenByType(ctx.ASSIGN().getSymbol(), Types.ASSIGN),
+                visitVariableInitializer(ctx.variableInitializer())           );
+
+        configureAST(modifierManager.attachAnnotations(de), ctx);
+        return configureAST(new DeclarationListStatement(de), ctx);
     }
 
     @Override
@@ -2037,21 +2002,17 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             return createFieldDeclarationListStatement(ctx, modifierManager, variableType, declarationExpressionList, classNode);
         }
 
-        declarationExpressionList.forEach(e -> {
-            VariableExpression variableExpression = (VariableExpression) e.getLeftExpression();
-
-            modifierManager.processVariableExpression(variableExpression);
-            modifierManager.attachAnnotations(e);
-        });
-
         int size = declarationExpressionList.size();
         if (size > 0) {
-            DeclarationExpression declarationExpression = declarationExpressionList.get(0);
+            for (DeclarationExpression e : declarationExpressionList) {
+                modifierManager.processVariableExpression(e.getVariableExpression());
+                modifierManager.attachAnnotations(e);
+            }
 
-            if (1 == size) {
+            DeclarationExpression declarationExpression = declarationExpressionList.get(0);
+            if (size == 1) {
                 configureAST(declarationExpression, ctx);
-            } else {
-                // Tweak start of first declaration
+            } else { // adjust start of first declaration
                 declarationExpression.setLineNumber(ctx.getStart().getLine());
                 declarationExpression.setColumnNumber(ctx.getStart().getCharPositionInLine() + 1);
             }
@@ -2125,7 +2086,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
                 fieldNode.setInitialValueExpression(initialValue);
             }
             modifierManager.attachAnnotations(propertyNode);
-            propertyNode.addAnnotation(new AnnotationNode(ClassHelper.make(CompileStatic.class)));
+            propertyNode.addAnnotation(makeAnnotationNode(CompileStatic.class));
             // expand properties early so AST transforms will be handled correctly
             PropertyExpander expander = new PropertyExpander(classNode);
             expander.visitProperty(propertyNode);
@@ -2170,7 +2131,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
             classNode.getFields().remove(propertyNode.getField());
             fieldNode = new FieldNode(fieldName, modifiers, variableType, classNode.redirect(), propertyNode.hasInitialExpression() ? propertyNode.getInitialExpression() : initialValue);
             propertyNode.setField(fieldNode);
-            propertyNode.addAnnotation(new AnnotationNode(ClassHelper.make(CompileStatic.class)));
+            propertyNode.addAnnotation(makeAnnotationNode(CompileStatic.class));
             classNode.addField(fieldNode);
             // expand properties early so AST transforms will be handled correctly
             PropertyExpander expander = new PropertyExpander(classNode);
@@ -4278,7 +4239,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     @Override
     public AnnotationNode visitAnnotation(final AnnotationContext ctx) {
         String annotationName = this.visitAnnotationName(ctx.annotationName());
-        AnnotationNode annotationNode = new AnnotationNode(ClassHelper.make(annotationName));
+        AnnotationNode annotationNode = new AnnotationNode(makeClassNode(annotationName));
         List<Tuple2<String, Expression>> annotationElementValues = this.visitElementValues(ctx.elementValues());
 
         annotationElementValues.forEach(e -> annotationNode.addMember(e.getV1(), e.getV2()));
@@ -4413,7 +4374,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     }
 
     private ClassNode createClassNode(final GroovyParserRuleContext ctx) {
-        ClassNode result = ClassHelper.make(ctx.getText());
+        ClassNode result = makeClassNode(ctx.getText());
 
         if (!isTrue(ctx, IS_INSIDE_INSTANCEOF_EXPR)) { // type in the "instanceof" expression should not have proxy to redirect to it
             result = this.proxyClassNode(result);
@@ -4727,17 +4688,7 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     }
 
     private boolean isTrue(final NodeMetaDataHandler nodeMetaDataHandler, final String key) {
-        Object nmd = nodeMetaDataHandler.getNodeMetaData(key);
-
-        if (null == nmd) {
-            return false;
-        }
-
-        if (!(nmd instanceof Boolean)) {
-            throw new GroovyBugError(nodeMetaDataHandler + " node meta data[" + key + "] is not an instance of Boolean");
-        }
-
-        return (Boolean) nmd;
+        return Boolean.TRUE.equals(nodeMetaDataHandler.getNodeMetaData(key));
     }
 
     private CompilationFailedException createParsingFailedException(final String msg, final GroovyParserRuleContext ctx) {
@@ -4914,7 +4865,6 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
     private static final String SQ_STR = "'";
     private static final String DQ_STR = "\"";
     private static final String DOLLAR_SLASH_STR = "$/";
-    private static final String VAR_STR = "var";
 
     private static final Map<String, String> QUOTATION_MAP = Maps.of(
             DQ_STR, DQ_STR,
diff --git a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
index 1319c1e829..4b26543d7d 100644
--- a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
@@ -63,6 +63,7 @@ import static org.codehaus.groovy.ast.AnnotationNode.RECORD_COMPONENT_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.TYPE_PARAMETER_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.TYPE_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.TYPE_USE_TARGET;
+import static org.codehaus.groovy.ast.ClassHelper.DEPRECATED_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.makeCached;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getInterfacesAndSuperInterfaces;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.listX;
@@ -146,10 +147,11 @@ public class ExtendedVerifier extends ClassCodeVisitorSupport {
 
     @Override
     public void visitDeclarationExpression(DeclarationExpression expression) {
-        VariableExpression varx = expression.getVariableExpression();
-        if (varx != null) {
-            visitAnnotations(expression, LOCAL_VARIABLE_TARGET);
-            ClassNode type = varx.getType();
+        visitAnnotations(expression, LOCAL_VARIABLE_TARGET);
+        if (expression.isMultipleAssignmentDeclaration()) {
+            expression.getTupleExpression().forEach(e -> visitTypeAnnotations(e.getType()));
+        } else {
+            ClassNode type = expression.getLeftExpression().getType();
             visitTypeAnnotations(type);
             extractTypeUseAnnotations(expression.getAnnotations(), type, LOCAL_VARIABLE_TARGET);
         }
@@ -393,7 +395,7 @@ public class ExtendedVerifier extends ClassCodeVisitorSupport {
     }
 
     private static void visitDeprecation(AnnotatedNode node, AnnotationNode visited) {
-        if (visited.getClassNode().isResolved() && visited.getClassNode().getName().equals("java.lang.Deprecated")) {
+        if (visited.getClassNode().isResolved() && visited.getClassNode().equals(DEPRECATED_TYPE)) {
             if (node instanceof MethodNode) {
                 MethodNode mn = (MethodNode) node;
                 mn.setModifiers(mn.getModifiers() | Opcodes.ACC_DEPRECATED);
diff --git a/src/test/org/apache/groovy/parser/antlr4/util/AstDumper.groovy b/src/test/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
index f67e01e6c6..48adb6a442 100644
--- a/src/test/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/util/AstDumper.groovy
@@ -18,8 +18,9 @@
  */
 package org.apache.groovy.parser.antlr4.util
 
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
-import org.codehaus.groovy.ast.ASTNode
 import org.codehaus.groovy.ast.AnnotationNode
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
@@ -96,6 +97,7 @@ import org.codehaus.groovy.classgen.Verifier
 import org.codehaus.groovy.control.CompilationUnit
 import org.codehaus.groovy.control.SourceUnit
 import org.codehaus.groovy.control.io.ReaderSource
+import org.codehaus.groovy.syntax.Types
 
 import java.lang.reflect.Modifier
 
@@ -142,11 +144,11 @@ class AstDumper {
  *
  * An adapter from ASTNode tree to source code.
  */
-@CompileStatic
+@AutoFinal @CompileStatic
 class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
 
     private final Writer _out
-    Stack<String> classNameStack = new Stack<String>()
+    Stack<String> classNameStack = []
     String _indent = ''
     boolean readyToIndent = true
     boolean showScriptFreeForm
@@ -176,7 +178,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         }
     }
 
-    private def visitAllImports(SourceUnit source) {
+    private void visitAllImports(SourceUnit source) {
         boolean staticImportsPresent = false
         boolean importsPresent = false
 
@@ -206,7 +208,6 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         }
     }
 
-
     void print(parameter) {
         def output = parameter.toString()
 
@@ -298,6 +299,8 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         if (node.isInterface()) print node.name
         else print "class $node.name"
         visitGenerics node?.genericsTypes
+        print ' extends '
+        visitType node.unresolvedSuperClass
         boolean first = true
         node.unresolvedInterfaces?.each {
             if (!first) {
@@ -308,8 +311,6 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
             first = false
             visitType it
         }
-        print ' extends '
-        visitType node.unresolvedSuperClass
         print ' { '
         printDoubleBreak()
 
@@ -378,26 +379,24 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         visitMethod(node)
     }
 
-    private String visitParameters(parameters) {
-        boolean first = true
-
-        parameters.each { Parameter it ->
-            if (!first) {
+    private void visitParameters(Parameter[] parameters) {
+        int i = 0
+        for (p in parameters) {
+            if (i++ != 0) {
                 print ', '
             }
-            first = false
 
-            it.annotations?.each {
-                visitAnnotationNode(it)
-                print(' ')
+            for (a in p.annotations) {
+                visitAnnotationNode a
+                print ' '
             }
 
-            visitModifiers(it.modifiers)
-            visitType it.type
-            print ' ' + it.name
-            if (it.initialExpression && !(it.initialExpression instanceof EmptyExpression)) {
+            visitModifiers(p.modifiers)
+            visitType p.type
+            print ' ' + p.name
+            if (p.initialExpression && p.initialExpression !instanceof EmptyExpression) {
                 print ' = '
-                it.initialExpression.visit this
+                p.initialExpression.visit this
             }
         }
     }
@@ -412,7 +411,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         visitModifiers(node.modifiers)
         if (node.name == '<init>') {
             print "${classNameStack.peek()}("
-            visitParameters(node.parameters)
+            visitParameters node.parameters
             print ') {'
             printLineBreak()
         } else if (node.name == '<clinit>') {
@@ -421,7 +420,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         } else {
             visitType node.returnType
             print " $node.name("
-            visitParameters(node.parameters)
+            visitParameters node.parameters
             print ')'
             if (node.exceptions) {
                 boolean first = true
@@ -472,7 +471,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
             // GROOVY-5150: final constants may be initialized directly
             print ' = '
             if (ClassHelper.STRING_TYPE == type) {
-                print "'" + node.initialValueExpression.text.replaceAll("'", "\\\\'") + "'"
+                print "'" + node.initialValueExpression.text.replace("'", "\\'") + "'"
             } else if (ClassHelper.char_TYPE == type) {
                 print "'${node.initialValueExpression.text}'"
             } else {
@@ -535,7 +534,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         printStatementLabels(statement)
         print 'for ('
         if (statement?.variable != ForStatement.FOR_LOOP_DUMMY) {
-            visitParameters([statement.variable])
+            visitParameters statement.variable
             print ' : '
         }
 
@@ -600,7 +599,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
             statement?.caseStatements?.each {
                 visitCaseStatement it
             }
-            if (statement?.defaultStatement) {
+            if (statement?.defaultStatement !instanceof EmptyStatement) {
                 print 'default: '
                 printLineBreak()
                 statement?.defaultStatement?.visit this
@@ -641,12 +640,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitMethodCallExpression(MethodCallExpression expression) {
-        Expression objectExp = expression.objectExpression
-        if (objectExp instanceof VariableExpression) {
-            visitVariableExpression(objectExp, false)
-        } else {
-            objectExp.visit(this)
-        }
+        printExpression expression.objectExpression
         if (expression.spreadSafe) {
             print '*'
         }
@@ -665,9 +659,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitStaticMethodCallExpression(StaticMethodCallExpression expression) {
+        boolean parens = expression?.arguments instanceof MethodCallExpression
+                || expression?.arguments instanceof VariableExpression
         print expression?.ownerType?.name + '.' + expression?.method
-        boolean parens = expression?.arguments instanceof VariableExpression
-                    || expression?.arguments instanceof MethodCallExpression
         if (parens) print '('
         expression?.arguments?.visit this
         if (parens) print ')'
@@ -688,37 +682,51 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitBinaryExpression(BinaryExpression expression) {
-        expression?.leftExpression?.visit this
-        print " $expression.operation.text "
-        expression.rightExpression.visit this
-
-        if (expression?.operation?.text == '[') {
-            print ']'
+        if (expression !instanceof DeclarationExpression && expression?.leftExpression instanceof VariableExpression) {
+            visitVariableExpression((VariableExpression) expression.leftExpression, false)
+        } else {
+            expression?.leftExpression?.visit this
+        }
+        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} "
+            expression?.rightExpression?.visit this
+            if (isAccess) {
+                print ']'
+            }
         }
     }
 
     @Override
     void visitPostfixExpression(PostfixExpression expression) {
-        print '('
-        expression?.expression?.visit this
-        print ')'
+        if (expression?.expression instanceof VariableExpression) {
+            visitVariableExpression((VariableExpression) expression?.expression, false)
+        } else {
+            print '('
+            expression?.expression?.visit this
+            print ')'
+        }
         print expression?.operation?.text
     }
 
     @Override
     void visitPrefixExpression(PrefixExpression expression) {
         print expression?.operation?.text
-        print '('
-        expression?.expression?.visit this
-        print ')'
+        if (expression?.expression instanceof VariableExpression) {
+            visitVariableExpression((VariableExpression) expression?.expression, false)
+        } else {
+            print '('
+            expression?.expression?.visit this
+            print ')'
+        }
     }
 
-
     @Override
     void visitClosureExpression(ClosureExpression expression) {
         print '{ '
         if (expression?.parameters) {
-            visitParameters(expression?.parameters)
+            visitParameters expression?.parameters
             print ' ->'
         }
         printLineBreak()
@@ -730,9 +738,9 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitLambdaExpression(LambdaExpression expression) {
-        print '( '
+        print '('
         if (expression?.parameters) {
-            visitParameters(expression?.parameters)
+            visitParameters expression?.parameters
         }
         print ') -> {'
         printLineBreak()
@@ -745,7 +753,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitTupleExpression(TupleExpression expression) {
         print '('
-        visitExpressionsAndCommaSeparate(expression?.expressions)
+        printExpressions expression?.expressions
         print ')'
     }
 
@@ -760,7 +768,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitPropertyExpression(PropertyExpression expression) {
-        expression?.objectExpression?.visit this
+        printExpression expression?.objectExpression
         if (expression?.spreadSafe) {
             print '*'
         } else if (expression?.isSafe()) {
@@ -784,6 +792,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         print expression?.field?.name
     }
 
+    @Override
     void visitConstantExpression(ConstantExpression expression, boolean unwrapQuotes = false) {
         if (expression.value instanceof String && !unwrapQuotes) {
             // string reverse escaping is very naive
@@ -799,8 +808,8 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         print expression.text
     }
 
+    @Override
     void visitVariableExpression(VariableExpression expression, boolean spacePad = true) {
-
         if (spacePad) {
             print ' ' + expression.name + ' '
         } else {
@@ -810,20 +819,22 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitDeclarationExpression(DeclarationExpression expression) {
-        // handle multiple assignment expressions
-        if (expression?.leftExpression instanceof ArgumentListExpression) {
-            print 'def '
-            visitArgumentlistExpression((ArgumentListExpression) expression?.leftExpression, true)
-            print " $expression.operation.text "
-            expression.rightExpression.visit this
-
-            if (expression?.operation?.text == '[') {
-                print ']'
-            }
-        } else {
-            visitType expression?.leftExpression?.type
-            visitBinaryExpression expression // is a BinaryExpression
+        if (!expression.isMultipleAssignmentDeclaration()) {
+            visitType expression.leftExpression.type
+            visitBinaryExpression expression
+            return
         }
+
+        print 'def ('
+        int i = 0
+        for (e in expression.tupleExpression) {
+            if (i++ != 0) print ', '
+            visitType e.type
+            print ' '
+            printExpression e
+        }
+        print ') = '
+        expression.rightExpression.visit this
     }
 
     @Override
@@ -837,35 +848,57 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         expression?.expression?.visit this
     }
 
+    @CompileDynamic
+    private void printUnaryExpression(String opText, Expression expression) {
+        print opText
+        if (expression?.expression instanceof VariableExpression) {
+            visitVariableExpression((VariableExpression) expression?.expression, false)
+        } else if (expression?.expression instanceof PropertyExpression)  {
+            expression?.expression?.visit this
+        } else {
+            print '('
+            expression?.expression?.visit this
+            print ')'
+        }
+    }
+
     @Override
     void visitNotExpression(NotExpression expression) {
-        print '!('
-        expression?.expression?.visit this
-        print ')'
+        printUnaryExpression('!', expression)
     }
 
     @Override
     void visitUnaryMinusExpression(UnaryMinusExpression expression) {
-        print '-('
-        expression?.expression?.visit this
-        print ')'
+        printUnaryExpression('-', expression)
     }
 
     @Override
     void visitUnaryPlusExpression(UnaryPlusExpression expression) {
-        print '+('
-        expression?.expression?.visit this
-        print ')'
+        printUnaryExpression('+', expression)
     }
 
     @Override
     void visitCastExpression(CastExpression expression) {
-        print '(('
-        expression?.expression?.visit this
-        print ') as '
-        visitType(expression?.type)
+        print '('
+        if (expression?.coerce) {
+            boolean isVariableExpressionOrPropertyExpression =
+                    expression?.expression instanceof VariableExpression || expression?.expression instanceof PropertyExpression
+            if (!isVariableExpressionOrPropertyExpression) {
+                print '('
+            }
+            printExpression(expression?.expression)
+            if (!isVariableExpressionOrPropertyExpression) {
+                print ')'
+            }
+            print ' as '
+            visitType(expression?.type)
+        } else {
+            print '('
+            visitType(expression?.type)
+            print ') '
+            printExpression(expression?.expression)
+        }
         print ')'
-
     }
 
     /**
@@ -883,41 +916,19 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         visitGenerics classNode?.genericsTypes
     }
 
-    void visitArgumentlistExpression(ArgumentListExpression expression, boolean showTypes = false) {
-        print '('
-        int count = expression?.expressions?.size()
-        expression.expressions.each {
-            if (showTypes) {
-                visitType it.type
-                print ' '
-            }
-            if (it instanceof VariableExpression) {
-                visitVariableExpression it, false
-            } else if (it instanceof ConstantExpression) {
-                visitConstantExpression it, false
-            } else {
-                it.visit this
-            }
-            count--
-            if (count) print ', '
-        }
-        print ')'
-    }
-
     @Override
     void visitBytecodeExpression(BytecodeExpression expression) {
         print '/*BytecodeExpression*/'
         printLineBreak()
     }
 
-
     @Override
     void visitMapExpression(MapExpression expression) {
         print '['
-        if (expression?.mapEntryExpressions?.size() == 0) {
+        if (!expression?.mapEntryExpressions) {
             print ':'
         } else {
-            visitExpressionsAndCommaSeparate((List) expression?.mapEntryExpressions)
+            printExpressions expression.mapEntryExpressions
         }
         print ']'
     }
@@ -936,7 +947,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitListExpression(ListExpression expression) {
         print '['
-        visitExpressionsAndCommaSeparate(expression?.expressions)
+        printExpressions expression?.expressions
         print ']'
     }
 
@@ -954,13 +965,15 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         statement?.catchStatements?.each { CatchStatement catchStatement ->
             visitCatchStatement(catchStatement)
         }
-        print 'finally { '
-        printLineBreak()
-        indented {
-            statement?.finallyStatement?.visit this
+        if (statement?.finallyStatement !instanceof EmptyStatement) {
+            print 'finally { '
+            printLineBreak()
+            indented {
+                statement?.finallyStatement?.visit this
+            }
+            print '} '
+            printLineBreak()
         }
-        print '} '
-        printLineBreak()
     }
 
     @Override
@@ -994,12 +1007,12 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitShortTernaryExpression(ElvisOperatorExpression expression) {
-        visitTernaryExpression(expression)
+        visitTernaryExpression expression
     }
 
     @Override
     void visitBooleanExpression(BooleanExpression expression) {
-        expression?.expression?.visit this
+        printExpression expression?.expression
     }
 
     @Override
@@ -1034,7 +1047,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitCatchStatement(CatchStatement statement) {
         print 'catch ('
-        visitParameters([statement.variable])
+        visitParameters statement.variable
         print ') {'
         printLineBreak()
         indented {
@@ -1059,15 +1072,22 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         statement?.messageExpression?.visit this
     }
 
+    @Override
+    void visitArgumentlistExpression(ArgumentListExpression expression) {
+        visitTupleExpression expression
+    }
+
     @Override
     void visitClosureListExpression(ClosureListExpression expression) {
-        boolean first = true
-        expression?.expressions?.each {
-            if (!first) {
+        int i = 0
+        for (e in expression?.expressions) {
+            if (i++ != 0) {
                 print ';'
+                if (e !instanceof EmptyExpression) {
+                    print ' '
+                }
             }
-            first = false
-            it.visit this
+            e.visit this
         }
     }
 
@@ -1090,27 +1110,36 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         print 'new '
         visitType expression?.elementType
         print '['
-        visitExpressionsAndCommaSeparate(expression?.sizeExpression)
+        printExpressions expression?.sizeExpression
         print ']'
     }
 
-    private void visitExpressionsAndCommaSeparate(List<? super Expression> expressions) {
-        boolean first = true
-        expressions?.each {
-            if (!first) {
-                print ', '
-            }
-            first = false
-            ((ASTNode) it).visit this
-        }
-    }
-
     @Override
     void visitSpreadMapExpression(SpreadMapExpression expression) {
         print '*:'
         expression?.expression?.visit this
     }
 
+    //--------------------------------------------------------------------------
+
+    private void printExpression(Expression expression) {
+        if (expression instanceof VariableExpression) {
+            visitVariableExpression expression, false
+        } else {
+            expression?.visit this
+        }
+    }
+
+    private void printExpressions(List<? extends Expression> expressions) {
+        int i = 0
+        for (e in expressions) {
+            if (i++ != 0) {
+                print ', '
+            }
+            printExpression e
+        }
+    }
+
     /**
      * Prints all labels for the given statement.  The labels will be printed on a single
      * line and line break will be added.
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 dd5be0ed3c..050c9bed65 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
@@ -186,7 +186,7 @@ and [compilephase] is a valid Integer based org.codehaus.groovy.control.CompileP
 class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperation, GroovyClassVisitor, GroovyCodeVisitor {
 
     private final Writer _out
-    Stack<String> classNameStack = new Stack<>()
+    Stack<String> classNameStack = []
     String _indent = ''
     boolean readyToIndent = true
     boolean showScriptFreeForm
@@ -200,6 +200,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         this.scriptHasBeenVisited = false
     }
 
+    @Override
     void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
 
         visitPackage(source?.getAST()?.getPackage())
@@ -215,7 +216,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         }
     }
 
-    private def visitAllImports(SourceUnit source) {
+    private void visitAllImports(SourceUnit source) {
         boolean staticImportsPresent = false
         boolean importsPresent = false
 
@@ -245,7 +246,6 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         }
     }
 
-
     void print(parameter) {
         def output = parameter.toString()
 
@@ -417,26 +417,24 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         visitMethod(node)
     }
 
-    private String visitParameters(parameters) {
-        boolean first = true
-
-        parameters.each { Parameter it ->
-            if (!first) {
+    private void visitParameters(Parameter[] parameters) {
+        int i = 0
+        for (p in parameters) {
+            if (i++ != 0) {
                 print ', '
             }
-            first = false
 
-            it.annotations?.each {
-                visitAnnotationNode(it)
-                print(' ')
+            for (a in p.annotations) {
+                visitAnnotationNode a
+                print ' '
             }
 
-            visitModifiers(it.modifiers)
-            visitType it.type
-            print ' ' + it.name
-            if (it.initialExpression && !(it.initialExpression instanceof EmptyExpression)) {
+            visitModifiers(p.modifiers)
+            visitType p.type
+            print ' ' + p.name
+            if (p.initialExpression && p.initialExpression !instanceof EmptyExpression) {
                 print ' = '
-                it.initialExpression.visit this
+                p.initialExpression.visit this
             }
         }
     }
@@ -451,7 +449,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         visitModifiers(node.modifiers)
         if (node.name == '<init>') {
             print "${classNameStack.peek()}("
-            visitParameters(node.parameters)
+            visitParameters node.parameters
             print ') {'
             printLineBreak()
         } else if (node.name == '<clinit>') {
@@ -460,7 +458,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         } else {
             visitType node.returnType
             print " $node.name("
-            visitParameters(node.parameters)
+            visitParameters node.parameters
             print ')'
             if (node.exceptions) {
                 boolean first = true
@@ -574,7 +572,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         printStatementLabels(statement)
         print 'for ('
         if (statement?.variable != ForStatement.FOR_LOOP_DUMMY) {
-            visitParameters([statement.variable])
+            visitParameters statement.variable
             print ' : '
         }
 
@@ -680,8 +678,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitMethodCallExpression(MethodCallExpression expression) {
-        Expression objectExpr = expression.objectExpression
-        printExpression(objectExpr)
+        printExpression expression.objectExpression
         if (expression.spreadSafe) {
             print '*'
         }
@@ -704,7 +701,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
                 || expression?.arguments instanceof VariableExpression
         print expression?.ownerType?.name + '.' + expression?.method
         if (parens) print '('
-        expression?.arguments?.visit(this)
+        expression?.arguments?.visit this
         if (parens) print ')'
     }
 
@@ -724,20 +721,16 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitBinaryExpression(BinaryExpression expression) {
         if (expression !instanceof DeclarationExpression && expression?.leftExpression instanceof VariableExpression) {
-            visitVariableExpression((VariableExpression) expression?.leftExpression, false)
+            visitVariableExpression((VariableExpression) expression.leftExpression, false)
         } else {
             expression?.leftExpression?.visit this
         }
-        if (!(expression.rightExpression instanceof EmptyExpression) || expression.operation.type != Types.ASSIGN) {
-            String opText = expression?.operation?.text
-            boolean isSubscriptOp = opText == '['
-            if (isSubscriptOp) {
-                print "["
-            } else {
-                print " $opText "
-            }
-            expression.rightExpression.visit this
-            if (isSubscriptOp) {
+        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} "
+            expression?.rightExpression?.visit this
+            if (isAccess) {
                 print ']'
             }
         }
@@ -771,7 +764,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     void visitClosureExpression(ClosureExpression expression) {
         print '{ '
         if (expression?.parameters) {
-            visitParameters(expression?.parameters)
+            visitParameters expression?.parameters
             print ' ->'
         }
         printLineBreak()
@@ -785,7 +778,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     void visitLambdaExpression(LambdaExpression expression) {
         print '('
         if (expression?.parameters) {
-            visitParameters(expression?.parameters)
+            visitParameters expression?.parameters
         }
         print ') -> {'
         printLineBreak()
@@ -798,7 +791,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitTupleExpression(TupleExpression expression) {
         print '('
-        visitExpressionsAndCommaSeparate(expression?.expressions)
+        printExpressions expression?.expressions
         print ')'
     }
 
@@ -813,8 +806,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitPropertyExpression(PropertyExpression expression) {
-        printExpression(expression?.objectExpression)
-
+        printExpression expression?.objectExpression
         if (expression?.spreadSafe) {
             print '*'
         } else if (expression?.isSafe()) {
@@ -838,6 +830,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         print expression?.field?.name
     }
 
+    @Override
     void visitConstantExpression(ConstantExpression expression, boolean unwrapQuotes = false) {
         if (expression.value instanceof String && !unwrapQuotes) {
             // string reverse escaping is very naive
@@ -864,20 +857,22 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitDeclarationExpression(DeclarationExpression expression) {
-        // handle multiple assignment expressions
-        if (expression?.leftExpression instanceof ArgumentListExpression) {
-            print 'def '
-            visitArgumentlistExpression((ArgumentListExpression) expression?.leftExpression, true)
-            print " $expression.operation.text "
-            expression.rightExpression.visit this
-
-            if (expression?.operation?.text == '[') {
-                print ']'
-            }
-        } else {
-            visitType expression?.leftExpression?.type
-            visitBinaryExpression expression // is a BinaryExpression
+        if (!expression.isMultipleAssignmentDeclaration()) {
+            visitType expression.leftExpression.type
+            visitBinaryExpression expression
+            return
         }
+
+        print 'def ('
+        int i = 0
+        for (e in expression.tupleExpression) {
+            if (i++ != 0) print ', '
+            visitType e.type
+            print ' '
+            printExpression e
+        }
+        print ') = '
+        expression.rightExpression.visit this
     }
 
     @Override
@@ -959,27 +954,6 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         visitGenerics classNode?.genericsTypes
     }
 
-    void visitArgumentlistExpression(ArgumentListExpression expression, boolean showTypes = false) {
-        print '('
-        int count = expression?.expressions?.size()
-        expression.expressions.each {
-            if (showTypes) {
-                visitType it.type
-                print ' '
-            }
-            if (it instanceof VariableExpression) {
-                visitVariableExpression it, false
-            } else if (it instanceof ConstantExpression) {
-                visitConstantExpression it, false
-            } else {
-                it.visit this
-            }
-            count--
-            if (count) print ', '
-        }
-        print ')'
-    }
-
     @Override
     void visitBytecodeExpression(BytecodeExpression expression) {
         print '/*BytecodeExpression*/'
@@ -989,10 +963,10 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitMapExpression(MapExpression expression) {
         print '['
-        if (expression?.mapEntryExpressions?.size() == 0) {
+        if (!expression?.mapEntryExpressions) {
             print ':'
         } else {
-            visitExpressionsAndCommaSeparate((List) expression?.mapEntryExpressions)
+            printExpressions expression.mapEntryExpressions
         }
         print ']'
     }
@@ -1011,7 +985,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitListExpression(ListExpression expression) {
         print '['
-        visitExpressionsAndCommaSeparate(expression?.expressions)
+        printExpressions expression?.expressions
         print ']'
     }
 
@@ -1071,12 +1045,12 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
 
     @Override
     void visitShortTernaryExpression(ElvisOperatorExpression expression) {
-        visitTernaryExpression(expression)
+        visitTernaryExpression expression
     }
 
     @Override
     void visitBooleanExpression(BooleanExpression expression) {
-        printExpression(expression?.expression)
+        printExpression expression?.expression
     }
 
     @Override
@@ -1111,7 +1085,7 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
     @Override
     void visitCatchStatement(CatchStatement statement) {
         print 'catch ('
-        visitParameters([statement.variable])
+        visitParameters statement.variable
         print ') {'
         printLineBreak()
         indented {
@@ -1136,18 +1110,22 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         statement?.messageExpression?.visit this
     }
 
+    @Override
+    void visitArgumentlistExpression(ArgumentListExpression expression) {
+        visitTupleExpression expression
+    }
+
     @Override
     void visitClosureListExpression(ClosureListExpression expression) {
-        boolean first = true
-        expression?.expressions?.each {
-            if (!first) {
+        int i = 0
+        for (e in expression?.expressions) {
+            if (i++ != 0) {
                 print ';'
-                if (it !instanceof EmptyExpression) {
+                if (e !instanceof EmptyExpression) {
                     print ' '
                 }
             }
-            first = false
-            it.visit this
+            e.visit this
         }
     }
 
@@ -1170,29 +1148,36 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         print 'new '
         visitType expression?.elementType
         print '['
-        visitExpressionsAndCommaSeparate(expression?.sizeExpression)
+        printExpressions expression?.sizeExpression
         print ']'
     }
 
-    private void visitExpressionsAndCommaSeparate(List<? extends Expression> expressions) {
-        if (expressions) {
-            boolean first = true
-            for (e in expressions) {
-                if (!first) {
-                    print ', '
-                }
-                first = false
-                e.visit(this)
-            }
-        }
-    }
-
     @Override
     void visitSpreadMapExpression(SpreadMapExpression expression) {
         print '*:'
         expression?.expression?.visit this
     }
 
+    //--------------------------------------------------------------------------
+
+    private void printExpression(Expression expression) {
+        if (expression instanceof VariableExpression) {
+            visitVariableExpression expression, false
+        } else {
+            expression?.visit this
+        }
+    }
+
+    private void printExpressions(List<? extends Expression> expressions) {
+        int i = 0
+        for (e in expressions) {
+            if (i++ != 0) {
+                print ', '
+            }
+            printExpression e
+        }
+    }
+
     /**
      * Prints all labels for the given statement.  The labels will be printed on a single
      * line and line break will be added.
@@ -1211,12 +1196,4 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati
         }
         return true
     }
-
-    private void printExpression(Expression expression) {
-        if (expression instanceof VariableExpression) {
-            visitVariableExpression((VariableExpression) expression, false)
-        } else {
-            expression?.visit this
-        }
-    }
 }
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 7da2ede8ba..1d60b0954e 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
@@ -287,7 +287,7 @@ final class AstNodeToScriptAdapterTest extends GroovyTestCase {
                         ['foo': 'bar', 'baz': bif + qux]'''
 
         String result = compileToScript(script, CompilePhase.SEMANTIC_ANALYSIS)
-        assert result.contains("['foo', \"\$bar\", x , java.lang.Math.min(5, 3)]")
+        assert result.contains("['foo', \"\$bar\", x, java.lang.Math.min(5, 3)]")
         assert result.contains("((['foo', \"\$bar\"]) as java.lang.String[])")
         assert result.contains("['foo': 'bar', 'baz': bif + qux ]")
     }
@@ -866,8 +866,8 @@ final class AstNodeToScriptAdapterTest extends GroovyTestCase {
         String result = compileToScript(script, CompilePhase.SEMANTIC_ANALYSIS)
         assert result.contains('java.lang.Object c1 = new MyClass()')
         assert result.contains('java.lang.Object c2 = new MyClass()')
-        assert result.contains('assert [ c1 , c2 ]*.getA() == [c1.getA(), c2.getA()] : null')
-        assert result.contains("assert [ c1 , c2 ]*.getA() == ['abc', 'abc'] : null")
+        assert result.contains('assert [c1, c2]*.getA() == [c1.getA(), c2.getA()] : null')
+        assert result.contains("assert [c1, c2]*.getA() == ['abc', 'abc'] : null")
     }
 
     void testVisitSafeMethodCall() {
diff --git a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy
index b63e878d08..73595d7c28 100644
--- a/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy
+++ b/subprojects/groovy-console/src/test/groovy/groovy/console/ui/ScriptToTreeNodeAdapterTest.groovy
@@ -258,7 +258,7 @@ final class ScriptToTreeNodeAdapterTest extends GroovyTestCase {
                         startsWith('BlockStatement'),
                         startsWith('ExpressionStatement'),
                         eq('Declaration - def (x, y) = [1, 2]'),
-                        eq('ArgumentList - (x, y)'),
+                        eq('Tuple - (x, y)'),
                 ]
         )
     }