Skip to content

fix(lower): VariantFields shadow fix — codex blocker on #565 - #568

Merged
briansrls merged 1 commit into
mainfrom
fix/variant-fields-shadow
Apr 19, 2026
Merged

briansrls merged 1 commit into
mainfrom
fix/variant-fields-shadow

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

Summary

Fixes the codex-flagged blocker that landed in #565 (merged): the new SurfacePattern::VariantFields lowering branch used the full outer scope as its duplicate-binding detector, so a legitimate match-arm shadow of an outer name got rejected as "binds X more than once." VariantWith already shadowed correctly — VariantFields should match that shape.

The bug

lower.rs:5074-5103 in the VariantFields arm: arm_scope.contains_key(&field.binding) checks the FULL outer scope cloned at line 5057. Any outer binding collides.

The fix

Per-arm introduced: HashSet<String> tracks only names THIS pattern brings in. Duplicate-check against introduced, not arm_scope. Pattern-local duplicates still reject; outer shadowing works via plain HashMap overwrite semantics (same as VariantWith).

Regression test

prereq2_named_payload_pattern_shadows_outer_scope_binding:

```
fn read(w: Wrapped, inner: Int) -> Int =
match w { Wrap { inner: inner } => inner.inner, Empty => inner }
```

inner is a fn parameter; match arm Wrap { inner: inner } shadows it. Previously rejected as duplicate; now compiles cleanly.

Test plan

  • New regression test passes
  • Existing duplicate-binding test still passes (pattern-local detector unchanged)
  • Full m1_substrate_test suite: 95 passed, 0 failed

🤖 Generated with Claude Code

…ed names

Codex blocker on #565 (merged without the fix): the new
`SurfacePattern::VariantFields` branch in `lower.rs` used
`arm_scope.contains_key(...)` as its duplicate-binding detector.
`arm_scope` was cloned from the enclosing scope, so ANY outer binding
that the pattern's field name shadows would fire a spurious
"binds X more than once" diagnostic. Match-arm shadowing for
VariantFields therefore broke for valid programs, while
`VariantWith { binding: x }` already shadowed correctly via plain
HashMap overwrite semantics.

Fix: track a per-arm `introduced: HashSet<String>` of names this
pattern brings in; duplicate-check against that set, not `arm_scope`.
Pattern-local duplicates (`Foo { a: x, b: x }`) still reject. Outer
shadowing (`fn read(w, inner: Int); match w { Wrap { inner: inner }
=> ... }`) now works.

Regression test:
`prereq2_named_payload_pattern_shadows_outer_scope_binding` exercises
the exact shadow case the bug rejected. Existing duplicate-binding
test (`prereq2_named_payload_pattern_duplicate_binding_fails_closed`)
continues to pass — the pattern-local detector is unchanged.

Full m1_substrate_test suite: 95 passed, 0 failed.
@briansrls

Copy link
Copy Markdown
Contributor Author

claude-review (director) — ✅ ship it

Root-caused correctly. The scope-inversion is the real bug: arm_scope is a clone of the outer scope, so contains_key over it conflates "this pattern re-binds a name" with "pattern binding collides with an outer binding." The fix (per-pattern introduced: HashSet<String>) cleanly separates the two concerns — pattern-local duplicates still reject, outer-name shadowing works.

Cross-checked:

  • Verified VariantWith shape (line 5068, arm_scope.insert(...) with overwrite) matches what you claim.
  • Checked for similar-shape bugs elsewhere: collect_scope_bound_free_vars at 1212 uses arm_scope.clone() differently (removes bindings before recursing) — that's a distinct correct usage, not a sibling bug.

Per TESTING.md:

  • Regression test name follows <subject>_<verb>_<object>_<condition> ✓
  • One claim per test ✓
  • Uses cached_compile_to_dag full pipeline — acceptable today per TESTING.md §Practical guidance (Dag builder API doesn't exist yet); natural unit-test once Phase 1 builders land.

Note: HashSet<String> is weakly typed per CODING.md §Typed handles — but this is a focused fix, not the place for the broader lowering-binding refactor. Merge as-is.

CI should clear shortly. Merge when green.

@briansrls briansrls mentioned this pull request Apr 19, 2026
@briansrls
briansrls merged commit f5110b4 into main Apr 19, 2026
3 checks passed
@briansrls
briansrls deleted the fix/variant-fields-shadow branch June 1, 2026 18:41
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.

1 participant