Fix typeof switch narrowing broken by case "" clauses#3934
Conversation
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>
case "" clauses
| } | ||
| if !slices.Contains(witnesses, text) { | ||
| witnesses[i] = text | ||
| // Return nil if one or more case clause expressions are not string literals. |
There was a problem hiding this comment.
@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
There was a problem hiding this comment.
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:
narrowTypeBySwitchOnTypeOf(line 1152):if text != ""→ maps empty witnesses toneverTypegetNotEqualFactsFromTypeofSwitch(line 1996):witness != ""→ skips empty witnessescomputeExhaustiveSwitchStatement→ delegates togetNotEqualFactsFromTypeofSwitch
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.
There was a problem hiding this comment.
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
getSwitchClauseTypeOfWitnessesto (1) bail out only when anycaseexpression 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 validcase "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. |
getSwitchClauseTypeOfWitnessesconflated empty string literal values (case "") with non-string-literal expressions — both producedtext == "", which aborted witness collection entirely and disabled all type narrowing for the switch.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'stext && !contains(witnesses, text) ? text : undefinedlogic.