Skip to content

Fix the small-step semantics of Imperative to consider block scopes for var initialization#1141

Open
aqjune-aws wants to merge 7 commits into
mainfrom
jlee/scoped_env
Open

Fix the small-step semantics of Imperative to consider block scopes for var initialization#1141
aqjune-aws wants to merge 7 commits into
mainfrom
jlee/scoped_env

Conversation

@aqjune-aws
Copy link
Copy Markdown
Contributor

@aqjune-aws aqjune-aws commented May 7, 2026

Issue #, if available:

Closes #372

Description of changes:

  • Adds scoped environment semantics to blocks: step_block_done now projects the store through the parent store via projectStore, discarding block-local variables on exit. This applies to both StepStmt (deterministic) and StepKleene
    (nondeterministic).
  • Adds PostWF helper definition in Specification.lean for postcondition stability under projectStore.
  • Adds .block constructor to KleeneStmt and corresponding step_block/step_block_body/step_block_done to StepKleene, mirroring the deterministic block semantics.
  • Changes StmtToKleeneStmt (.block _ bss _) to produce .block b (wrapping in a Kleene block), so the Kleene translation preserves scoping.

On top of this, this also renames touchedVars to modifiedOrDefinedVars for clarity, and instead makes touchedVars all vars that are read + modified + defined.

How to review?

  • Strata/DL/Imperative/StmtSemantics.lean has the most important update: the .block constructor now has the input store which will be used to 'project' the output store to the variables that have been defined before. Also, any block-exiting small steps like step_block_done will do the projection, to define variables which were defined inside the inner scope.
  • Correspondingly, the Kleene language was updated to add a notion of block and scoping to its syntax and semantics, otherwise DetToKleene doesn't prove. (Strata/DL/Imperative/KleeneStmt.lean, Strata/DL/Imperative/KleeneStmtSemantics.lean)
  • StrataTest/DL/Imperative/StepStmtTest.lean has a few new tests that show scoping works well.
  • DetToKleeneCorrect.lean and ProcBodyVerifyCorrect.lean shows that the top-level statements are not touched after this update.
  • Strata/DL/Imperative/HasVars.lean has the touchedVars update and Strata/Languages/Core/StatementSemantics.lean has some additional well-formedness about evaluator extension that are useful.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@github-actions github-actions Bot added the Core label May 7, 2026
@aqjune-aws aqjune-aws marked this pull request as ready for review May 7, 2026 21:21
@aqjune-aws aqjune-aws requested a review from a team May 7, 2026 21:21
Copy link
Copy Markdown
Contributor

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

🤖 Overall this is a well-structured PR that correctly implements block-scoped variable semantics via projectStore. The Kleene language extension mirrors the deterministic semantics cleanly, and the new tests provide good coverage of the scoping behavior.

A few items to consider:

  • The PostWF definition quantifies over all σ_parent, which is stronger than needed (in practice only the actual parent store matters). This is conservative and safe, but worth documenting that it means postconditions must not reference block-local variables.
  • There is significant code duplication in the new core_step_preserves_wf* / core_wf*_preserved theorems — all six follow the exact same proof pattern as core_step_preserves_wfBool.
  • Stmt.labels / Block.labels appear to be dead code (not referenced anywhere outside their definition).

| step _ _ _ hstep _ ih =>
exact ih (core_step_preserves_wfBool π φ h_wf_ext _ _ hwf₀ hstep)

theorem core_step_preserves_wfVar
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 core_step_preserves_wfVar, core_wfVar_preserved, core_step_preserves_wfCong, core_wfCong_preserved, core_step_preserves_wfExprCongr, core_wfExprCongr_preserved are all structurally identical to core_step_preserves_wfBool / core_wfBool_preserved — only the property type and the WFEvalExtension field differ. Consider extracting a generic helper parameterized by the property and the field accessor to avoid this 6× duplication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, indeed they have very similar but slightly different proofs.... I have tried this but resolving the duplication was non-trivial.

Comment thread Strata/DL/Imperative/Stmt.lean Outdated
/-! ### Labels -/

mutual
@[expose] def Stmt.labels (s : Stmt P C) : List String :=
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Stmt.labels and Block.labels are defined here but not referenced anywhere else in the codebase. Is this dead code left over from development, or is it intended for future use? If the latter, a brief docstring explaining the intended purpose would help.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed, thanks :)

omit [HasVal P] in
/-- Lift a `TripleBlock` to a `Triple` by wrapping in a block. -/
/-- A postcondition is well-formed if it is stable under `projectStore`. -/
def PostWF (Post : Env P → Prop) : Prop :=
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 PostWF quantifies over all σ_parent, meaning the postcondition must hold even when projected through a store that defines no variables (making everything none). In practice, TripleBlock.toTriple only applies projection with the actual parent store at block entry. Would it be more precise (and easier for callers to discharge) to parameterize PostWF by the specific σ_parent that will be used? Or at minimum, document that this means the postcondition must only reference variables that are guaranteed to be defined in the parent scope.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This had to be forall σ_parent because in general we don't know which variables are defined in this scope unless we know the information of the exact context. A Hoare triple is only aware of the code block inside the pre-postconditions, but can't know the code outside of it:

c1
{pre}
c2      <-- A Hoare triple must only be aware of this c2; we can't give information about c1 or c2 (which will have .init) to post.
{post}
c3

Copy link
Copy Markdown
Contributor

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

🔍 The new commit is a merge of main — no changes to the files I previously commented on. My three suggestions (deduplicating the wf-preservation proofs, clarifying whether Stmt.labels/Block.labels are dead code, and documenting PostWF's universal quantification over σ_parent) remain unaddressed. These were non-blocking suggestions so LGTM overall, but it would be good to get a response on the labels question in particular.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Semantics should be scoped

2 participants