Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object literal types with only optional properties break control flow narrowing #48858

Closed
DaviDevMod opened this issue Apr 27, 2022 · 2 comments · Fixed by #49865
Closed

Object literal types with only optional properties break control flow narrowing #48858

DaviDevMod opened this issue Apr 27, 2022 · 2 comments · Fixed by #49865
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@DaviDevMod
Copy link

DaviDevMod commented Apr 27, 2022

Bug Report

If an object literal type with only optional properties is part of a union, the union can't be narrowed.

I expected the bug to be common enough to have been already discussed somewhere, but the most relevant issue I could find was #31156. I apologise in advance if I'm opening a dupe.

🔎 Search Terms

narrowing union

🕗 Version & Regression Information

The bug can be found in any version of the TypeScript Playground (currently from v3.3.3333 to 4.6.2, including the Nightly v4.7.0-dev.20220427).

⏯ Playground Link

Playground link with relevant code

💻 Code

// Base case. It works as expected.
type CaseOne = 'A' | { optional: false }

const f = (arg: CaseOne) => {
    // `arg` is narrowed to `A` as expected.
    if (arg === 'A') arg;
}

// Any object literal in the union has only optional properties.
type CaseTwo = 'A' | { optional?: true } | { anotherOne?: 2 }

const g = (arg: CaseTwo) => {
    // No narrowing occurs.
    if (arg === 'A') arg;
}

// The union has at least one object literal with at least one required property
// and at least one object literal with only optional properties.
type CaseThree = 'A' | { optional?: true } | { anotherOne?: 2 } | { dizzy: 'eh' }

const h = (arg: CaseThree) => {
    // Unreliable narrowing that depends on how you play with the question marks.
    if (arg === 'A') arg;
}

🙁 Actual behavior

Object literal types with only optional properties break control flow narrowing.

🙂 Expected behavior

It should be possible to narrow unions containing an object literal type with only optional properties.

@andrewbranch
Copy link
Member

Hm, the issue stems from the fact that string literal types are comparable with {} and other weak object types. Comparability strikes again. @DanielRosenwasser do you know of a related/duplicate for this? Seems like the kind of issue that haunts you.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Apr 27, 2022

It's not really specific to comparability, this would happen with assignability too, right?

This does feel like a bug since we already special-case literal types in the negative case.

// Any object literal in the union has only optional properties.
type CaseTwo = 'A' | { optional?: true } | { anotherOne?: 2 }

const g = (arg: CaseTwo) => {
    // Narrowing occurs.
    if (arg !== 'A') arg;

    // Narrowing does not occur.
    else arg
}

https://github.dev/microsoft/TypeScript/blob/bab02d24be4d1821e29c65100e282db26ee3e2c8/src/compiler/checker.ts#L24921-L24929

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
4 participants