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
, thenT
is eitherany
orunknown
. unknown
isn't assignable to anything, so ifT
is assignable to anything, then it must beany
, otherwise it'sunknown
.- Need
[T] extends [0]
instead ofT extends 0
or else the result isany[] | unknown[]
. This is becauseT extends 0 ? ... : ...
is distributive, and apparentlyany
always distributes over both branches of a conditional type.
- Need
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.