Skip to content

Two Msg arms sharing a payload member name mis-resolve: NS1061 fires against the wrong arm #250

Description

@sonhyrd

Summary

When two Msg arms declare a payload member of the same name with different types,
the checker resolves the wrong arm's type and reports NS1061 against the innocent arm.

Reproduced against v0.7.1 (19519dd5), macOS, node 24.15.0.

Reproduction

export type PtyPhase = "output" | "exit";
export interface TerminalView { readonly scrollback: number }
export interface Model { readonly scrollback: number; readonly exited: boolean }
export function initialModel(): Model { return { scrollback: 0, exited: false } }
export type Msg =
  | { readonly kind: "term_state"; readonly state: TerminalView }
  | { readonly kind: "pty_event"; readonly state: PtyPhase; readonly code: number };
export const viewUnbound = ["term_state", "pty_event"] as const;
export function update(model: Model, msg: Msg): Model {
  switch (msg.kind) {
    case "term_state": return { ...model, scrollback: msg.state.scrollback | 0 };
    case "pty_event": return msg.state === "exit" ? { ...model, exited: true } : model;
  }
}
core.ts:12:30 error NS1061 value records stay scalar where the model keeps them
  `===` compares `TerminalView` values by identity, which value storage does not carry ...

Line 12 is the pty_event arm, whose state is a string-literal union — comparing it
with === is ordinary and correct. The diagnostic names TerminalView, which is the
other arm's type. Renaming either member (e.g. term_state's to view) transpiles
clean, with no other change.

Why this shape is likely to be hit

state is what the docs' own terminal example names both of these members: the
<terminal> state arm and the pty-event arm. An app that mounts a terminal and owns the
pty behind it writes both arms, and the collision is the natural spelling.

The diagnostic text itself is fine — it is the arm resolution that is wrong, so the
teaching lands on code that has nothing to do with the problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions