Lift unlabeled block expressions emitted by Python-to-Laurel#1133
Lift unlabeled block expressions emitted by Python-to-Laurel#1133tautschnig wants to merge 1 commit into
Conversation
The Python-to-Laurel translator emits unlabeled Block expressions in
expression position for two common patterns:
* Modeled method calls with precondition tag checks:
{ asserts...; Call }
* Unmodeled method calls (PR #1019): havocs receiver + Any-typed args,
then yields a Hole:
{ havoc_stmt...; Hole }
The Laurel-to-Core translator rejects any Block in expression position
("block expression should have been lowered in a separate pass"), so
the LiftImperativeExpressions pass must eliminate these patterns.
The existing Block case in transformExpr kept the Block structure and
only transformed its sub-expressions. This change replaces the Block
structure with "prepend side-effects + return last element as value",
which is the natural semantics for these patterns.
Changes in LiftImperativeExpressions.lean:
1. transformExpr .Block case: for unlabeled blocks, recursively transform
each stmt (preserves SSA-snapshot semantics for assigns in expression
position), hoist asserts/declares via onlyKeepSideEffectStmtsAndLast,
then return the last element as the expression value rather than
wrapping back into a Block.
2. New helper containsAssignmentOutsideUnlabeledBlock: used by the
Assert/Assume cases in transformStmt. These cases previously bailed
out when a condition contained any assignment; they now proceed when
the assignment is inside an unlabeled block (since the .Block case
above will safely lift the side-effect).
The original containsAssignment is preserved for use by the ITE case,
which correctly isolates branch-local prepends when a branch contains
assignments.
Eliminates all "block expression should have been lowered" errors in
the internal benchmark suite.
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the Laurel liftExpressionAssignments pass so that unlabeled Block expressions occurring in expression position are lowered into “prepended side-effects + final value expression”, eliminating Block nodes that the Laurel-to-Core translator rejects. This targets Python-to-Laurel output patterns like { asserts; Call } and { havoc...; Hole }, and relaxes assert/assume-condition handling to allow assignments when they occur inside such unlabeled blocks.
Changes:
- Change
transformExpr’s.Blockhandling so unlabeled blocks are lowered by hoisting side effects and returning the last element as the value (instead of rebuilding aBlock). - Add
containsAssignmentOutsideUnlabeledBlockand use it intransformStmtfor.Assert/.Assumeso conditions can be transformed when assignments are confined to unlabeled blocks. - Extend lifting tests with an additional procedure to cover conditional assignment lifting behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Strata/Languages/Laurel/LiftImperativeExpressions.lean | Lowers unlabeled block expressions to prepends + value; adjusts assert/assume filtering for assignments inside unlabeled blocks. |
| StrataTest/Languages/Laurel/LiftExpressionAssignmentsTest.lean | Adds regression coverage for unlabeled blocks in expression contexts and ITE branches. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- (PR #1019 for unmodeled calls). | ||
| let _ ← onlyKeepSideEffectStmtsAndLast (head :: rest) | ||
| return (head :: rest).getLast (by simp) | ||
| else | ||
| return ⟨ .Block (← onlyKeepSideEffectStmtsAndLast newStmts) labelOption, source⟩ |
| procedure condAssign(x: int) | ||
| opaque | ||
| { | ||
| var y: int := 0; | ||
| var z: int := (if x > 0 then { y := y + 1 } else { 0 }) + y; | ||
| assert z == 2 |
|
Hey, thanks for this PR. You're right that Laurel should support the code that PythonToLaurel is generating here. One thing though, the existing error "block expression should have been lowered" is phrased too strongly. Sorry about that. LaurelToCoreTranslator is intended to support some blocks in expressions, namely the ones that can be translated to Core expressions. The intention is for a block like Could we investigate making sure that only valid expression blocks make it to
Can we lift the asserts? The Laurel->Core translator will handle a singleton block
Can you say more about what Maybe there should be a separate case for a hole assignment in a block: |
The Python-to-Laurel translator emits unlabeled Block expressions in expression position for two common patterns:
The Laurel-to-Core translator rejects any Block in expression position ("block expression should have been lowered in a separate pass"), so the LiftImperativeExpressions pass must eliminate these patterns.
The existing Block case in transformExpr kept the Block structure and only transformed its sub-expressions. This change replaces the Block structure with "prepend side-effects + return last element as value", which is the natural semantics for these patterns.
Changes in LiftImperativeExpressions.lean:
transformExpr .Block case: for unlabeled blocks, recursively transform each stmt (preserves SSA-snapshot semantics for assigns in expression position), hoist asserts/declares via onlyKeepSideEffectStmtsAndLast, then return the last element as the expression value rather than wrapping back into a Block.
New helper containsAssignmentOutsideUnlabeledBlock: used by the Assert/Assume cases in transformStmt. These cases previously bailed out when a condition contained any assignment; they now proceed when the assignment is inside an unlabeled block (since the .Block case above will safely lift the side-effect).
The original containsAssignment is preserved for use by the ITE case, which correctly isolates branch-local prepends when a branch contains assignments.
Eliminates all "block expression should have been lowered" errors in the internal benchmark suite.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.