Skip to content

Enable implicit non-top-level conditions (#1160)#2250

Open
Vampire wants to merge 1 commit intomasterfrom
vampire/implicit-non-top-level-conditions
Open

Enable implicit non-top-level conditions (#1160)#2250
Vampire wants to merge 1 commit intomasterfrom
vampire/implicit-non-top-level-conditions

Conversation

@Vampire
Copy link
Member

@Vampire Vampire commented Oct 29, 2025

Fixes #1160

Summary by CodeRabbit

  • Breaking Changes

    • Expression statements in then/expect/filter blocks — including many nested contexts and certain interaction closures — are now treated as implicit conditions by default; this may cause compilation failures. Use the prefix operator !! to opt out of implicit-condition handling.
  • Tests

    • Added and updated tests and snapshots to cover non-top-level condition evaluation and related edge cases.
  • Documentation

    • Updated release notes and primer to clarify implicit-condition rules and the unknown-closure exception.

Copy link
Member Author

Vampire commented Oct 29, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Vampire
Copy link
Member Author

Vampire commented Oct 29, 2025

While fixing the verify methods also stumbled over the sole line that needs to be changed to enable non-top-level implicit conditions.
Turns out this was more an arbitrary restriction that was very easy to change and also only one of our tests needed adaption because it relied on impicit condition being non-effective inside an if. :-)

@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from 007a5a9 to 70fb92e Compare October 29, 2025 13:45
Copy link
Member Author

Vampire commented Oct 29, 2025

Ok, two tests.
Good that we have the !! available now. :-)

@codecov
Copy link

codecov bot commented Oct 29, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.23%. Comparing base (8d380cc) to head (ab01700).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #2250      +/-   ##
============================================
+ Coverage     82.16%   82.23%   +0.07%     
- Complexity     4822     4827       +5     
============================================
  Files           472      472              
  Lines         15036    15037       +1     
  Branches       1905     1905              
============================================
+ Hits          12354    12366      +12     
+ Misses         1989     1983       -6     
+ Partials        693      688       -5     
Files with missing lines Coverage Δ
...kframework/compiler/AbstractDeepBlockRewriter.java 96.34% <100.00%> (ø)
...org/spockframework/compiler/DeepBlockRewriter.java 98.24% <100.00%> (ø)
...g/spockframework/compiler/NoSpecialMethodCall.java 54.16% <100.00%> (+1.99%) ⬆️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AndreasTu
Copy link
Member

Very nice, but should we maybe wait for or plan a Spock 3 version?
I think this change in 2.4 will hinder the adoption of the new version. And the 2.3 is quite old by now.

So I would be for delaying that PR until we released 2.4 finally and then we plan a 3.0 as next version.

@leonard84
Copy link
Member

While I also want to get rid of this confusing behavior, I think we should postpone it until Spock 3.0, as the breaking change is quite significant.

@leonard84 leonard84 added this to the 3.0 milestone Nov 2, 2025
@Vampire
Copy link
Member Author

Vampire commented Nov 9, 2025

Up to @leonard84 I'd say.
I don't care when it comes, I just stumbled over the line and thought I'll create a PR right away before I forget the details again, as I know Leonard wants to have this change.

In my personal opinion I think the change is not that much breaking.
I'd expect that most cases where this is breaking existing code is, where it should always better failed because someone checked a condition on non-top-level and thought the test is green while it is actually red.

And the cases where this is not the case, but the behavior used like in the two cases we had in the codebase, you can easily use !! to disable the implicit assertion.

We were seldomly shy to do breaking changes, why become shy now? :-)

If you really think it is too breaking to bring it in 2.4 now, no problem, just delay it until later.
It just then needs another 3 years until it reaches the users.
(I consider "normal" users here that do not use an M-release but only final releases).
🤷‍♂️

As I said, I just thought I'll do the changes now that I have the details and easy solution in mind. and did it in a way it could still make it into 2.4 (regarding the release notes changes).

@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch 2 times, most recently from 5633a59 to 34517fe Compare November 14, 2025 01:39
@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from 34517fe to b38799d Compare November 18, 2025 22:51
@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from b38799d to 00ed347 Compare March 13, 2026 04:12
@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 499eeb73-bb2e-4a66-8f83-3f49d0055a85

📥 Commits

Reviewing files that changed from the base of the PR and between 64b1e59 and ab01700.

