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

paulk-asert 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 4a4986e385 GROOVY-12052: groovy-contracts @Ensures could support 
static methods
4a4986e385 is described below

commit 4a4986e3854e9ec8c7be001a3b4066197cf6e832
Author: Paul King <[email protected]>
AuthorDate: Sun May 31 10:41:08 2026 +1000

    GROOVY-12052: groovy-contracts @Ensures could support static methods
---
 .../ast/visitor/AnnotationClosureVisitor.java      |  7 +++++
 .../contracts/generation/CandidateChecks.java      |  1 -
 .../generation/PostconditionGenerator.java         | 15 +++++++---
 .../other/ClosureExpressionValidationTests.groovy  | 20 +++++++++++++
 .../tests/post/SimplePostconditionTests.groovy     | 33 ++++++++++++++++++++--
 5 files changed, 68 insertions(+), 8 deletions(-)

diff --git 
a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java
 
b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java
index f1061ff271..6bff20d7b2 100644
--- 
a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java
+++ 
b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/ast/visitor/AnnotationClosureVisitor.java
@@ -484,6 +484,13 @@ public class AnnotationClosureVisitor extends BaseVisitor 
implements ASTNodeMeta
         @Override
         public void visitVariableExpression(VariableExpression expression) {
 
+            // GROOVY-12052: 'old' snapshots instance state, which a static 
method has none of
+            if (!secondPass && methodNode != null && methodNode.isStatic()
+                    && "old".equals(expression.getName())
+                    && 
AnnotationUtils.hasAnnotationOfType(annotationNode.getClassNode(), 
POSTCONDITION_TYPE_NAME)) {
+                addError("[groovy-contracts] 'old' is not supported in 
postconditions of static methods.", expression);
+            }
+
             // in case of a FieldNode, checks whether the FieldNode can be 
replaced with a Parameter
             Variable accessedVariable = 
getParameterCandidate(expression.getAccessedVariable());
             if (accessedVariable instanceof FieldNode fieldNode) {
diff --git 
a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java
 
b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java
index 50ae38fca6..bba5ed0792 100644
--- 
a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java
+++ 
b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/CandidateChecks.java
@@ -131,7 +131,6 @@ public class CandidateChecks {
      */
     public static boolean isPostconditionCandidate(final ClassNode type, final 
MethodNode method) {
         if (!isPreconditionCandidate(type, method)) return false;
-        if (method.isStatic()) return false;
 
         return true;
     }
diff --git 
a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java
 
b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java
index e520311bb2..1863f85fca 100644
--- 
a/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java
+++ 
b/subprojects/groovy-contracts/src/main/java/org/apache/groovy/contracts/generation/PostconditionGenerator.java
@@ -45,6 +45,7 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.mapX;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
 
 /**
@@ -140,21 +141,27 @@ public class PostconditionGenerator extends BaseGenerator 
{
                     localPostconditionBlockStatement.getStatements().add(0, 
declS(result, returnStatement.getExpression()));
                     
AssertStatementCreationUtility.injectResultVariableReturnStatementAndAssertionCallStatement(block,
 method.getReturnType().redirect(), returnStatement, 
localPostconditionBlockStatement);
                 }
-                setOldVariablesIfEnabled(block, contractsEnabled);
+                setOldVariablesIfEnabled(block, contractsEnabled, 
method.isStatic());
 
             } else if (method instanceof ConstructorNode) {
                 
block.addStatements(postconditionBlockStatement.getStatements());
             } else {
-                setOldVariablesIfEnabled(block, contractsEnabled);
+                setOldVariablesIfEnabled(block, contractsEnabled, 
method.isStatic());
                 
block.addStatements(postconditionBlockStatement.getStatements());
             }
             method.putNodeMetaData(METHOD_PROCESSED, true);
         }
     }
 
-    private void setOldVariablesIfEnabled(BlockStatement block, Expression 
contractsEnabled) {
-        // Assign the return statement expression to a local variable: Map old
+    private void setOldVariablesIfEnabled(BlockStatement block, Expression 
contractsEnabled, boolean isStatic) {
         final Expression oldVariableExpression = localVarX("old", new 
ClassNode(Map.class));
+        if (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)
+            block.getStatements().add(0, declS(oldVariableExpression, mapX()));
+            return;
+        }
+        // Assign the return statement expression to a local variable: Map old
         Statement oldVariableStatement = assignS(oldVariableExpression, 
callThisX(OldVariableGenerationUtility.OLD_VARIABLES_METHOD));
         block.getStatements().add(0, declS(oldVariableExpression, 
ConstantExpression.NULL));
         block.getStatements().add(1, ifS(boolX(contractsEnabled), 
block(oldVariableStatement)));
diff --git 
a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy
 
b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy
index 5a42686e86..163d182513 100644
--- 
a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy
+++ 
b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/other/ClosureExpressionValidationTests.groovy
@@ -199,4 +199,24 @@ class ClosureExpressionValidationTests extends 
GroovyShellTestCase {
 
         assertTrue msg.contains("State changing postfix & prefix operators are 
not supported.")
     }
+
+    // GROOVY-12052
+    void testOldNotSupportedInStaticPostcondition() {
+
+        def msg = shouldFail CompilationFailedException, {
+            evaluate """
+                    import groovy.contracts.*
+
+                    class A {
+
+                        @Ensures({ old.value == result })
+                        static int op(int value) { value }
+                    }
+
+                    A.op(1)
+                """
+        }
+
+        assertTrue msg.contains("'old' is not supported in postconditions of 
static methods")
+    }
 }
diff --git 
a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/post/SimplePostconditionTests.groovy
 
b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/post/SimplePostconditionTests.groovy
index b88c4638bb..8bac8f5539 100644
--- 
a/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/post/SimplePostconditionTests.groovy
+++ 
b/subprojects/groovy-contracts/src/test/groovy/org/apache/groovy/contracts/tests/post/SimplePostconditionTests.groovy
@@ -166,8 +166,9 @@ class Account {
         }
     }
 
+    // GROOVY-12052
     @Test
-    void no_postcondition_on_static_methods() {
+    void postcondition_on_static_void_method() {
 
         def source = """
         import groovy.contracts.*
@@ -175,14 +176,40 @@ class Account {
 class Account {
 
    @Ensures({ amount != null })
-   static def withdraw(def amount)  {
+   static void withdraw(def amount)  {
+     println amount
+   }
+}
+    """
+
+        def clazz = add_class_to_classpath(source)
+        clazz.withdraw(10)
+        shouldFail(PostconditionViolation) {
+            clazz.withdraw(null)
+        }
+    }
+
+    // GROOVY-12052
+    @Test
+    void postcondition_with_result_on_static_method() {
+
+        def source = """
+        import groovy.contracts.*
+
+class Account {
+
+   @Ensures({ result >= 0 })
+   static int balanceAfter(int amount)  {
      return amount
    }
 }
     """
 
         def clazz = add_class_to_classpath(source)
-        assert clazz.withdraw(null) == null
+        assert clazz.balanceAfter(10) == 10
+        shouldFail(PostconditionViolation) {
+            clazz.balanceAfter(-5)
+        }
     }
 
     @Test

Reply via email to