Typescript: Equality operator with unknown does not narrow the type of the unknown

Created on 23 Jun 2018  路  8Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.0.0-dev.20180623


Search Terms: typescript unknown equality guard

Code

let x: unknown; // reproduces with const too
if (x === 5) {
    let y = x.toString(10);
}

Expected behavior:
x inside of the if block to be of type 5, y to get type of string, everyone happy.
Actual behavior:
x inside of the if block is still of type unknown, I get an error for x being of type unknown and therefore does not have a property toString.
Playground Link: N/A (dev build)

Related Issues: #24439

Committed Suggestion help wanted

Most helpful comment

@jcalz Not exactly, that there is a feature request, unknown can be narrowed in other ways. The following is the result on the TypeScript version mentioned at the top of this issue:

let x: unknown; // unknown always considered initialized.
if (Array.isArray(x)) {
    x; // any[] (I would expect unknown[], but that's a different issue)
} else if (x instanceof Promise) {
    x; // Promise<any> (again, would expect Promise<unknown> but eh)
} else if (x === true) {
    x; // unknown, wat.
}
let y = String(x); // y: string, as expected
let z = Number(x); // z: number, as expected

So you see, the only real "wat" here is the direct equality comparison.

It's also worth noting that this behavior is consistent with any (That is, attempts to narrow any with equality are met with any as the narrowed type), but this is still rather unexpected behavior.

All 8 comments

Related to #9999 also?

@jcalz Not exactly, that there is a feature request, unknown can be narrowed in other ways. The following is the result on the TypeScript version mentioned at the top of this issue:

let x: unknown; // unknown always considered initialized.
if (Array.isArray(x)) {
    x; // any[] (I would expect unknown[], but that's a different issue)
} else if (x instanceof Promise) {
    x; // Promise<any> (again, would expect Promise<unknown> but eh)
} else if (x === true) {
    x; // unknown, wat.
}
let y = String(x); // y: string, as expected
let z = Number(x); // z: number, as expected

So you see, the only real "wat" here is the direct equality comparison.

It's also worth noting that this behavior is consistent with any (That is, attempts to narrow any with equality are met with any as the narrowed type), but this is still rather unexpected behavior.

@DanielRosenwasser === today doesn't narrow anything other than members from unions or primitives into literals today. unknown is neither a primitive nor a union, so this is expected under our current rules, just like how {} and any won't be narrowed by ===, either.

This is especially helpful for narrowing unknown to a string enum

type Response = 'yes' | 'no' | 'idk';
let validate: (x: unknown) => Response = x => (x === 'yes' || x === 'no') ? x : 'idk'; // Err: type 'unknown' is not assignable to type '"idk"'

Accepting PRs assuming there isn't any big impact to perf or complexity

Attempt at a PR up at #26941

Question: why just unknown, shouldn't any work the same way?

--strict
let a: any = Math.random();
if (a === null) {
    // a is null type, not any type
}
Was this page helpful?
0 / 5 - 0 ratings