📒 Files selected for processing (10)
  • docs/release_notes.adoc
  • docs/spock_primer.adoc
  • spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java
  • spock-junit4/src/test/groovy/org/spockframework/junit4/junit/JUnitFixtureMethods.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy
  • spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyAllMethodsAstSpec/transforms_conditions_in_private_methods.groovy
  • spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyMethodsAstSpec/transforms_conditions_in_private_methods.groovy
✅ Files skipped from review due to trivial changes (1)
  • spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy
🚧 Files skipped from review as they are similar to previous changes (7)
  • docs/spock_primer.adoc
  • spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java
  • spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyAllMethodsAstSpec/transforms_conditions_in_private_methods.groovy
  • spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyMethodsAstSpec/transforms_conditions_in_private_methods.groovy
  • docs/release_notes.adoc
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy
  • spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java

📝 Walkthrough

Walkthrough

Expression statements in then/expect/filter blocks are now treated as implicit conditions even when nested (e.g., inside closures or condition blocks), a new closure-specific sentinel was added, rewriter logic adjusted, docs/tests updated, and an opt-out !! prefix documented.

Changes

Cohort / File(s) Summary
Documentation
docs/release_notes.adoc, docs/spock_primer.adoc
Add breaking-change note and clarify that expression statements (not general top-level expressions) are treated as implicit conditions except inside unknown closures; document !! opt-out and adjust assert guidance.
Compiler Rewriter
spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java, spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java
Allow implicit-condition handling for nested expression statements in Then/Expect/Filter blocks by relaxing the currTopLevelStat check; use a closure-specific sentinel when traversing unrelated closures.
Sentinel Constant
spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java
Add public CLOSURE_INSTANCE sentinel alongside existing INSTANCE to distinguish closure termination from other sentinel semantics.
Tests — behavior adjustments
spock-junit4/src/test/groovy/org/spockframework/junit4/junit/JUnitFixtureMethods.groovy, spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy
Coerce assignments/expressions with !!(...) where needed to avoid implicit-condition interpretation; adjust an assertion to a bare boolean expression in one branch.
Condition Specs
spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy
Add tests covering failing non-top-level condition and a non-top-level boolean-like expression to validate new implicit-condition semantics.
Snapshots / AST outputs
spock-specs/src/test/resources/snapshots/.../transforms_conditions_in_private_methods.groovy
Update transformed private-method condition evaluations to use SpockRuntime.verifyMethodCondition(...) wrapped in try/catch/finally so condition failures/exceptions are routed through Spock runtime instrumentation.

Sequence Diagram(s)

sequenceDiagram
    participant Parser as Parser/AST
    participant Rewriter as DeepBlockRewriter
    participant Sentinel as NoSpecialMethodCall
    participant Condition as ConditionRewriter

    Parser->>Rewriter: emit ExpressionStatement node
    Note right of Rewriter: determine current block (Then/Expect/Filter),\ncurrSpecialMethodCall state, and interactionClosureDepth
    Rewriter->>Sentinel: read currSpecialMethodCall (INSTANCE / CLOSURE_INSTANCE / other)
    alt eligible for implicit condition
        Rewriter->>Condition: check & rewrite ExpressionStatement -> implicit condition
        Condition->>Parser: produce assertion/verification AST
    else not eligible
        Rewriter->>Parser: leave ExpressionStatement unchanged
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I nibbled through nested lines and found a subtle clue,
Gave closures their own sentinel and taught expressions what to do,
A double-bang to hush the ones that shouldn't be a test—
I hop away contented now, and let the build find rest. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Enable implicit non-top-level conditions' clearly and concisely summarizes the main objective of the changeset: enabling implicit conditions to work in nested contexts.
Linked Issues check ✅ Passed The pull request successfully addresses issue #1160 by enabling implicit conditions in non-top-level contexts like if-statements, allowing expressions inside nested blocks to be treated as implicit assertions.
Out of Scope Changes check ✅ Passed All code changes are directly related to the objective of enabling implicit non-top-level conditions; the modifications to compiler logic, tests, and documentation are all within scope of issue #1160.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vampire/implicit-non-top-level-conditions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java (2)

204-208: ⚠️ Potential issue | 🟠 Major

Make the new interaction guard exception-safe.

This branch now depends on insideInteraction and interactionClosureDepth being restored perfectly, but both are still mutated without finally in the surrounding traversal. If rewriting throws while visiting an interaction, leaked state can make later statements look like implicit conditions.

Possible hardening
 private InteractionRewriter visitInteractionAwareExpressionStatement(ExpressionStatement stat) {
   InteractionRewriter rewriter = new InteractionRewriter(resources, getCurrentWithOrMockClosure());
   if (isInteractionExpression(rewriter, stat)) {
     insideInteraction = true;
-    super.doVisitExpressionStatement(stat);
-    insideInteraction = false;
+    try {
+      super.doVisitExpressionStatement(stat);
+    } finally {
+      insideInteraction = false;
+    }
   } else {
     super.doVisitExpressionStatement(stat);
   }
   return rewriter;
 }

 `@Override`
 protected void doVisitClosureExpression(ClosureExpression expr) {
-  if (insideInteraction) interactionClosureDepth++;
-  closureDepth++;
-  super.doVisitClosureExpression(expr);
-  defineRecorders(expr);
-  closureDepth--;
-  if (insideInteraction) interactionClosureDepth--;
+  boolean inInteraction = insideInteraction;
+  if (inInteraction) interactionClosureDepth++;
+  closureDepth++;
+  try {
+    super.doVisitClosureExpression(expr);
+    defineRecorders(expr);
+  } finally {
+    closureDepth--;
+    if (inInteraction) interactionClosureDepth--;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java`
around lines 204 - 208, The new interaction guard relies on mutable fields
insideInteraction and interactionClosureDepth remaining correctly restored even
if rewriting throws; make this exception-safe by ensuring any mutations to
insideInteraction and interactionClosureDepth in DeepBlockRewriter (the
traversal/visit methods that enter interaction closures) are wrapped so the
previous values are always restored in a finally block (or use a try/finally
helper/RAII-like pattern) around the body that can throw; update the methods
that set these flags (the visit/enter methods handling interaction closures
referenced in the diff) to save the old values, set the new ones, execute the
traversal, and restore the saved values in finally.

204-208: ⚠️ Potential issue | 🟠 Major

interactionClosureDepth == 1 is too broad for interaction-condition detection.

This also matches arbitrary first-level closures inside an interaction argument, e.g. 1 * svc.call(items.every { it > 0 }). In that case it > 0 gets rewritten as a Spock condition even though it lives in an unknown closure, which breaks the promised .every { ... } safety and changes valid interaction code. Please narrow this branch to actual argument-constraint closures and add a regression around a collection-method closure inside an interaction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java`
around lines 204 - 208, The current check in DeepBlockRewriter that treats any
first-level closure inside an interaction as an interaction-condition (using
insideInteraction && interactionClosureDepth == 1) is too broad; update the
condition so it only treats closures that are actual argument-constraint
closures as interaction conditions — i.e., verify the closure node is a direct
argument of the interaction method call and represents a constraint-style
closure (instead of any first-level closure) before rewriting; modify the logic
around insideInteraction and interactionClosureDepth to inspect the parent AST
node (the method call / argument position) to confirm it's the interaction
argument-constraint, and add a regression test that ensures a collection-method
closure inside an interaction like `1 * svc.call(items.every { it > 0 })` is not
rewritten into a Spock condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java`:
- Around line 204-208: The new interaction guard relies on mutable fields
insideInteraction and interactionClosureDepth remaining correctly restored even
if rewriting throws; make this exception-safe by ensuring any mutations to
insideInteraction and interactionClosureDepth in DeepBlockRewriter (the
traversal/visit methods that enter interaction closures) are wrapped so the
previous values are always restored in a finally block (or use a try/finally
helper/RAII-like pattern) around the body that can throw; update the methods
that set these flags (the visit/enter methods handling interaction closures
referenced in the diff) to save the old values, set the new ones, execute the
traversal, and restore the saved values in finally.
- Around line 204-208: The current check in DeepBlockRewriter that treats any
first-level closure inside an interaction as an interaction-condition (using
insideInteraction && interactionClosureDepth == 1) is too broad; update the
condition so it only treats closures that are actual argument-constraint
closures as interaction conditions — i.e., verify the closure node is a direct
argument of the interaction method call and represents a constraint-style
closure (instead of any first-level closure) before rewriting; modify the logic
around insideInteraction and interactionClosureDepth to inspect the parent AST
node (the method call / argument position) to confirm it's the interaction
argument-constraint, and add a regression test that ensures a collection-method
closure inside an interaction like `1 * svc.call(items.every { it > 0 })` is not
rewritten into a Spock condition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e4efe5c-ac53-4d5f-bf40-021d3c7e5cb9

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6342c and 00ed347.

📒 Files selected for processing (8)
  • docs/release_notes.adoc
  • docs/spock_primer.adoc
  • spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java
  • spock-junit4/src/test/groovy/org/spockframework/junit4/junit/JUnitFixtureMethods.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy

@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from 00ed347 to 789cc32 Compare March 13, 2026 15:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java (1)

25-26: Consider adding documentation to clarify the distinction.

Both INSTANCE and CLOSURE_INSTANCE are functionally identical NoSpecialMethodCall objects—distinguished only by object identity. This identity distinction is critical for DeepBlockRewriter.handleImplicitCondition (line 204), which uses currSpecialMethodCall == NoSpecialMethodCall.INSTANCE to differentiate top-level blocks from closure scopes.

A brief Javadoc explaining this would help future maintainers understand the design intent.

📝 Suggested documentation
 public class NoSpecialMethodCall implements ISpecialMethodCall {
+  /**
+   * Sentinel instance used for top-level block scope.
+   * Distinguished from {`@link` `#CLOSURE_INSTANCE`} by identity check in
+   * {`@link` DeepBlockRewriter#handleImplicitCondition}.
+   */
   public static final ISpecialMethodCall INSTANCE = new NoSpecialMethodCall();
+  /**
+   * Sentinel instance used inside unrelated closures.
+   * Expressions in closures bound to this instance are not treated as implicit conditions.
+   */
   public static final ISpecialMethodCall CLOSURE_INSTANCE = new NoSpecialMethodCall();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java`
around lines 25 - 26, Add Javadoc to the NoSpecialMethodCall static fields
clarifying that INSTANCE and CLOSURE_INSTANCE are intentionally distinct by
object identity (even though they are functionally identical) so callers can
detect top-level vs closure scopes; specifically document that
DeepBlockRewriter.handleImplicitCondition uses identity comparison
(currSpecialMethodCall == NoSpecialMethodCall.INSTANCE) to recognize top-level
blocks, and that CLOSURE_INSTANCE is used for closure-scoped blocks to prevent
that match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java`:
- Around line 25-26: Add Javadoc to the NoSpecialMethodCall static fields
clarifying that INSTANCE and CLOSURE_INSTANCE are intentionally distinct by
object identity (even though they are functionally identical) so callers can
detect top-level vs closure scopes; specifically document that
DeepBlockRewriter.handleImplicitCondition uses identity comparison
(currSpecialMethodCall == NoSpecialMethodCall.INSTANCE) to recognize top-level
blocks, and that CLOSURE_INSTANCE is used for closure-scoped blocks to prevent
that match.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c814360e-e56e-4d8e-9bd3-53374ccd39d6

📥 Commits

Reviewing files that changed from the base of the PR and between 00ed347 and 789cc32.

📒 Files selected for processing (8)
  • docs/release_notes.adoc
  • docs/spock_primer.adoc
  • spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java
  • spock-junit4/src/test/groovy/org/spockframework/junit4/junit/JUnitFixtureMethods.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy
🚧 Files skipped from review as they are similar to previous changes (2)
  • spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy

@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from 789cc32 to 4abd191 Compare March 13, 2026 16:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java (1)

204-208: 🛠️ Refactor suggestion | 🟠 Major

Decouple implicit-condition logic from singleton identity.

The gate at lines 204–208 restricts block-level implicit conditions using currSpecialMethodCall == NoSpecialMethodCall.INSTANCE, but NoSpecialMethodCall remains publicly instantiable. Since INSTANCE and CLOSURE_INSTANCE are behaviorally identical, any fresh new NoSpecialMethodCall() would silently fail the identity check and disable implicit-condition rewriting in then/expect/filter blocks.

Currently the codebase only uses the two static instances, but this design is fragile. Encode the "no special method call" state explicitly (e.g., a dedicated flag or method on ISpecialMethodCall) instead of inferring it from identity checks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java`
around lines 204 - 208, The identity check against NoSpecialMethodCall.INSTANCE
is fragile; add an explicit predicate to ISpecialMethodCall (e.g., boolean
isNoSpecialMethodCall()) implemented to return true in NoSpecialMethodCall (both
INSTANCE and CLOSURE_INSTANCE) and false in all other implementations, then
replace the identity check in DeepBlockRewriter (the condition using
currSpecialMethodCall == NoSpecialMethodCall.INSTANCE together with
isThenOrExpectOrFilterBlock()) with
currSpecialMethodCall.isNoSpecialMethodCall() so implicit-condition logic no
longer depends on singleton identity; keep the other branches
(isConditionMethodCall(), isConditionBlock(), isGroupConditionBlock(),
insideInteraction && interactionClosureDepth == 1) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/release_notes.adoc`:
- Around line 11-14: The release note is too broad; change the wording to scope
the behavior to assertion/condition contexts only by clarifying that expression
statements are treated as implicit conditions only in assertion-related contexts
handled by DeepBlockRewriter.handleImplicitCondition — specifically in
then/expect/filter blocks, known condition blocks/methods (e.g.,
`@ConditionBlock`, verifyAll/with), and first-level interaction closures, and not
globally for all expression statements or unknown closures.

---

Outside diff comments:
In `@spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java`:
- Around line 204-208: The identity check against NoSpecialMethodCall.INSTANCE
is fragile; add an explicit predicate to ISpecialMethodCall (e.g., boolean
isNoSpecialMethodCall()) implemented to return true in NoSpecialMethodCall (both
INSTANCE and CLOSURE_INSTANCE) and false in all other implementations, then
replace the identity check in DeepBlockRewriter (the condition using
currSpecialMethodCall == NoSpecialMethodCall.INSTANCE together with
isThenOrExpectOrFilterBlock()) with
currSpecialMethodCall.isNoSpecialMethodCall() so implicit-condition logic no
longer depends on singleton identity; keep the other branches
(isConditionMethodCall(), isConditionBlock(), isGroupConditionBlock(),
insideInteraction && interactionClosureDepth == 1) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd1e7819-cc8f-4486-a5ea-8d4e70ef622f

📥 Commits

Reviewing files that changed from the base of the PR and between 789cc32 and 4abd191.

📒 Files selected for processing (10)
  • docs/release_notes.adoc
  • docs/spock_primer.adoc
  • spock-core/src/main/java/org/spockframework/compiler/AbstractDeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/DeepBlockRewriter.java
  • spock-core/src/main/java/org/spockframework/compiler/NoSpecialMethodCall.java
  • spock-junit4/src/test/groovy/org/spockframework/junit4/junit/JUnitFixtureMethods.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy
  • spock-specs/src/test/groovy/org/spockframework/smoke/extension/TempDirExtensionSpec.groovy
  • spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyAllMethodsAstSpec/transforms_conditions_in_private_methods.groovy
  • spock-specs/src/test/resources/snapshots/org/spockframework/smoke/ast/condition/VerifyMethodsAstSpec/transforms_conditions_in_private_methods.groovy
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/spock_primer.adoc
  • spock-specs/src/test/groovy/org/spockframework/smoke/condition/ConditionEvaluation.groovy

Comment on lines +11 to +14
* It is no longer true that only top-level expression statements are treated as implicit conditions.
Just like within `with { ... }`, `verifyAll { ... }`, `@ConditionBlock` methods, and other places,
each expression statement is now considered an implicit condition, except for statements in unknown
closures.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Scope this note to assertion contexts.

The implementation does not make expression statements implicit globally; DeepBlockRewriter.handleImplicitCondition() still limits this to then/expect/filter, known condition blocks/methods, and first-level interaction closures. As written, “each expression statement” reads broader than the compiler change actually is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/release_notes.adoc` around lines 11 - 14, The release note is too broad;
change the wording to scope the behavior to assertion/condition contexts only by
clarifying that expression statements are treated as implicit conditions only in
assertion-related contexts handled by DeepBlockRewriter.handleImplicitCondition
— specifically in then/expect/filter blocks, known condition blocks/methods
(e.g., `@ConditionBlock`, verifyAll/with), and first-level interaction closures,
and not globally for all expression statements or unknown closures.

@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from 4abd191 to 64b1e59 Compare March 19, 2026 16:28
@testlens-app

This comment has been minimized.

@Vampire Vampire force-pushed the vampire/implicit-non-top-level-conditions branch from 64b1e59 to ab01700 Compare March 21, 2026 21:35
@testlens-app
Copy link

testlens-app bot commented Mar 21, 2026

🔎 No tests executed 🔎

🏷️ Commit: ab01700
▶️ Tests: 0 executed
🟡 Checks: 58/61 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spock assertion inside if-statement doesn't work

3 participants