You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Assertion functions cannot be used to narrow discriminated union types #37241
Recently I tried to get creative with an assert function, but ran into a shortcoming. I think I understand why this is the behavior, but it would be nice if it wasn't the case.
TypeScript Version: 3.9.0-dev.20200304
Search Terms: assertion assert function discriminated union narrowing
Code
interfaceCat{type: 'cat';canMeow: true;}interfaceDog{type: 'dog';canBark: true;}typeAnimal=Cat|Dog;functionassertEqual<T>(value: any,type: T): asserts value is T{if(value!==type){thrownewError('Not equal!');}}constanimal={type: 'cat',canMeow: true}asAnimal;assertEqual(animal.type,'cat'asconst);// Erroranimal.type;// "cat"animal.canMeow;// Property 'canMeow' does not exist on type 'Dog'// Worksif(animal.type!=='cat'){thrownewError();}animal.canMeow;// true
Expected behavior:
Calling assertEqual(animal.type, 'cat') would infer animal as a Cat.
Actual behavior:
Only animal.type by itself is inferred; the type narrow does not "propagate" up to the union type.
changed the title [-]Assertion functions cannot be used to narrow union types[/-][+]Assertion functions cannot be used to narrow discriminated union types[/+]on Mar 6, 2020
I'm having a similar problem. It's not totally clear to me if your example exposes the same behavior, but it seems very similar: the type is not narrowed by the assertion.
interface Ok extends String {
_nominal: null;
}
function assertIsOk(foo: string | Ok): asserts foo is Ok {
if (foo !== "ok") {
throw new Error("not ok");
}
}
function acceptsOnlyString(foo: string) {
...
}
function checkIfOk(baz: string) {
assertIsOk(baz);
// compiles - `baz` is now type "string & Ok"; expected it to be type "Ok" and would fail
acceptsOnlyString(baz);
}
Note for a workaround in the first example, a more generic asserts value works for the original example situation, since that asserts the entirety of the statement as true, like an 'if' would
interface Cat {
type: 'cat';
canMeow: true;
}
interface Dog {
type: 'dog';
canBark: true;
}
type Animal = Cat | Dog;
function assert(value: any): asserts value {
if (!value) {
throw new Error();
}
}
const animal = { type: 'cat', canMeow: true } as Animal;
assert(animal.type === 'cat');
animal.type; // "cat"
animal.canMeow; // Works
It seems to be affecting other union types, as the code below demonstrates.
Code
typeMyUnion={[key: string]: any}|string[];functionassertObject(value: any): asserts value is Record<string,any>{if(value===null||typeofvalue!=="object"||Array.isArray(value)){thrownewError("Assertion failed");}}functiongetValue(): MyUnion{return{foo: "bar"};}constvalue=getValue();assertObject(value);// Error as it does not know if it is a string[] or { [key: string]: any }console.log(value["propName"]);
I've also observed that TypeScript is using the already known type at caller (i.e. MyType) to narrow after the assert function is called. This is resulting in never as a result.
I am not sure this part is expected.
typeMyType=|{[key: string]: {[k: string]: number;};}|string;consta: MyType={foo: {bar: 123}};functionassertUndefined(value: any): asserts value is undefined{if(value!==undefined)thrownewError();}assertUndefined(a.foo);// `a.foo` is `never`console.log(a.foo);
Activity
[-]Assertion functions cannot be used to narrow union types[/-][+]Assertion functions cannot be used to narrow discriminated union types[/+]jamietre commentedon Mar 6, 2020
I'm having a similar problem. It's not totally clear to me if your example exposes the same behavior, but it seems very similar: the type is not narrowed by the assertion.
Works the same way when coded as a type guard.
BobElDevil commentedon Mar 9, 2020
Note for a workaround in the first example, a more generic
asserts valueworks for the original example situation, since that asserts the entirety of the statement as true, like an 'if' wouldwmaca commentedon Mar 18, 2020
It seems to be affecting other union types, as the code below demonstrates.
Code
Playground Link: here
More details
I've also observed that TypeScript is using the already known type at caller (i.e.
MyType) to narrow after the assert function is called. This is resulting inneveras a result.I am not sure this part is expected.