Skip to content

Instantly share code, notes, and snippets.

@laughinghan
Last active January 18, 2020 21:08
Show Gist options
  • Save laughinghan/c9f6d24ee253c9c111501fa5116d2bad to your computer and use it in GitHub Desktop.
Save laughinghan/c9f6d24ee253c9c111501fa5116d2bad to your computer and use it in GitHub Desktop.

In microsoft/TypeScript#28916, all the proposed typings including mine (which I believe to fix some problems) narrow unknown to any[], which can be illustrated by expanding the fn2() test case to:

function fn2(arg: unknown) {
  if (Array.isArray(arg)) {
    arg.push(""); // Should OK
    var n: number;
    n = arg[0]; // Should this OK or FAIL?
                // Should arg have been narrowed to any[] or unknown[]?
  }
}

Arguably, if someone is using unknown, they probably want to remain type-safe even after using Array.isArray() to narrow the type of arg, and therefore it ought to be narrowed to unknown[]. However, this is a breaking change from the old Array.isArray(arg: any): arg is any[] type signature, and would also be inconsistent with object and {}, which are more restrictive and arguably should be even more type-safe than unknown. Maybe in a future, breaking update to TypeScript, though, all 3 of them (unknown, object, and {}) would all be narrowed to unknown[]?

Furthermore, the only way I've figured out to do this is really ugly:

 unknown extends T ? (
-  Extract<any[], T>
+  [T] extends [0] ? Extract<any[], T> : Extract<unknown[], T>
 ) : (
   T extends readonly any[] ? T : T & any[]
 )

Explanation:

  • If unknown extends T, then T is either any or unknown.
  • unknown isn't assignable to anything, so if T is assignable to anything, then it must be any, otherwise it's unknown.
    • Need [T] extends [0] instead of T extends 0 or else the result is any[] | unknown[]. This is because T extends 0 ? ... : ... is distributive, and apparently any always distributes over both branches of a conditional type.

You might be wondering, wouldn't it be way simpler to just do Extract<T[], T>, so any gets narrowed to any[] and unknown gets narrowed to unknown[]? Yes, that would be much less ugly if it worked, but that actually breaks test fn16() (contrast with the uglier way that works), which is an important test; similar versions of tests fn5() thru fn11() would all also fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment