Skip to content

feat: add block scoping for if/elif/else and match arms#254

Merged
frendsick merged 3 commits into
mainfrom
feat/if-match-block-scoping
Jun 12, 2026
Merged

feat: add block scoping for if/elif/else and match arms#254
frendsick merged 3 commits into
mainfrom
feat/if-match-block-scoping

Conversation

@frendsick

Copy link
Copy Markdown
Owner

Summary

Implements strict block scoping for if/elif/else/fi (#248) and match arms (#249), reusing the while-block scope machinery from #251.

  • Each if/elif/else branch and each match arm gets its own scope frame: push on entry, pop at the next branch boundary or fi/end.
  • An is-binding or pattern binding is visible only inside its own branch/arm — not in a sibling branch, not in the else, not after the block.
  • Implicit bindings that would shadow an accessible block-local or function-level variable are rejected with DuplicateName. Globals are a separate, exempt namespace; explicit = x may still mutate an outer variable, and a new = x with no outer binding is block-local.

To bootstrap under the stricter rules, every compiler / stdlib / lsp site that relied on a binding leaking past its block (the early-return guard idiom) was refactored to the positive form if X Variant(b) is then <body> fi or an .unwrap hoist — no behavioral change, just no leak.

Test plan

  • test_scope: 13 new cases — is-binding then-only / not-in-else / not-after-fi, elif independence, outer mutation, block-local new var, shadow error; match per-arm visibility, cross-arm invisibility, shadow error, outer mutation, block-local new var
  • New error fixtures: if_is_binding_out_of_scope, match_arm_binding_out_of_scope (UNDEFINED_NAME); if_binding_shadow, match_binding_shadow (DUPLICATE_NAME)
  • Full compiler suite: 60 passed, 0 failed
  • Examples: 47 passed, 0 failed
  • Bootstrap: self-compilation + fixed-point
  • lsp.casa and formatter/format.casa build; all changed files casafmt-clean

Closes #248
Closes #249

frendsick added 3 commits May 30, 2026 01:33
Scope is-bindings and pattern bindings to their own block frame, reusing
the while-block scope machinery: each if/elif/else branch and each match
arm pushes a frame on entry and pops it at the next branch boundary or
`fi`/`end`. A binding is visible only inside its branch/arm, never in a
sibling branch, the else, or after the block. Implicit bindings that would
shadow an accessible block-local or function-level variable are rejected
(DuplicateName); globals remain a separate, exempt namespace and explicit
`= x` may still mutate an outer variable.

Refactor every compiler, stdlib, and lsp site that relied on a binding
leaking past its block (the early-return guard idiom) to the positive form
`if X Variant(b) is then <body> fi` or an `.unwrap` hoist, so the compiler
bootstraps under the stricter rules.

Closes #248
Closes #249
@frendsick
frendsick merged commit 20c69d8 into main Jun 12, 2026
3 checks passed
@frendsick
frendsick deleted the feat/if-match-block-scoping branch June 12, 2026 08:17
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.

match arm block scoping if block scoping with is-binding enforcement

1 participant