microsoft / microsoft/TypeScript

[TS 4.2] Narrowing a union type is inconsistent using type guards on different member of union

Open
#43,904 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Domain: check: Control Flow
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

Bug Report

🔎 Search Terms

narrowing union type

🕗 Version & Regression Information
  • This changed between versions 4.1.5 and 4.2.3
⏯ Playground Link

Playground link with relevant code

💻 Code
export type SuccessStatusCode = 200 | 201
export type FailureStatusCode = 400 | 401 | 402 | 403 | 500 | 502

interface HttpResponse<StatusCode extends SuccessStatusCode | FailureStatusCode> {
  statusCode: StatusCode
}

interface LegacyApiResponse extends Partial<{ [key: string]: unknown }> {
	statusCode?: never
}

type Success = HttpResponse<SuccessStatusCode> | LegacyApiResponse
type Failure = HttpResponse<FailureStatusCode>

type Result<S extends Success, F extends Failure = never> = S | F

declare function resultIsSuccess<S extends Success, F extends Failure>(r: Result<S, F>): r is S
declare function resultIsFailure<S extends Success, F extends Failure>(r: Result<S, F>): r is F

type TestResult = Result<{ success: true }, HttpResponse<403>>

declare const result: TestResult

export function test() {
  if (resultIsFailure(result)) {
    result // Inferred as HttpResponse<403>.
  } else {
    result // Inferred as { success: true }.
  }
}

export function test2() {
  if (resultIsSuccess(result)) {
    result // Inferred as { success: true }.
  } else {
    result // Cannot infer, stay as TestResult.
  }
}
🙁 Actual behavior

While test1 works fine, in test2, the else branch cannot narrow the type to HttpResponse<403>.

🙂 Expected behavior

test1 and test2 are just narrowing down result using complementary type guards, their behavior should be consistent.
And they are consistent in 4.1.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked TypeScript Playground reproduction and compare the complementary resultIsSuccess and resultIsFailure guards in test() and test2(). Trace how the type checker narrows the else branches; done means both examples consistently narrow the complementary union member as expected in the reported TypeScript versions.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.