[ 
https://issues.apache.org/jira/browse/GROOVY-12078?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18087805#comment-18087805
 ] 

ASF GitHub Bot commented on GROOVY-12078:
-----------------------------------------

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


##########
subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java:
##########
@@ -141,29 +149,64 @@ private void addPostcondition(MethodNode method, 
BlockStatement postconditionBlo
                     localPostconditionBlockStatement.getStatements().add(0, 
declS(result, returnStatement.getExpression()));
                     
AssertStatementCreationUtility.injectResultVariableReturnStatementAndAssertionCallStatement(block,
 method.getReturnType().redirect(), returnStatement, 
localPostconditionBlockStatement);
                 }
-                setOldVariablesIfEnabled(block, contractsEnabled, 
method.isStatic());
+                setOldVariablesIfEnabled(block, contractsEnabled, method);
 
             } else if (method instanceof ConstructorNode) {
                 
block.addStatements(postconditionBlockStatement.getStatements());
             } else {
-                setOldVariablesIfEnabled(block, contractsEnabled, 
method.isStatic());
+                setOldVariablesIfEnabled(block, contractsEnabled, method);
                 
block.addStatements(postconditionBlockStatement.getStatements());
             }
             method.putNodeMetaData(METHOD_PROCESSED, true);
         }
     }
 
-    private void setOldVariablesIfEnabled(BlockStatement block, Expression 
contractsEnabled, boolean isStatic) {
+    private void setOldVariablesIfEnabled(BlockStatement block, Expression 
contractsEnabled, MethodNode method) {
         final Expression oldVariableExpression = localVarX("old", new 
ClassNode(Map.class));
-        if (isStatic) {
+        if (method.isStatic()) {
             // static methods have no instance state to snapshot; the 
synthetic old-variables method is an
-            // instance method, so 'old' is simply an empty map (references to 
it are rejected at compile time)
+            // instance method, so 'old' starts as an empty map. GROOVY-12078: 
it is still populated with
+            // the entry values of any parameters referenced via old.<name> 
(field references stay rejected)
             block.getStatements().add(0, declS(oldVariableExpression, mapX()));
+            List<Statement> parameterSnapshots = 
parameterSnapshotStatements(method);
+            if (!parameterSnapshots.isEmpty()) {
+                block.getStatements().add(1, ifS(boolX(contractsEnabled), 
block(new VariableScope(), parameterSnapshots)));
+            }
             return;
         }
-        // Assign the return statement expression to a local variable: Map old
-        Statement oldVariableStatement = assignS(oldVariableExpression, 
callThisX(OldVariableGenerationUtility.OLD_VARIABLES_METHOD));
+        // Snapshot instance field state into a local variable: Map old = 
$_gc_computeOldVariables()
+        final List<Statement> oldSetup = new ArrayList<>();
+        oldSetup.add(assignS(oldVariableExpression, 
callThisX(OldVariableGenerationUtility.OLD_VARIABLES_METHOD)));
+        // GROOVY-12078: also snapshot the entry values of parameters 
referenced via old.<name>, so a
+        // postcondition can compare against an argument that the method body 
reassigns
+        oldSetup.addAll(parameterSnapshotStatements(method));
+
         block.getStatements().add(0, declS(oldVariableExpression, 
ConstantExpression.NULL));
-        block.getStatements().add(1, ifS(boolX(contractsEnabled), 
block(oldVariableStatement)));
+        block.getStatements().add(1, ifS(boolX(contractsEnabled), block(new 
VariableScope(), oldSetup)));
+    }
+
+    /**
+     * Builds {@code old.put('name', name)} statements for each method 
parameter referenced via
+     * {@code old.<name>} in the postcondition. Parameters are snapshotted 
after the field state so a
+     * parameter shadowing a field name wins, mirroring lexical scoping. 
{@code final} (and {@code val})
+     * parameters are snapshotted too: {@code final} prevents reassignment but 
not in-place mutation, so
+     * {@code old.<name>} must still capture the entry state.
+     *
+     * @param method the method whose postcondition is being generated
+     * @return the list of snapshot statements (possibly empty)
+     */
+    private static List<Statement> parameterSnapshotStatements(final 
MethodNode method) {
+        final Set<String> oldReferences = 
method.getNodeMetaData(AnnotationClosureVisitor.OLD_REFERENCES_KEY);
+        if (oldReferences == null || oldReferences.isEmpty()) return List.of();
+
+        final List<Statement> statements = new ArrayList<>();
+        for (Parameter parameter : method.getParameters()) {
+            if (!oldReferences.contains(parameter.getName())) continue;
+            // old.put('name', name) - defensively copying a mutable value so 
an in-place mutation
+            // in the body does not also change the snapshot

Review Comment:
   The comment claims the snapshot is always a defensive copy, but 
`snapshotExpression(...)` only clones for cloneable value-like types; other 
types are stored by reference. Updating the comment avoids overstating the 
guarantee and matches the actual behavior.



##########
subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/OldVariableGenerationUtility.java:
##########
@@ -126,4 +122,49 @@ public static void addOldVariableMethodNode(final 
ClassNode classNode) {
         preconditionMethodNode.setSynthetic(true);
 
     }
+
+    /**
+     * Returns an expression that snapshots {@code value} (of declared type 
{@code type}) for storage in
+     * the {@code old} map: a null-safe {@code clone()} call for the cloneable 
value-like types, otherwise
+     * the value reference itself. Cloning the same set of types as the 
instance-field snapshot means an
+     * object that the method body mutates in place is still observed in its 
pre-call state via {@code old}.
+     *
+     * @param type  the declared type of the value being snapshotted
+     * @param value the expression producing the value to snapshot
+     * @return the snapshot expression
+     */
+    public static Expression snapshotExpression(final ClassNode type, final 
Expression value) {
+        final ClassNode wrapperType = ClassHelper.getWrapper(type);
+        if (isOldSnapshotType(wrapperType) && isCloneable(wrapperType)) {
+            final MethodCallExpression clone = callX(value, "clone");
+            clone.setSafe(true); // leave a null value as null
+            return clone;
+        }
+        return value;
+    }
+
+    /**
+     * Reports whether the (wrapper) type participates in {@code old} 
snapshotting: the value-like JDK
+     * types ({@code java.lang} / {@code java.math} / {@code java.util} / 
{@code java.sql}), {@link String},
+     * {@link groovy.lang.GString} and the primitives.
+     */
+    private static boolean isOldSnapshotType(final ClassNode wrapperType) {
+        final String name = wrapperType.getName();
+        return name.startsWith("java.lang")
+                || ClassHelper.isPrimitiveType(wrapperType)
+                || name.startsWith("java.math")
+                || name.startsWith("java.util")
+                || name.startsWith("java.sql")
+                || "groovy.lang.GString".equals(name)
+                || "java.lang.String".equals(name);

Review Comment:
   `name.startsWith("java.lang")` already covers `java.lang.String`, so the 
explicit `"java.lang.String".equals(name)` check is redundant and can be 
removed to reduce noise.





> support old parameter values in @Ensures postconditions (incl. static methods)
> ------------------------------------------------------------------------------
>
>                 Key: GROOVY-12078
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12078
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Priority: Major
>




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to