Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/antlr/GroovyParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,7 @@ statement
| tryCatchStatement #tryCatchStmtAlt
| SYNCHRONIZED expressionInPar nls block #synchronizedStmtAlt
| RETURN expression? #returnStmtAlt
| RETURN AT identifier expression? #returnAtStmtAlt
| THROW expression #throwStmtAlt
| breakStatement #breakStmtAlt
| continueStatement #continueStmtAlt
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/groovy/transform/SupportsLoopControl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.transform;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks an iterator-style method as cooperating with the loop-control protocol
* (GROOVY-12126): {@code break} and {@code continue} may be used inside a
* closure passed directly as an argument to the method, with these semantics:
* <ul>
* <li>value-ignoring iterators (e.g. {@code each}, {@code times}, {@code upto}):
* {@code break} stops the iteration, {@code continue} proceeds to the next
* element;</li>
* <li>value-consuming iterators (e.g. {@code collect}, {@code findAll},
* {@code inject}): {@code break} stops the iteration excluding the current
* element's contribution, {@code continue} skips the current element's
* contribution.</li>
* </ul>
* Cooperating methods catch the compiler-internal
* {@link org.codehaus.groovy.runtime.LoopControl} signals around each
* per-element closure invocation. A signal thrown inside a closure passed to a
* method without this annotation propagates to the caller.
*
* @since 6.0.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SupportsLoopControl {
}
14 changes: 14 additions & 0 deletions src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,20 @@ public ReturnStatement visitReturnStmtAlt(final ReturnStmtAltContext ctx) {
ctx);
}

@Override
public ReturnStatement visitReturnAtStmtAlt(final ReturnAtStmtAltContext ctx) {
if (switchExpressionRuleContextStack.peek() instanceof SwitchExpressionContext) {
throw createParsingFailedException("switch expression does not support `return`", ctx);
}

ReturnStatement returnStatement = new ReturnStatement(asBoolean(ctx.expression())
? (Expression) this.visit(ctx.expression())
: ConstantExpression.EMPTY_EXPRESSION);
returnStatement.setTarget(this.visitIdentifier(ctx.identifier()));

return configureAST(returnStatement, ctx);
}

@Override
public ThrowStatement visitThrowStmtAlt(final ThrowStmtAltContext ctx) {
return configureAST(
Expand Down
30 changes: 29 additions & 1 deletion src/main/java/org/codehaus/groovy/ast/stmt/ReturnStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public class ReturnStatement extends Statement {

private Expression expression;

/**
* The optional non-local return target, i.e. the {@code name} in {@code return@name expr}.
* Null for an ordinary return.
*
* @since 6.0.0
*/
private String target;

/**
* Constructs a return statement from an expression statement by extracting its expression.
*
Expand Down Expand Up @@ -79,9 +87,29 @@ public void setExpression(final Expression expression) {
this.expression = expression;
}

/**
* Returns the non-local return target for a {@code return@name expr} statement.
*
* @return the name of the enclosing method targeted by this return, or null for an ordinary return
* @since 6.0.0
*/
public String getTarget() {
return target;
}

/**
* Sets the non-local return target, i.e. the {@code name} in {@code return@name expr}.
*
* @param target the name of the enclosing method targeted by this return
* @since 6.0.0
*/
public void setTarget(final String target) {
this.target = target;
}

@Override
public String getText() {
return "return " + expression.getText();
return "return" + (target != null ? "@" + target : "") + " " + expression.getText();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ private void addPhaseOperations() {
visitor.visitClass(classNode);
}, Phases.CANONICALIZATION);

addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
new NonLocalControlFlowRewriter(source).rewrite(classNode);
}, Phases.CANONICALIZATION);

addPhaseOperation((final SourceUnit source, final GeneratorContext context, final ClassNode classNode) -> {
Object callback = classNode.getNodeMetaData(DYNAMIC_OUTER_NODE_CALLBACK);
if (callback instanceof IPrimaryClassNodeOperation) {
Expand Down
Loading
Loading