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
commit 876fbed302a2e92d572d6c01ce4f134c712cb1c5 Author: Eric Milles <[email protected]> AuthorDate: Mon Mar 31 19:46:39 2025 -0500 GROOVY-10683: support `for (index, item in list)` looping --- src/antlr/GroovyParser.g4 | 6 +++- .../apache/groovy/parser/antlr4/AstBuilder.java | 36 +++++++++++++------ .../org/codehaus/groovy/ast/stmt/ForStatement.java | 42 +++++++++++++++++----- .../groovy/classgen/VariableScopeVisitor.java | 11 ++++-- .../groovy/classgen/VerifierCodeVisitor.java | 12 +++++-- .../groovy/classgen/asm/StatementWriter.java | 15 +++++--- .../asm/sc/StaticTypesStatementWriter.java | 29 ++++++++++----- .../transform/CategoryASTTransformation.java | 6 +++- .../transform/sc/StaticCompilationVisitor.java | 7 ++-- src/spec/test/SemanticsTest.groovy | 5 +++ src/test/groovy/ForLoopTest.groovy | 22 ++++++++++++ .../groovy/parser/antlr4/util/AstDumper.groovy | 8 +++-- .../console/ui/AstNodeToScriptAdapter.groovy | 17 +++++---- 13 files changed, 164 insertions(+), 52 deletions(-) diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4 index edf75c4cde..b41faad333 100644 --- a/src/antlr/GroovyParser.g4 +++ b/src/antlr/GroovyParser.g4 @@ -691,7 +691,11 @@ forControl ; enhancedForControl - : variableModifiersOpt type? identifier (COLON | IN) expression + : (indexVariable COMMA)? variableModifiersOpt type? identifier (COLON | IN) expression + ; + +indexVariable + : (BuiltInPrimitiveType | DEF | VAR)? identifier ; originalForControl 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 e6903c80d4..2bef2bb19e 100644 --- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java +++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java @@ -147,6 +147,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; import static groovy.lang.Tuple.tuple; @@ -469,15 +470,15 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> { @Override public ForStatement visitForStmtAlt(final ForStmtAltContext ctx) { - var controlElements = this.visitForControl(ctx.forControl()); + Function<Statement, ForStatement> maker = this.visitForControl(ctx.forControl()); - Statement loopBlock = this.unpackStatement((Statement) this.visit(ctx.statement())); + Statement loopBody = this.unpackStatement((Statement) this.visit(ctx.statement())); - return configureAST(new ForStatement(controlElements.getV1(), controlElements.getV2(), loopBlock), ctx); + return configureAST(maker.apply(loopBody), ctx); } @Override - public Tuple2<Parameter, Expression> visitForControl(final ForControlContext ctx) { + public Function<Statement, ForStatement> visitForControl(final ForControlContext ctx) { // e.g. `for (var e : x) { }` and `for (e in x) { }` EnhancedForControlContext enhancedCtx = ctx.enhancedForControl(); if (asBoolean(enhancedCtx)) { @@ -494,22 +495,37 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> { } @Override - public Tuple2<Parameter, Expression> visitEnhancedForControl(final EnhancedForControlContext ctx) { - var parameter = configureAST(new Parameter(this.visitType(ctx.type()), this.visitIdentifier(ctx.identifier())), ctx.identifier()); - new ModifierManager(this, this.visitVariableModifiersOpt(ctx.variableModifiersOpt())).processParameter(parameter); + public Function<Statement, ForStatement> visitEnhancedForControl(final EnhancedForControlContext ctx) { + var indexParameter = asBoolean(ctx.indexVariable()) ? this.visitIndexVariable(ctx.indexVariable()) : null; - return tuple(parameter, (Expression) this.visit(ctx.expression())); + var valueParameter = configureAST(new Parameter(this.visitType(ctx.type()), this.visitIdentifier(ctx.identifier())), ctx.identifier()); + ModifierManager modifierManager = new ModifierManager(this, this.visitVariableModifiersOpt(ctx.variableModifiersOpt())); + modifierManager.processParameter(valueParameter); + + return (body) -> new ForStatement(indexParameter, valueParameter, (Expression) this.visit(ctx.expression()), body); + } + + @Override + public Parameter visitIndexVariable(final IndexVariableContext ctx) { + var primitiveType = ctx.BuiltInPrimitiveType(); + if (primitiveType != null && primitiveType.getText().length() != 3) { + throw this.createParsingFailedException(primitiveType.getText() + " is not allowed here", ctx); + } + + var indexParameter = configureAST(new Parameter(ClassHelper.int_TYPE, this.visitIdentifier(ctx.identifier())), ctx.identifier()); + indexParameter.setModifiers(Opcodes.ACC_FINAL); + return indexParameter; } @Override - public Tuple2<Parameter, Expression> visitOriginalForControl(final OriginalForControlContext ctx) { + public Function<Statement, ForStatement> visitOriginalForControl(final OriginalForControlContext ctx) { ClosureListExpression closureListExpression = new ClosureListExpression(); closureListExpression.addExpression(this.visitForInit(ctx.forInit())); closureListExpression.addExpression(Optional.ofNullable(ctx.expression()) .map(e -> (Expression) this.visit(e)).orElse(EmptyExpression.INSTANCE)); closureListExpression.addExpression(this.visitForUpdate(ctx.forUpdate())); - return tuple(ForStatement.FOR_LOOP_DUMMY, closureListExpression); + return (body) -> new ForStatement(ForStatement.FOR_LOOP_DUMMY, closureListExpression, body); } @Override diff --git a/src/main/java/org/codehaus/groovy/ast/stmt/ForStatement.java b/src/main/java/org/codehaus/groovy/ast/stmt/ForStatement.java index 3cfdd9d455..40d3e5dd13 100644 --- a/src/main/java/org/codehaus/groovy/ast/stmt/ForStatement.java +++ b/src/main/java/org/codehaus/groovy/ast/stmt/ForStatement.java @@ -25,6 +25,8 @@ import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.VariableScope; import org.codehaus.groovy.ast.expr.Expression; +import static java.util.Objects.requireNonNull; + /** * Represents a for loop in Groovy. */ @@ -32,33 +34,57 @@ public class ForStatement extends Statement implements LoopingStatement { public static final Parameter FOR_LOOP_DUMMY = new Parameter(ClassHelper.OBJECT_TYPE, "forLoopDummyParameter"); - private final Parameter variable; + private final Parameter indexVariable, valueVariable; private Expression collectionExpression; private Statement loopBlock; - public ForStatement(final Parameter variable, final Expression collectionExpression, final Statement loopBlock) { - this.variable = variable; + /** + * @since 5.0.0 + */ + public ForStatement(final Parameter indexVariable, final Parameter valueVariable, final Expression collectionExpression, final Statement loopBlock) { + this.indexVariable = indexVariable; // optional component + this.valueVariable = requireNonNull(valueVariable); setCollectionExpression(collectionExpression); setLoopBlock(loopBlock); } + public ForStatement(final Parameter variable, final Expression collectionExpression, final Statement loopBlock) { + this(null, variable, collectionExpression, loopBlock); + } + public void setCollectionExpression(final Expression collectionExpression) { - this.collectionExpression = collectionExpression; + this.collectionExpression = requireNonNull(collectionExpression); } @Override public void setLoopBlock(final Statement loopBlock) { - this.loopBlock = loopBlock; + this.loopBlock = requireNonNull(loopBlock); } //-------------------------------------------------------------------------- + /** + * @since 5.0.0 + */ + public Parameter getIndexVariable() { + return indexVariable; + } + + /** + * @since 5.0.0 + */ + public Parameter getValueVariable() { + return valueVariable != FOR_LOOP_DUMMY ? valueVariable : null; + } + + @Deprecated(since = "5.0.0") public Parameter getVariable() { - return variable; + return valueVariable; } + @Deprecated(since = "5.0.0") public ClassNode getVariableType() { - return variable.getType(); + return valueVariable.getType(); } public Expression getCollectionExpression() { @@ -79,7 +105,7 @@ public class ForStatement extends Statement implements LoopingStatement { } public void setVariableScope(final VariableScope scope) { - this.scope = scope; + this.scope = scope; } //-------------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java b/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java index 7c0b2f4bc9..4a73c7ffbf 100644 --- a/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java +++ b/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java @@ -69,7 +69,9 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Supplier; import static java.lang.reflect.Modifier.isFinal; @@ -551,9 +553,12 @@ public class VariableScopeVisitor extends ClassCodeVisitorSupport { public void visitForLoop(final ForStatement statement) { pushState(); statement.setVariableScope(currentScope); - Parameter parameter = statement.getVariable(); - parameter.setInStaticContext(currentScope.isInStaticContext()); - if (parameter != ForStatement.FOR_LOOP_DUMMY) declare(parameter, statement); + Consumer<Parameter> define = (parameter) -> { + parameter.setInStaticContext(currentScope.isInStaticContext()); + declare(parameter, statement); + }; + Optional.ofNullable(statement.getIndexVariable()).ifPresent(define); + Optional.ofNullable(statement.getValueVariable()).ifPresent(define); super.visitForLoop(statement); popState(); } diff --git a/src/main/java/org/codehaus/groovy/classgen/VerifierCodeVisitor.java b/src/main/java/org/codehaus/groovy/classgen/VerifierCodeVisitor.java index 4fa7dcc442..be87099c6a 100644 --- a/src/main/java/org/codehaus/groovy/classgen/VerifierCodeVisitor.java +++ b/src/main/java/org/codehaus/groovy/classgen/VerifierCodeVisitor.java @@ -21,6 +21,7 @@ package org.codehaus.groovy.classgen; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; @@ -30,6 +31,8 @@ import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.syntax.RuntimeParserException; +import java.util.Optional; + /** * Performs various checks on code inside methods and constructors * including checking for valid field, variables names etc. that @@ -44,9 +47,12 @@ public class VerifierCodeVisitor extends CodeVisitorSupport { } @Override - public void visitForLoop(ForStatement expression) { - assertValidIdentifier(expression.getVariable().getName(), "for loop variable name", expression); - super.visitForLoop(expression); + public void visitForLoop(ForStatement statement) { + Optional.ofNullable(statement.getIndexVariable()).map(Variable::getName) + .ifPresent(name -> assertValidIdentifier(name, "for loop index variable name", statement)); + Optional.ofNullable(statement.getValueVariable()).map(Variable::getName) + .ifPresent(name -> assertValidIdentifier(name, "for loop value variable name", statement)); + super.visitForLoop(statement); } @Override diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java index 710e8cf747..3a06162e0c 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java @@ -93,7 +93,7 @@ public class StatementWriter { } public void writeForStatement(final ForStatement statement) { - if (statement.getVariable() == ForStatement.FOR_LOOP_DUMMY) { + if (statement.getCollectionExpression() instanceof ClosureListExpression) { writeForLoopWithClosureList(statement); } else { writeForInLoop(statement); @@ -122,8 +122,12 @@ public class StatementWriter { MethodVisitor mv = controller.getMethodVisitor(); OperandStack operandStack = controller.getOperandStack(); - // declare the loop counter - BytecodeVariable variable = compileStack.defineVariable(statement.getVariable(), false); + // declare the loop index and value variables + BytecodeVariable indexVariable = Optional.ofNullable(statement.getIndexVariable()).map(iv -> { + mv.visitInsn(ICONST_M1); // initialize to -1 so increment can pair with next() + return compileStack.defineVariable(iv, true); + }).orElse(null); + BytecodeVariable valueVariable = compileStack.defineVariable(statement.getValueVariable(), false); // get the iterator and generate the loop control int iterator = compileStack.defineTemporaryVariable("iterator", ClassHelper.Iterator_TYPE, true); @@ -141,7 +145,10 @@ public class StatementWriter { mv.visitVarInsn(ALOAD, iterator); writeIteratorNext(mv); operandStack.push(ClassHelper.OBJECT_TYPE); - operandStack.storeVar(variable); + operandStack.storeVar(valueVariable); + if (indexVariable != null) { + mv.visitIincInsn(indexVariable.getIndex(), 1); + } // generate the loop body statement.getLoopBlock().visit(controller.getAcg()); diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java index b05c90dffc..d9fdd428ae 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java @@ -38,6 +38,7 @@ import org.objectweb.asm.MethodVisitor; import java.util.Enumeration; import java.util.Objects; +import java.util.Optional; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.ALOAD; @@ -50,6 +51,7 @@ import static org.objectweb.asm.Opcodes.FALOAD; import static org.objectweb.asm.Opcodes.GOTO; import static org.objectweb.asm.Opcodes.IALOAD; import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_M1; import static org.objectweb.asm.Opcodes.IFEQ; import static org.objectweb.asm.Opcodes.IFNULL; import static org.objectweb.asm.Opcodes.IF_ICMPGE; @@ -95,9 +97,8 @@ public class StaticTypesStatementWriter extends StatementWriter { ClassNode collectionType = controller.getTypeChooser().resolveType(collectionExpression, controller.getClassNode()); int mark = operandStack.getStackLength(); - Parameter loopVariable = loop.getVariable(); - if (collectionType.isArray() && loopVariable.getType().equals(collectionType.getComponentType())) { - writeOptimizedForEachLoop(loop, loopVariable, collectionExpression, collectionType); + if (collectionType.isArray() && collectionType.getComponentType().equals(loop.getVariableType())) { + writeOptimizedForEachLoop(loop, collectionExpression, collectionType); } else if (GeneralUtils.isOrImplements(collectionType, ENUMERATION_CLASSNODE)) { writeEnumerationBasedForEachLoop(loop, collectionExpression, collectionType); } else { @@ -107,13 +108,16 @@ public class StaticTypesStatementWriter extends StatementWriter { compileStack.pop(); } - private void writeOptimizedForEachLoop(final ForStatement loop, final Parameter loopVariable, final Expression arrayExpression, final ClassNode arrayType) { + private void writeOptimizedForEachLoop(final ForStatement loop, final Expression arrayExpression, final ClassNode arrayType) { CompileStack compileStack = controller.getCompileStack(); OperandStack operandStack = controller.getOperandStack(); MethodVisitor mv = controller.getMethodVisitor(); AsmClassGenerator acg = controller.getAcg(); - BytecodeVariable variable = compileStack.defineVariable(loopVariable, arrayType.getComponentType(), false); + BytecodeVariable indexVariable = Optional.ofNullable(loop.getIndexVariable()).map(iv -> { + mv.visitInsn(ICONST_M1); return compileStack.defineVariable(iv, true); + }).orElse(null); + BytecodeVariable valueVariable = compileStack.defineVariable(loop.getValueVariable(), arrayType.getComponentType(), false); Label continueLabel = compileStack.getContinueLabel(); Label breakLabel = compileStack.getBreakLabel(); @@ -141,10 +145,13 @@ public class StaticTypesStatementWriter extends StatementWriter { mv.visitJumpInsn(IF_ICMPGE, breakLabel); // get array element - loadFromArray(mv, operandStack, variable, array, loopIdx); + loadFromArray(mv, operandStack, valueVariable, array, loopIdx); // $idx += 1 mv.visitIincInsn(loopIdx, 1); + if (indexVariable != null) { + mv.visitIincInsn(indexVariable.getIndex(), 1); + } // loop body loop.getLoopBlock().visit(acg); @@ -190,7 +197,10 @@ public class StaticTypesStatementWriter extends StatementWriter { OperandStack operandStack = controller.getOperandStack(); MethodVisitor mv = controller.getMethodVisitor(); - BytecodeVariable variable = compileStack.defineVariable(loop.getVariable(), false); + BytecodeVariable indexVariable = Optional.ofNullable(loop.getIndexVariable()).map(iv -> { + mv.visitInsn(ICONST_M1); return compileStack.defineVariable(iv, true); + }).orElse(null); + BytecodeVariable valueVariable = compileStack.defineVariable(loop.getValueVariable(), false); Label continueLabel = compileStack.getContinueLabel(); Label breakLabel = compileStack.getBreakLabel(); @@ -210,7 +220,10 @@ public class StaticTypesStatementWriter extends StatementWriter { mv.visitVarInsn(ALOAD, enumeration); ENUMERATION_NEXT_METHOD.call(mv); operandStack.push(ClassHelper.OBJECT_TYPE); - operandStack.storeVar(variable); + operandStack.storeVar(valueVariable); + if (indexVariable != null) { + mv.visitIincInsn(indexVariable.getIndex(), 1); + } loop.getLoopBlock().visit(controller.getAcg()); mv.visitJumpInsn(GOTO, continueLabel); diff --git a/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java index 72662fa825..30249a9a97 100644 --- a/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java +++ b/src/main/java/org/codehaus/groovy/transform/CategoryASTTransformation.java @@ -221,7 +221,11 @@ public class CategoryASTTransformation implements ASTTransformation { public void visitForLoop(final ForStatement statement) { Expression exp = statement.getCollectionExpression(); exp.visit(this); - Parameter loopParam = statement.getVariable(); + Parameter loopParam = statement.getIndexVariable(); + if (loopParam != null) { + varStack.getLast().add(loopParam.getName()); + } + loopParam = statement.getValueVariable(); if (loopParam != null) { varStack.getLast().add(loopParam.getName()); } diff --git a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java index 893f836a3f..0c806eb4ce 100644 --- a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java @@ -257,9 +257,10 @@ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor { super.visitForLoop(statement); var collectionExpression = statement.getCollectionExpression(); if (!(collectionExpression instanceof ClosureListExpression)) { - if (statement.getVariable().isDynamicTyped()) { // GROOVY-8169 - ClassNode inferredType = getType(statement.getVariable()); - statement.getVariable().setType(inferredType); // GROOVY-5640, GROOVY-5641 + var valueVariable = statement.getValueVariable(); + if (valueVariable.isDynamicTyped()) { // GROOVY-8169 + ClassNode inferredType = getType(valueVariable); + valueVariable.setType(inferredType); // GROOVY-5640, GROOVY-5641 } } } diff --git a/src/spec/test/SemanticsTest.groovy b/src/spec/test/SemanticsTest.groovy index da6a4ca13f..ff2e2b5355 100644 --- a/src/spec/test/SemanticsTest.groovy +++ b/src/spec/test/SemanticsTest.groovy @@ -245,6 +245,11 @@ final class SemanticsTest { list.add(c) } assert list == ['a', 'b', 'c'] + + // iterate with index + for ( int i, k in map.keySet() ) { + assert map.get(k) == i + 1 + } // end::groovy_for_loop_example[] } diff --git a/src/test/groovy/ForLoopTest.groovy b/src/test/groovy/ForLoopTest.groovy index 5608a553fb..90b3009b03 100644 --- a/src/test/groovy/ForLoopTest.groovy +++ b/src/test/groovy/ForLoopTest.groovy @@ -18,6 +18,8 @@ */ package groovy +import groovy.transform.CompileStatic + import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource @@ -125,6 +127,26 @@ final class ForLoopTest { assert answer == [1, 2, 3] } + // GROOVY-10683 + @Test @CompileStatic + void testForEachWithIndex() { + for (int i, var v in [0, 1, 2, 3]) { + assert i == v + continue + } + for (int i, var v in new int[]{0, 1, 2, 3}) { + assert i == v + continue + } + for (int i, var v in Collections.enumeration([0, 1, 2, 3])) { + assert i == v + continue + } + for (int i, def ts in Thread.State) { + assert i == ((Thread.State) ts).ordinal() // GROOVY-11597 + } + } + @Test void testClassicFor() { for (int i = 0; i < 10; i++) { 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 48adb6a442..5e9a161599 100644 --- a/src/test/org/apache/groovy/parser/antlr4/util/AstDumper.groovy +++ b/src/test/org/apache/groovy/parser/antlr4/util/AstDumper.groovy @@ -533,8 +533,12 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati void visitForLoop(ForStatement statement) { printStatementLabels(statement) print 'for (' - if (statement?.variable != ForStatement.FOR_LOOP_DUMMY) { - visitParameters statement.variable + if (statement?.indexVariable) { + visitParameters statement.indexVariable + print ', ' + } + if (statement?.valueVariable) { + visitParameters statement.valueVariable print ' : ' } 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 e83dbcdfe7..fc6f2ab327 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 @@ -571,20 +571,19 @@ class AstNodeToScriptVisitor implements CompilationUnit.IPrimaryClassNodeOperati void visitForLoop(ForStatement statement) { printStatementLabels(statement) print 'for (' - if (statement?.variable != ForStatement.FOR_LOOP_DUMMY) { - visitParameters statement.variable + if (statement.valueVariable) { + if (statement.indexVariable) { + visitParameters(statement.indexVariable) + print ', ' + } + visitParameters(statement.valueVariable) print ' : ' } - - if (statement?.collectionExpression instanceof ListExpression) { - statement?.collectionExpression?.visit this - } else { - statement?.collectionExpression?.visit this - } + statement.collectionExpression.visit(this) print ') {' printLineBreak() indented { - statement?.loopBlock?.visit this + statement.loopBlock.visit(this) } print '}' printLineBreak()
