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

Discriminated unions is not work when field key is number #57245

Open
NWYLZW opened this issue Jan 31, 2024 · 9 comments
Open

Discriminated unions is not work when field key is number #57245

NWYLZW opened this issue Jan 31, 2024 · 9 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@NWYLZW
Copy link

NWYLZW commented Jan 31, 2024

πŸ”Ž Search Terms

  • discriminated unions, "discriminated-unions"
  • "numbers as keys in object", "number key"
  • "type guard"

πŸ•— Version & Regression Information

  • This is a crash

⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBAglC8UB2BXAtgIwgJwNoF0oAfKAZ2GwEskBzAgKHtEigCEEoBvKABgC5k6LNgA0UAIYCAjFAC+xLrwHkqtMZKgByKZrmMAJhADGAG3HZoRgPZJyEgTHqHT5yzbsYBrRpQBmUAApmCCt-cVweQnhorVRMHE0ASi56KDSoa1srEwgAOhMrGgDxRNT0gHpy9Or0gD0AfnpZej9A4NCoDFxNcU0omM044SSU6szSbLyCooxS6sqamoamlv8g8BD-Ls0ePoQBoYTkzjK08cn8woDZ06gFxbrG5tb1yA6uyP3EQaEj0fTzjlLjM5hUqg80stmkA

πŸ’» Code

type A = number[] | string[]

type B = { 0: number, a: 1 } | { 0: string, a: '1' }

declare const a: A
declare const b: B

if (typeof a[0] === 'number') {
    console.log(a)
    //          ^? A
}
if (typeof b['a'] === 'number') {
    console.log(b)
    //          ^? { 0: number, a: 1 }
}
if (typeof b['0'] === 'number') {
    console.log(b)
    //          ^? B
}
if (typeof b[0] === 'number') {
    console.log(b)
    //          ^? B
}

πŸ™ Actual behavior

Unable to type guard b as an option of the union type B.

πŸ™‚ Expected behavior

Type b as { 0: number, a: 1 } when type guarding it with typeof b['0'] === 'number'.

Additional information about the issue

@NWYLZW
Copy link
Author

NWYLZW commented Jan 31, 2024

It seems that there is no restriction in the documentation that keys must be strings.

@fatcerberus
Copy link

fatcerberus commented Jan 31, 2024

It's not about the keys being strings or not - discriminants must be literal-typed. number and string are not valid discriminants, and you also can't do a discriminant check via typeof.

@NWYLZW
Copy link
Author

NWYLZW commented Jan 31, 2024

image
typeof works in some cases, but I completely don't understand it.

@NWYLZW
Copy link
Author

NWYLZW commented Jan 31, 2024

image image

?????????????????

@browsnet
Copy link

https://www.typescriptlang.org/play?ts=5.4.0-dev.20240131#code/C4TwDgpgBAglC8UDeUBeAuKA7ArgWwCMIAnKAXygB9k1MBnYYgSywHNyoAoUSKAIQQ0M2fEVIVqKYQQD2MgDYQAhlg6cAJhADG8pcWhaZWBlCWYYG7bv1RDx4FAKY+nTkwBmUABQ8IMz0oA2gDkqMEAugjwiMGyCspYwQCUyJxQ6bZGdPEAdPIyrF5KSWkZAPRlGVUZAHoA-Jxkbp4+4H4BIWGR0TG4hCTJqVV22Yp5BUUlVRXV1fWNrh7evv6OnRFRMXGKKoNIpekjufmFBFPllbO1DU1LrZCrBOvd0VDBfWJ7B5nGxxNn3xmV3S8yaQA

image

It seems that only specific union types trigger this bug. In this example, only the union of number and string triggers the bug, probably because it is inferred as number | string. When I change it to another primitive type union or a literal type, Discriminated works successfully.

@whzx5byb
Copy link

boolean is treated as true | false internally, which is literal-typed, and make the property discriminated.

@fatcerberus
Copy link

This is the first I'm seeing that typeof works to narrow a discriminated union. That's highly unorthodox (and in this case, even misleading). Maintainers have always said discriminated union narrowing only works with direct value comparisons (i.e. x[0] === 'foo').

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Feb 1, 2024
@RyanCavanaugh
Copy link
Member

Narrowing via type on discriminated unions works by looking for properties that look like discriminant properties... but string[] and number[] don't have any such properties! It could be done, but just isn't right now.

@NWYLZW
Copy link
Author

NWYLZW commented Feb 2, 2024

One strange thing here is that if it's not a union type with only string[], there is a partial solution to this problem.

type A = ({ [0]: number } & number[]) | ({ [0]: boolean } & boolean[])

declare const a: A

if (typeof a[0] === 'number') {
    console.log(a)
    //          ^? { [0]: number } & number[]
}
if (typeof a[0] === 'boolean') {
    console.log(a)
    //          ^? { [0]: boolean } & boolean[]

Playground Link

If we replace boolean[] with string[], then a problem will arise.

type A = ({ [0]: number } & number[]) | ({ [0]: string } & string[])

declare const a: A

if (typeof a[0] === 'number') {
    console.log(a)
    //          ^? A
}
if (typeof a[0] === 'string') {
    console.log(a)
    //          ^? A

Playground Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants