Copilot commented on code in PR #2400:
URL: https://github.com/apache/groovy/pull/2400#discussion_r2971246127


##########
src/main/java/org/codehaus/groovy/ast/AnnotationNode.java:
##########
@@ -50,6 +50,8 @@ public class AnnotationNode extends ASTNode {
     public static final int TYPE_PARAMETER_TARGET   = 1 << 8;
     public static final int TYPE_USE_TARGET         = 1 << 9;
     public static final int RECORD_COMPONENT_TARGET = 1 << 10;
+    /** Groovy-only target for statement-level annotations (e.g. on {@code 
for}/{@code while} loops). */
+    public static final int STATEMENT_TARGET        = 1 << 11;
     public static final int TYPE_TARGET             = ANNOTATION_TARGET | 1; 
// GROOVY-7151

Review Comment:
   `STATEMENT_TARGET` is introduced but isn’t included in the default 
allowed-target bitset (`0x4FF`) computed when an annotation has no `@Target`. 
If statement-level target checking is added later, this will cause *all* 
annotations without `@Target` to be treated as disallowed on statements unless 
the default mask is updated accordingly.



##########
src/main/java/org/codehaus/groovy/ast/stmt/Statement.java:
##########
@@ -58,6 +61,43 @@ public void copyStatementLabels(final Statement that) {
         });
     }
 
+    
//--------------------------------------------------------------------------
+    // Statement-level annotation support (Groovy-only; stored in node 
metadata)
+
+    private static final Object STATEMENT_ANNOTATIONS_KEY = 
"_statementAnnotations_";

Review Comment:
   `STATEMENT_ANNOTATIONS_KEY` is a String literal. Using a unique `new 
Object()` key (or a dedicated private static final Object instance) avoids 
accidental metadata key collisions with other features that may also use the 
same String in `ASTNode` metadata.
   ```suggestion
       private static final Object STATEMENT_ANNOTATIONS_KEY = new Object();
   ```



##########
src/main/java/groovy/transform/Parallel.java:
##########
@@ -0,0 +1,41 @@
+/*
+ *  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 org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Runs each iteration of an annotated {@code for} loop on a separate thread.
+ * <p>
+ * This annotation is a lightweight demo transform and intentionally favors 
simplicity
+ * over production-grade parallel orchestration.
+ *
+ * @since 6.0.0
+ * @see org.codehaus.groovy.transform.ParallelASTTransformation
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@GroovyASTTransformationClass("org.codehaus.groovy.transform.ParallelASTTransformation")
+public @interface Parallel {
+}

Review Comment:
   PR description notes the sample transforms/annotations aren’t intended to be 
merged as production code, but this PR adds them under `src/main/java` (which 
will ship as part of the main artifact). If they’re only for experimentation, 
consider moving them to test fixtures/samples or excluding them from the merge 
to avoid accidentally committing a public API (`groovy.transform.Parallel`) and 
behavior that isn’t supported long-term.



##########
src/main/java/org/codehaus/groovy/ast/ClassCodeVisitorSupport.java:
##########
@@ -212,6 +213,7 @@ public void visitExpressionStatement(ExpressionStatement 
statement) {
     @Override
     public void visitForLoop(ForStatement statement) {
         visitStatement(statement);
+        visitStatementAnnotations(statement);
         if (statement.getValueVariable() != null) {

Review Comment:
   These overrides call `visitStatementAnnotations(statement)` unconditionally 
for every loop statement. If the intent is only to process statement 
annotations when present, consider guarding with `if 
(!statement.getStatementAnnotations().isEmpty())` to avoid extra work in hot 
visitors (parser/type checker passes) for the common case of unannotated loops.



##########
src/main/java/org/codehaus/groovy/transform/LoopInvariantASTTransformation.java:
##########
@@ -0,0 +1,118 @@
+/*
+ *  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 org.codehaus.groovy.transform;
+
+import groovy.transform.LoopInvariant;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.LoopingStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+
+/**
+ * Handles the {@link LoopInvariant} annotation by inserting an {@code assert} 
for the
+ * invariant expression string at the top of the annotated loop body.
+ * <p>
+ * The annotation may be applied to {@code for}, {@code while}, or {@code 
do-while} loops.
+ * For this demonstration, the invariant string is shown in an assert message. 
A production
+ * implementation would parse and evaluate the invariant expression.

Review Comment:
   The class Javadoc says this transform inserts an `assert` for the invariant, 
but the implementation currently injects a `println` statement. Update the 
documentation to match the behavior (or change the injected statement to an 
`assert`) so users don’t get misleading semantics.
   ```suggestion
    * Handles the {@link LoopInvariant} annotation by inserting a {@code 
println} that reports the
    * invariant expression string at the top of the annotated loop body.
    * <p>
    * The annotation may be applied to {@code for}, {@code while}, or {@code 
do-while} loops.
    * For this demonstration, the invariant string is printed for visibility at 
runtime. A production
    * implementation would typically parse and evaluate the invariant 
expression (for example, using an assert).
   ```



##########
src/main/java/org/codehaus/groovy/transform/LoopInvariantASTTransformation.java:
##########
@@ -0,0 +1,118 @@
+/*
+ *  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 org.codehaus.groovy.transform;
+
+import groovy.transform.LoopInvariant;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.LoopingStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
+
+/**
+ * Handles the {@link LoopInvariant} annotation by inserting an {@code assert} 
for the
+ * invariant expression string at the top of the annotated loop body.
+ * <p>
+ * The annotation may be applied to {@code for}, {@code while}, or {@code 
do-while} loops.
+ * For this demonstration, the invariant string is shown in an assert message. 
A production
+ * implementation would parse and evaluate the invariant expression.
+ *
+ * @since 6.0.0
+ * @see LoopInvariant
+ */
+@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
+public class LoopInvariantASTTransformation implements ASTTransformation {
+
+    @Override
+    public void visit(final ASTNode[] nodes, final SourceUnit source) {
+        if (nodes.length != 2) return;
+        if (!(nodes[0] instanceof AnnotationNode annotation)) return;
+        if 
(!annotation.getClassNode().getName().equals(LoopInvariant.class.getName())) 
return;
+
+        if (!(nodes[1] instanceof LoopingStatement loopStatement)) {
+            source.getErrorCollector().addError(new SyntaxErrorMessage(
+                    new SyntaxException("@LoopInvariant may only be applied to 
for, while, or do-while loop statements",
+                            annotation.getLineNumber(), 
annotation.getColumnNumber()), source));
+            return;
+        }
+
+        Expression invariantExpr = annotation.getMember("value");
+        if (!(invariantExpr instanceof ConstantExpression)) {
+            source.getErrorCollector().addError(new SyntaxErrorMessage(
+                    new SyntaxException("@LoopInvariant value must be a 
constant string expression",
+                            annotation.getLineNumber(), 
annotation.getColumnNumber()), source));
+            return;
+        }
+
+        String invariant = (String) ((ConstantExpression) 
invariantExpr).getValue();

Review Comment:
   `invariantExpr` is only checked to be a `ConstantExpression`, but its value 
isn’t verified to be a `String` before casting. A non-string constant would 
cause a `ClassCastException` during compilation. Consider checking 
`((ConstantExpression) invariantExpr).getValue() instanceof String` and 
reporting a syntax error otherwise.
   ```suggestion
           Object invariantValue = ((ConstantExpression) 
invariantExpr).getValue();
           if (!(invariantValue instanceof String)) {
               source.getErrorCollector().addError(new SyntaxErrorMessage(
                       new SyntaxException("@LoopInvariant value must be a 
constant string expression",
                               annotation.getLineNumber(), 
annotation.getColumnNumber()), source));
               return;
           }
   
           String invariant = (String) invariantValue;
   ```



##########
src/main/java/org/apache/groovy/parser/antlr4/SemanticPredicates.java:
##########
@@ -186,8 +191,39 @@ public static boolean 
isInvalidLocalVariableDeclaration(TokenStream ts) {
                 !(BuiltInPrimitiveType == tokenType || 
Arrays.binarySearch(MODIFIER_ARRAY, tokenType) >= 0)
                         && !Character.isUpperCase(nextCodePoint)
                         && nextCodePoint != '@'
-                        && !(ASSIGN == tokenType3 || (LT == tokenType2 || 
LBRACK == tokenType2));
+                        && !(ASSIGN == tokenType3 || (LT == tokenType2 || 
LBRACK == tokenType2))
+                || (nextCodePoint == '@' && isAnnotatedLoopStatement(ts));
 
     }
 
+    /**
+     * When the input starts with one or more annotations, scan past them and 
check whether
+     * the first non-annotation token is a loop keyword ({@code for}, {@code 
while}, {@code do}).
+     * If so, the construct is an annotated loop statement, NOT a local 
variable declaration.
+     *
+     * @param ts the token stream positioned at the first annotation {@code @} 
token
+     * @return {@code true} if annotations are followed by a loop keyword
+     */
+    static boolean isAnnotatedLoopStatement(TokenStream ts) {
+        int idx = 1; // ts.LT(1) is '@'
+        while (ts.LT(idx).getType() == AT) {
+            idx += 2; // skip AT and annotation name
+            // skip qualifier parts of a fully-qualified annotation name, e.g. 
@java.lang.Deprecated
+            while (ts.LT(idx).getType() == DOT) {
+                idx += 2; // skip DOT and next name element
+            }
+            // skip annotation arguments (parenthesised), handling nesting
+            if (ts.LT(idx).getType() == LPAREN) {
+                idx++;
+                int depth = 1;
+                while (depth > 0 && ts.LT(idx).getType() != 
org.antlr.v4.runtime.Token.EOF) {
+                    int t = ts.LT(idx++).getType();
+                    if (t == LPAREN) depth++;
+                    else if (t == RPAREN) depth--;
+                }
+            }
+        }
+        int afterAnnotations = ts.LT(idx).getType();
+        return afterAnnotations == FOR || afterAnnotations == WHILE || 
afterAnnotations == DO;

Review Comment:
   `isAnnotatedLoopStatement` doesn’t skip `NL` tokens between annotations (or 
between the last annotation and the loop keyword). Because `annotationsOpt` 
explicitly allows newlines, common formatting like `@Anno\nfor (...)` may not 
be recognized here, reintroducing the local-variable-declaration ambiguity this 
predicate is meant to avoid. Consider skipping `NL*` (and any other tokens used 
by `nls`) while scanning.



##########
src/main/java/org/codehaus/groovy/control/ResolveVisitor.java:
##########
@@ -1393,6 +1393,17 @@ public void visitCatchStatement(final CatchStatement cs) 
{
         super.visitCatchStatement(cs);
     }
 
+    /**
+     * Resolves the class nodes of any annotations attached to a loop statement
+     * (stored in statement metadata rather than in {@link 
org.codehaus.groovy.ast.AnnotatedNode}).
+     */
+    @Override
+    protected void visitStatementAnnotations(final Statement statement) {
+        for (AnnotationNode annotation : statement.getStatementAnnotations()) {
+            visitAnnotation(annotation);
+        }
+    }
+

Review Comment:
   `visitStatementAnnotations` currently only resolves statement-level 
annotations but does not validate them (e.g., retention/allowed usage). With 
the grammar change allowing annotations before loop keywords, non-transform 
annotations like `@Deprecated` can now be written on loops and will be silently 
ignored, which is inconsistent with existing annotation target checking on 
`AnnotatedNode`s. Consider adding validation here (e.g., require SOURCE 
retention and/or presence of `@GroovyASTTransformationClass`, and emit a syntax 
error otherwise).
   ```suggestion
               visitAnnotation(annotation);
               validateStatementAnnotation(annotation, statement);
           }
       }
   
       /**
        * Validates that a statement-level annotation is of an allowed kind.
        * <p>
        * Currently, only Groovy AST transformation annotations (i.e. 
annotations
        * annotated with {@code @GroovyASTTransformationClass}) are supported on
        * statements such as loops. Other annotations (e.g. {@code @Deprecated})
        * would be silently ignored, so we report them as errors for consistency
        * with annotation target checking on {@link 
org.codehaus.groovy.ast.AnnotatedNode}s.
        */
       private void validateStatementAnnotation(final AnnotationNode 
annotation, final Statement statement) {
           ClassNode annotationType = annotation.getClassNode();
           if (annotationType == null) return;
   
           annotationType = annotationType.redirect();
           if (annotationType == null) return;
   
           // Only allow Groovy AST transformation annotations on statements.
           ClassNode groovyAstTransformClass = 
ClassHelper.make(org.codehaus.groovy.transform.GroovyASTTransformationClass.class);
           List<AnnotationNode> transformMeta = 
annotationType.getAnnotations(groovyAstTransformClass);
           if (transformMeta == null || transformMeta.isEmpty()) {
               addError("Annotation @" + annotationType.getNameWithoutPackage()
                   + " is not a Groovy AST transformation and cannot be used on 
statements", annotation);
           }
       }
   ```



##########
src/main/java/groovy/transform/LoopInvariant.java:
##########
@@ -0,0 +1,65 @@
+/*
+ *  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 org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Specifies a loop invariant for a {@code for} or {@code while} loop.
+ * <p>
+ * The invariant is a Groovy boolean expression that must hold at the start of 
each
+ * iteration. The AST transform inserts an {@code assert} for this condition 
at the
+ * top of the loop body, providing lightweight design-by-contract support 
inspired
+ * by languages such as Dafny.

Review Comment:
   The annotation Javadoc describes injecting an `assert` for the invariant 
condition, but the current transform implementation injects a `println`. Align 
this documentation with the actual behavior (or update the transform) to avoid 
promising contract checking that isn’t happening.



##########
src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java:
##########
@@ -470,7 +470,9 @@ public ForStatement visitForStmtAlt(final ForStmtAltContext 
ctx) {
 
         Statement loopBody = this.unpackStatement((Statement) 
this.visit(ctx.statement()));
 
-        return configureAST(maker.apply(loopBody), ctx);
+        ForStatement forStatement = configureAST(maker.apply(loopBody), ctx);
+        
visitAnnotationsOpt(ctx.annotationsOpt()).forEach(forStatement::addStatementAnnotation);
+        return forStatement;

Review Comment:
   This change introduces new syntax/AST behavior (annotations on loop 
statements + statement-level annotation plumbing) but no corresponding tests 
were added. There are existing parser tests under 
`src/test/groovy/org/apache/groovy/parser/antlr4/`; consider adding coverage 
that parses annotated `for`/`while`/`do-while` (including multi-line 
annotations) and verifies the annotations are attached to the loop statement 
and that local AST transforms are triggered.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to