Skip to content

Debugger: project hoisted async/generator variables as source locals #1399

Open

Description

@nickna

Deferred debugger polish from #1306. This issue is independent of the standalone-editor epic.

Goal

Make user-declared variables hoisted into async/generator/async-generator state-machine fields appear with useful source names and lexical lifetimes in managed debugger Locals/Watch experiences, without exposing compiler spills or changing runtime semantics.

Current state

SharpTS emits stable state-machine/display-class names, standard state-machine attributes, StateMachineMethod mappings, async suspension/resume records, sequence points, and source-named fields. Hoisted variables can be inspected as fields on the generated frame, but full projection into ordinary locals is not guaranteed and is documented as accepted v1 behavior.

Investigation gate

Before changing metadata or generated names:

  • Run the Editor parity: TypeScript-source debugging and standalone LSP navigation #1306 manual debugger checklist and record how current hoisted parameters/locals/captures appear in current VS Code C# debugger, Visual Studio, and Rider where available.
  • Produce equivalent small Roslyn C# async, iterator, and async-iterator control programs and compare PE/PDB metadata plus debugger presentation.
  • Determine which portable-PDB custom information and generated-field conventions each debugger actually consumes (for example state-machine hoisted-local scopes), rather than assuming Roslyn metadata is universally interpreted.
  • Record the accepted cross-debugger target and any unavoidable debugger-specific differences.

Work

  • Track source binding identity, source name, lexical scope, generated state-machine field, and suspension lifetime through async/generator lowering.
  • Emit standards-compatible hoisted-local scope/custom debug information required by the accepted debugger target.
  • If debugger expression evaluators require generated field-name conventions, adopt a stable convention while preserving runtime/reflection correctness and readable fallback presentation.
  • Represent parameters, locals crossing suspension, nested-block/shadowed locals, loop bindings, catch bindings, captures shared with display classes, and async-generator state consistently.
  • Keep spills, awaiters, state/current fields, destructuring temporaries, and other scaffolding hidden/non-user.
  • Preserve kickoff/MoveNext mappings, async stepping records, Just My Code attributes, source sequence points, and final-PE/PDB identity.
  • Emit no additional debug metadata or runtime cost for non-debug builds.
  • Update debugger documentation with the verified presentation and remaining debugger limitations.

Verification

  • Metadata tests decode and assert every added custom-debug-information blob, ownership handle, local-scope range, and field association; do not merely check that a GUID exists.
  • Tests cover async functions, generators, async generators, multiple awaits/yields, nested blocks, shadowed names, loop/catch bindings, parameters, captures/display classes, destructuring, imports, and transformed var declarations.
  • Negative tests prove compiler-generated fields/temporaries are not projected as user locals.
  • Deterministic builds produce stable generated names and byte-equivalent semantic metadata where expected.
  • Final rewritten PE and PDB identities/method row mappings remain valid; debug and non-debug programs both run.
  • Manual debugger matrix confirms, at minimum in the accepted primary debugger, that each source variable appears under its source name only while in scope before and after suspension, can be evaluated, and shadowed variables resolve to the correct value.
  • At least one second managed debugger is checked or its incompatibility is documented with a minimized control comparison.
  • Existing DebugSymbolsTests, full unit tests, IL verification, Test262, and TypeScript conformance validation are green.

Success criteria

The accepted debugger(s) present supported hoisted variables like source locals across suspension points with correct names, values, and lexical visibility; generated scaffolding remains hidden; portable-PDB metadata is structurally decoded by automated tests; and runtime/non-debug output is unchanged.

Non-goals

  • Interpreter debugging or a SharpTS DAP adapter.
  • Fabricating locals that have no stable storage/lifetime.
  • Making every debugger display identical when its portable-PDB support differs.

Activity

  1. added
    enhancementNew feature or request
    deferredDe-prioritized; not planned for active work (see tracking comment)
    on Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    deferredDe-prioritized; not planned for active work (see tracking comment)enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions