2

I can't understand why the type of nVal1 can be inferred on each position, while nVal2 can't be.

enter image description here

function rSelf1<T extends unknown[] | [unknown]>(vs: T): T {
  return vs;
}

function rSelf2<T extends unknown[]>(vs: T): T {
  return vs;
}

const nVal1 = rSelf1(["a", 1, 2, true]);
const nVal2 = rSelf2(["a", 1, 2, true]);

code link: https://codesandbox.io/s/typescript-playground-export-forked-t1d8o?file=/index.ts

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
zhilian
  • 155
  • 2
  • 11

1 Answers1

2

(A whole bunch of idk the answer but here's some digging I did to maybe give you some context and related info)

This both baffles me and excites me. Essentially you made me realize "unknown[] | [unknown]" can be used to infer tuples. I always wanted to do this. This does seem hacky and I cant figure out how this works. I found this ts-essentials type for tuple. Then I did a little more digging and found this github reply explaining how this works. This helps us a bit. We now know that "unknown[] | [unknown]" forces the complier to infer the tuple type. But why? I dont know that yet. Cant seem to find anything to explain this behavior.

My thought before I did some research on this. So I cant directly answer your question, but I came across this related stackoverflow post. You can use rest parameters to easily infer tuple types. I like this method because it seems its something the typescript creators intended. But it adds runtime boiler plate and thats not good.

I found this other way(solution 2) to do this, but still not a fully proper solution. With this one you require the user of the function to do "as const" on the array.

// solution 1
function tuple<T extends unknown[]>(...v: T) {
  return v;
}
function rSelf3<T>(vs: T): T {
  return vs;
}
const nVal3 = rSelf3(tuple("a", 1, true));

// solution 2
type Writable<T> = { -readonly [P in keyof T]: T[P] };
function rSelf4<T extends readonly unknown[]>(vs: T): Writable<T> {
  return vs;
}
const nVal4 = rSelf4(["a", 1, true] as const);
zhilian
  • 155
  • 2
  • 11
orey chandan
  • 43
  • 1
  • 5
  • 1
    I've modified the solution 1 slightly to make it work and concise. `export type Tuple = [T] | T[];` This definition really inspire me! I can't find anything about this "magic" in official docs but it really work. So, maybe, the author leave this special grammar to make life simple. PS: the question initially comes from [the definition of Promise.allSettled](https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2020.promise.d.ts#L33) – zhilian Jan 10 '22 at 02:31