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

Empty array ([]) inferred to be never[] instead of [] or undefined[] #51979

Closed
maranomynet opened this issue Dec 20, 2022 · 8 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@maranomynet
Copy link

maranomynet commented Dec 20, 2022

Bug Report

I ran into this behavior where TypeScript infers that an empty array (literal []) to be of type never[], which it interprets to never happen and thus to be of no sigificance.

Runtime errors then ensue.

🔎 Search Terms

"never[]" array

🕗 Version & Regression Information

Affects all versions from 3.5.1 through 4.9.4 (and the nightly)

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about "common-bugs-that-arent-bugs"

⏯ Playground Link

Playground link with relevant code

💻 Code

const ret = Math.random() > 0.5 ? ['foo', 'bar'] as const : [];
//    ^? const ret: readonly ['foo', 'bar'] | never[]

// @ts-expect-error  (because `ret[0]` may be `undefined`)
const foo: string = ret[0]
//                      ^? (property) 0: "foo"
// @ts-expect-error  (because `ret[1]` may be `undefined`)
const bar: string = ret[1]
//                      ^? (property) 1: "bar"

// 🔥🔥 This will throw a runtime error half the time. 🙀
console.log(
    foo.toUpperCase(),
    bar.toLocaleLowerCase()
)

🙁 Actual behavior

I get a TS error on the @ts-expect-error comment as the ret tuple is inferred to be effectively always ['foo', 'bar']

...and then I get runtime errors once I try and do .toUpperCase() on the (sometimes undefined) values.

🙂 Expected behavior

I'd expect the inferred type of ret to be something like readonly ['foo', 'bar'] | [] or possibly readonly ['foo', 'bar'] | | Array<undefined>.

To fix this I have to explicitly change the first line to either:

const ret = Math.random() > 0.5 ? ['foo', 'bar'] as const : [] as [];

…or:

const ret = Math.random() > 0.5 ? ['foo', 'bar'] as const : [] as const;
@maranomynet maranomynet changed the title Empty array ([]) inferred to be never[] instead of undefined[] Empty array ([]) inferred to be never[] instead of [] or undefined[] Dec 20, 2022
@fatcerberus
Copy link

fatcerberus commented Dec 20, 2022

Working as intended. In this context never[] doesn’t mean “never happens” but rather indicates that the array contains only values of type never (which is trivially true—it’s empty).

The never[] typing for empty arrays is inconvenient, but is intentional because the alternatives are worse. See #51853 (comment)

@tylim88
Copy link

tylim88 commented Dec 20, 2022

I am more concerned about array being any[]

const a = false || []
//    ^?
const b = []
//    ^?

image

@fatcerberus
Copy link

@tylim88 It’s not really any[]. It’s an evolving type, so if you .push() something into it, the compiler will infer the type of whatever value(s) you add.

@tylim88
Copy link

tylim88 commented Dec 20, 2022

@fatcerberus

I see, thx for your explanation

before evolve:
image

after evolve:

image

@andrewbranch andrewbranch added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 21, 2022
@maranomynet
Copy link
Author

maranomynet commented Dec 21, 2022

The JS code is obviously buggy, the conditional assignment to res is pretty straightforward, yet TypeScript fails to capture this and somehow this is fine?

Can anyone please help me understand why the runtime error is not a problem?

In fact, even a type of readonly ['foo', 'bar'] | any[] would seem to be more correct (and useful).

@fatcerberus
Copy link

fatcerberus commented Dec 21, 2022

That's completely unrelated to the never[] inference, though. The runtime error still happens even if the inferred type is any[], because there's no bounds checking for array accesses, only for tuple types. You might want to consider enabling noUncheckedIndexedAccess if that's a problem for you.

const ret = Math.random() > 0.5 ? ['foo', 'bar'] as const : [] as any[];
//    ^? const ret: any[] | readonly ['foo', 'bar']

const foo: string = ret[0]
const bar: string = ret[1]

// STILL throws a runtime error half the time.
console.log(
    foo.toUpperCase(),
    bar.toLocaleLowerCase()
)

@maranomynet
Copy link
Author

In fact noUncheckedIndexedAccess makes no difference here, as the inferred type is to be effectively just readonly ['foo', 'bar']. Which is my point.

Not that I'm actively rooting for any but then the untrustworthyness of values pulled out of ret are at least somewhat more apparent

image

Playground link

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants