Skip to content

Fix typeof switch narrowing broken by case "" clauses#3934

Open
Copilot wants to merge 2 commits into
mainfrom
copilot/fix-tsgo-type-narrowing-issue
Open

Fix typeof switch narrowing broken by case "" clauses#3934
Copilot wants to merge 2 commits into
mainfrom
copilot/fix-tsgo-type-narrowing-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 16, 2026

getSwitchClauseTypeOfWitnesses conflated empty string literal values (case "") with non-string-literal expressions — both produced text == "", which aborted witness collection entirely and disabled all type narrowing for the switch.

function f(x: string | number) {
  switch (typeof x) {
    case "":       // empty string — should be ignored for narrowing, not kill it
    case "string": // valid witness — should narrow x to string
      x.charAt(0); // ❌ incorrectly errors: "Property 'charAt' does not exist on type 'string | number'"
      break;
  }
}
  • Separated the two concerns: non-string-literal case expressions are now checked upfront via core.Some, and empty string literals are treated as empty witnesses (no narrowing for that clause) rather than aborting the entire witness array — matching the TS reference implementation's text && !contains(witnesses, text) ? text : undefined logic.

In getSwitchClauseTypeOfWitnesses, an empty string literal value (case "")
was incorrectly treated the same as a non-string-literal expression,
causing the function to return nil and disabling all type narrowing.

The fix separates the two concerns:
1. Non-string-literal case expressions → return nil (no witnesses)
2. Empty string literal values → treat as empty witness (no narrowing
   for that clause, but other clauses still narrow correctly)

This matches the TypeScript reference implementation behavior.

Fixes #1495

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/493d3b3e-abfa-47c0-a3d4-e5101635afc7

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix tsgo type narrowing issue in switch statement Fix typeof switch narrowing broken by case "" clauses May 16, 2026
Copilot AI requested a review from jakebailey May 16, 2026 20:27
Comment thread internal/checker/flow.go
}
if !slices.Contains(witnesses, text) {
witnesses[i] = text
// Return nil if one or more case clause expressions are not string literals.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot The original code had const witnesses: (string | undefined)[] = [];; I'm not exactly sure that we won't have other errors related to this? Surely empty text will still cause problems as the slice is still of strings

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The []string with "" as the undefined equivalent is the pre-existing design — default clauses and duplicate case values already produced "" entries in the witnesses slice before my change. All three consumers guard against empty strings:

  1. narrowTypeBySwitchOnTypeOf (line 1152): if text != "" → maps empty witnesses to neverType
  2. getNotEqualFactsFromTypeofSwitch (line 1996): witness != "" → skips empty witnesses
  3. computeExhaustiveSwitchStatement → delegates to getNotEqualFactsFromTypeofSwitch

So case "" now produces the same "" witness entry that default clauses and duplicates already do — meaning "no narrowing for this clause" — instead of aborting the entire witness array. The behavior matches the TS reference where undefined witnesses are simply skipped in all consuming code paths.

@jakebailey jakebailey marked this pull request as ready for review May 16, 2026 20:56
Copilot AI review requested due to automatic review settings May 16, 2026 20:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes control-flow narrowing for switch (typeof x) when a clause uses an empty string literal (case ""), ensuring witness collection isn’t aborted and subsequent valid typeof cases still narrow correctly.

Changes:

  • Updated getSwitchClauseTypeOfWitnesses to (1) bail out only when any case expression is not a string-literal-like node, and (2) treat "" as an empty witness rather than cancelling witness collection.
  • Added a compiler test covering case "" followed by a valid case "string" and updated reference baselines to validate the narrowing behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/checker/flow.go Fixes witness collection for typeof switches so empty-string cases don’t disable narrowing for the entire switch.
testdata/tests/cases/compiler/typeofSwitchEmptyStringCase.ts Adds a regression test for case "" in typeof switches.
testdata/baselines/reference/compiler/typeofSwitchEmptyStringCase.types Verifies that x narrows to string inside the "string" case despite the earlier case "".
testdata/baselines/reference/compiler/typeofSwitchEmptyStringCase.symbols Captures expected symbol resolution for the new test.
testdata/baselines/reference/compiler/typeofSwitchEmptyStringCase.js Captures expected emit output for the new test.
testdata/baselines/reference/compiler/typeofSwitchEmptyStringCase.errors.txt Captures the expected comparability error for case "" while still allowing narrowing.

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.

tsgo loses all type narrowing in a switch (typeof x) when any clause is case ""

3 participants