Skip to content

fix(when): type a when subject from the binding in scope at the when - #335

Merged
Hessesian merged 3 commits into
mainfrom
fix/when-subject-through-local-binding
Oct 20, 2026
Merged

Hessesian merged 3 commits into
mainfrom
fix/when-subject-through-local-binding

Conversation

@Hessesian

Copy link
Copy Markdown
Owner

Stacked on #334. Review and merge that first; this PR's diff is only the commit on top.

Bug

resolve_declared_type_from_cst was the one scope-aware declared-type lookup, used for when subjects. It searched a statement list in any order and only read annotations (plus Boolean literals), so:

fun handle(value: Color) {
    when (value) { Color.RED -> {} }   // reported: missing SMALL, LARGE
    val value: Size = Size.SMALL       // declared after the when, still won
}

fun other(value: Color) {}
fun handle() {
    val value = pick()                 // pick(): Size
    when (value) { Size.SMALL -> {} }  // reported: missing RED, GREEN, BLUE
}

The same applied to the root of a field-chain subject (when (holder.shade)).

Fix

  • cst_symbol::local_binding_of(name, point, bytes): the by-name-at-a-point form of local_binding_at from fix(resolve): type an identifier from the declaration that binds it at that node #334, for callers that hold a name and a position rather than the identifier node. local_binding_at now delegates to it.
  • expr_type::infer_local_type_at(name, point, ..): the type that binding states, with the file it was written in.
  • fill_when::analyze_when and resolver::infer::infer_field_chain_type ask it, then fall back to the same by-name lookups as before.
  • resolve_declared_type_from_cst (116 lines) is deleted. It had exactly these two callers.

One deliberate behaviour change beyond scoping: an unannotated subject now carries the file its type was written in, and analyze_when uses that file to resolve the type's members, as it already did for field chains.

Tests

Three new diagnostics tests in fill_when_tests.rs, all red before the change with the wrong enum's branches in the message: later-declared local, unannotated local with a same-named parameter in another function, and field-chain root. Existing Boolean-literal and nullable-subject tests still pass through the new path.

RUSTFLAGS="-D warnings" cargo test: 2073 unit tests + integration suites pass. cargo clippy --all-targets -- -D warnings clean.

Not measured

No Moneta run: resolution-accuracy exercises classify_cursor + resolve_identity, not when diagnostics, so it would not see this change. Cost is one scope climb per when expression.

Merge-order note

This removes one resolver::infer:: reach from src/features/fill_when.rs. If #333 (the ratchet) merges first, its resolver::infer:: baseline must drop from 7 to 6 when this is rebased; the test will say so.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Field-chain inference discards the newly available origin URL and can still select an unrelated homonymous root type.

3 open findings
What changed in this PR

Fixes when subject inference to use the local binding in scope at the expression.

Changes:

  • Adds scope-aware local type inference with origin tracking.
  • Updates plain and field-chain when subjects to use it.
  • Adds regression tests for shadowed and inferred locals.
File Description
src/​resolver/​infer.rs Uses scoped binding inference for chain roots.
src/​indexer/​infer/​expr_type.rs Adds infer_local_type_at.
src/​indexer/​infer/​cst_symbol.rs Adds by-name binding lookup at a CST node.
src/​indexer.rs Re-exports the new inference helper.
src/​features/​fill_when.rs Uses scope-aware subject type inference.
src/​features/​fill_when_tests.rs Adds diagnostic regressions.
AGENTS.md Documents the new helper.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/resolver/infer.rs Resolved
Comment thread src/features/fill_when.rs Resolved
Comment thread src/features/fill_when_tests.rs Resolved
@Hessesian
Hessesian force-pushed the fix/when-subject-through-local-binding branch from fcadf47 to 8b42dc5 Compare October 9, 2026 15:25
@Hessesian
Hessesian changed the base branch from fix/local-binding-at-use-site to main October 20, 2026 08:05
Hessesian and others added 2 commits October 20, 2026 10:10
`resolve_declared_type_from_cst` was the one scope-aware declared-type
lookup, but it searched a statement list in any order and only read
annotations (plus Boolean literals). So for a `when` subject:

- a same-named `val` declared *after* the `when` won over the parameter
  actually in scope, and
- an unannotated `val value = pick()` fell through to the file-wide
  by-name scan, which answered with another function's `value` parameter.

Both reported missing branches of the wrong enum. The same applied to the
root of a field-chain subject (`when (holder.shade)`).

`cst_symbol::local_binding_of(name, point, ..)` is the by-name-at-a-point
form of `local_binding_at`, and `infer_local_type_at` turns the binding
into a type. `fill_when::analyze_when` and `infer_field_chain_type` now
ask it; `resolve_declared_type_from_cst` had no other callers and is
deleted. An unannotated subject also carries the file its type was
written in, which `analyze_when` uses to resolve the type's members.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tten in

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Hessesian
Hessesian force-pushed the fix/when-subject-through-local-binding branch from b0ed1cd to 11b0d5c Compare October 20, 2026 08:10
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Hessesian
Hessesian merged commit fbc0f0d into main Oct 20, 2026
4 checks passed
@Hessesian
Hessesian deleted the fix/when-subject-through-local-binding branch October 20, 2026 08:16
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.

2 participants