0

Here is the code:

const arr = [1,2,3]
const res1 = arr.slice()
const res2 = Object.assign([],arr)

If I do a shallow clone with arr.slice(), then I will get a new array res1 of type number[], which is the same as arr. But if I do that with Object.assign(), what I get is an array res2 of type never[] & number[].

Why the types of res2 will contain the type never[]? And how could it be the type of number[] while it also be the type of never[] (by &)?

Chor
  • 833
  • 1
  • 5
  • 14
  • The TypeScript intersection type return for `Object.assign[]` is an approximation of what actually happens. So `Object.assign({a: 1}, {b: 2})` will be of type `{a: number} & {b: number}` which is correct, but `Object.assign({a: 1}, {a: ""}` will be of type `{a: number} & {a: string}` which is incorrect. It's not always correct, but it's useful in many circumstances. An empty array `[]` will sometimes be inferred as type `never[]` (it is "auto-typed" if you set props or push to it, but you're not doing that here) and so you have this result. I'd do [this](//tsplay.dev/wEGBgm) instead. – jcalz Nov 22 '21 at 01:15
  • If that makes sense and answers your question I could write up an answer. If not, please [edit] the question to explain what's missing so someone can address it. – jcalz Nov 22 '21 at 01:17
  • 1
    If you're looking for another way to create a shallow array copy, you can simply use `[...arr]` – jsejcksn Nov 22 '21 at 01:21
  • @jcalz I think it makes sense. But one question I still don't understand is that "An empty array [] will sometimes be inferred as type never[]". What `sometimes` actually means? Also, for `const arr = []`, the type can be inferred as `any[]`. Why in this question it is `never[] & number[]` instead of `any[] & number`? – Chor Nov 22 '21 at 01:28
  • It is not really `any[]` if you're using `--strictNullChecks` and `--noImplicitAny`, it is *auto-typed*, meaning the compiler *evolves* it later based on what you put into it (see [the docs](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#example-4)). So it's based on control flow, like [this](https://tsplay.dev/mZbXJm). That `any[]` you see with quickinfo is not really accurate. If you have an empty array and the compiler needs to infer a type for it, it will tend to be `never[]`. – jcalz Nov 22 '21 at 01:32
  • This is mentioned in the https://stackoverflow.com/questions/54117100/why-does-typescript-infer-the-never-type-when-reducing-an-array-with-concat question that @Kaiido wants to close this one as a duplicate of. You kind of have *two* questions here... "why does `[]` get inferred as `never[]`" and "why does `Object.assign()` return an incorrect intersection type"? Which one is your primary question? – jcalz Nov 22 '21 at 01:33
  • See https://github.com/microsoft/TypeScript/issues/29795 for the `never[]` vs `any[]` issue – jcalz Nov 22 '21 at 01:36
  • @jcalz Thanks. From the reference you gave I have learned about some useful infos. The reason why `arr` being type `any[]` in `const arr = []` is because after declaring `arr`, we still have a chance to add all kinds of members to that array. And TS could use control flow analysis to extend its type gradually. So initial type being `any[]` means the possible extendable. – Chor Nov 22 '21 at 02:35
  • @jcalz But for `Object.assign([],arr)`, `[]` will be kept being an empty array for a while. And it is difficult to use control flow analysis to do the inference job. So in this condition, `[]` is inferred as type `never[]` instead of `any[]`. It is actually a empty union. But union type shouldn't be empty according to union's definition, so in this case, it has to be `never[]`. – Chor Nov 22 '21 at 02:37

1 Answers1

0

Take a look at the type of Object.assign:

Object.assign<T, U>(target: T, source: U): T & U

I.e. the returned type is the intersection of whatever objects you pass as target and source. Without explicit type declarations typescript has to infer the types of the passed arguments here. For arr this is correctly inferred as number[] but for the first argument/target - since it is an empty array and so the type of the non-existent elements can not be inferred - it is inferred as never[], thus resulting in the return type never[] & number[].

You could achieve your expected result by explicitly giving the type of target, e.g.:

const res2 = Object.assign([] as number[], arr)
acran
  • 7,070
  • 1
  • 18
  • 35
  • But if I write `const arr = []`, the type can be inferred as `any[]`. Why in this question it is `never[] & number[]` instaed of `any[] & number`? – Chor Nov 22 '21 at 01:25
  • It's an [intersection](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types), not a [union](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types). – jcalz Nov 22 '21 at 01:28
  • @jcalz you're correct. I updated the answer to fix this error. – acran Nov 22 '21 at 12:04