Skip to content

fix(semantic): typed-return concise arrow opens a scope and declares params (#60) - #63

Merged
ericsssan merged 2 commits into
mainfrom
fix/60-typed-return-arrow-scope
Jun 25, 2026
Merged

fix(semantic): typed-return concise arrow opens a scope and declares params (#60)#63
ericsssan merged 2 commits into
mainfrom
fix/60-typed-return-arrow-scope

Conversation

@ericsssan

Copy link
Copy Markdown
Owner

Fixes #60.

Problem

The parseParenthesized typed-return arrow branch ((params): T => body) built the arrow_fn node without calling emitScopeOpen/emitParamDeclaresFromRange. So for untyped params with an explicit return type, the arrow opened no scope and declared no parameters — they were absent from the scope tree entirely, breaking every scope-aware analysis (no-unused-vars, no-shadow, no-undef, redeclaration, …).

const f = (a, b): number => a + b;   // a, b had NO symbols, no arrow scope
const g = (a, b = a): string => b;   // same

The typed-param path ((a: T): U =>, via looksLikeTsArrowParams) and the empty-param path already worked.

Fix

Open the arrow scope and declare the params before parsing the body, mirroring the other arrow paths, and reuse the function-level param_ev_mark to re-home parameter-default references into the arrow scope (#56 parity). The body then parses inside the scope so its references resolve to the params.

This branch has a speculative backtrack for the cond ? (a): T => body : alt conditional-consequent ambiguity. Scope events are emitted only when no backtrack is possible (!saved_cc) — otherwise they would orphan on the backtrack — so that rare ambiguous case keeps its prior (no-scope) behavior, and there are no events to undo on the backtrack path.

Validation

  • Full suite green. New tests: typed-return arrow declares its params in an arrow_function scope and the body resolves to them; default re-homes to the param (semantic: arrow-function default value does not resolve sibling/self parameters (deferred param-declare ordering) #56 parity); js_ts-mode parity.
  • TypeScript conformance identical to baseline: must-parse 17910/17913, must-reject 1210/1223.
  • Semantic sweep over 19,233 files: 0 crashes, with the expected deltas vs main — +45 arrow scopes, +47 parameter symbols (params now declared), and −47 references (the spurious param-name reads from the cover parse are now correctly cancelled as declarations rather than counted as value reads).

…params (#60)

The `parseParenthesized` typed-return arrow branch (`(params): T => body`) built
the arrow_fn node without ever calling emitScopeOpen/emitParamDeclaresFromRange,
so for untyped params with an explicit return type the arrow opened no scope and
declared no parameters — they were absent from the scope tree, breaking every
scope-aware analysis (no-unused-vars, no-shadow, no-undef, redeclaration, …).
The typed-param path (`(a: T): U =>`) and the empty-param path already worked.

Open the arrow scope and declare the params before parsing the body, mirroring
the other arrow paths, and reuse the function-level `param_ev_mark` to re-home
parameter default references into the arrow scope (#56 parity). The body now
parses inside the scope so its references resolve to the params.

The branch has a speculative backtrack for the `cond ? (a): T => body : alt`
conditional-consequent ambiguity. Scope events are emitted only when no such
backtrack is possible (`!saved_cc`) — otherwise they would orphan on the
backtrack — so that rare ambiguous case keeps its prior (no-scope) behavior; no
events to undo on the backtrack path.

Validated: full suite green; TS conformance identical to baseline
(17910/17913, 1210/1223); semantic sweep over 19,233 files, 0 crashes, with the
expected deltas — +45 arrow scopes, +47 parameter symbols, and -47 references
(the spurious param-NAME reads from the cover parse are now correctly cancelled
as declarations rather than counted as reads).
Add the highest-value guard the suite lacked: a typed-return arrow whose params
are never referenced (`(a, b): number => 0`) still declares them in the arrow
scope — proving declaration is independent of use. Add rest- and destructured-
param shapes, a type-predicate return (`a is string`, no crash + declared), a
ref pin to the js_ts test (body parses inside the new scope), and a regression
pin that the conditional-consequent ambiguity (`c ? (a): T => a : alt`, the case
the fix gates out via !saved_cc) still analyzes without diagnostics.
@ericsssan
ericsssan merged commit 4737daf into main Jun 25, 2026
2 checks passed
@ericsssan
ericsssan deleted the fix/60-typed-return-arrow-scope branch June 25, 2026 05:18
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.

semantic: typed-return concise arrow ((params): T => body) opens no scope and declares no parameters

1 participant