-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Narrowing discriminated unions with user-defined type guards #11787
Comments
What is |
@aluanhaddad I think that's just a typo for |
@timruffles It doesn't seem like what you're proposing generalizes well. Your What you want to do can be accomplished with the existing type system, it is just somewhat awkward: type Cheese = {
type: 'brie';
brieOnly: true,
} |
{
type: 'cheddar';
cheddarOnly: true,
};
function narrows<Wide, Narrow extends Wide>(w: Wide, f: (w: Wide) => Narrow): w is Narrow {
return f(w) !== undefined;
}
function doSomethingWithCheese(cheeseSample: Cheese) {
if (cheeseSample.type === 'cheddar') {
cheeseSample.cheddarOnly;
}
if (narrows(cheeseSample, c => c.type === 'cheddar' ? c : undefined)) {
const t: 'cheddar' = cheeseSample.type;
cheeseSample.cheddarOnly;
}
} Right now I'm relying on type inference to make this work, but the fact that you have to use |
This is still an issue in 3.x. I had a slightly simpler variant that didn't have a type-parameterized type guard, but I think it's the same basic issue: interface Foo {
type: "foo";
foo: string;
}
interface Bar {
type: "bar";
bar: string;
}
type FooBar = Foo | Bar;
function isFooType(t: "foo" | "bar"): t is "foo" {
return true;
}
const f: FooBar = null as any;
if (f.type === "foo") {
f.foo; // works
}
if (isFooType(f.type)) {
f.foo; // breaks
} This is a bummer, cause it means that a user-defined type guard deep in the type hierarchy may get bubbled up with "wrapper" type guards for containing types in order to discriminate them: function isFoo(foobar: Foo | Bar): foobar is Foo {
return isFooType(foobar.type);
} |
@masaeedu the If you re-read my example you'll see it's the same narrowing & inference logic as is used in the primitive example |
Also running into this issue as of interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Rectangle; Using a basic Demonstrates expected discriminated union type inference: const shape1: Shape = JSON.parse('{ "kind": "square", "size" : 10 }');
console.log(shape1.kind); // compiles, as expected
console.log(shape1.size); // compiler error, as expected
if (shape1.kind === "square") {
console.log(shape1.size); // compiles, as expected
} Demonstrates incorrect discriminated union type inference using type guard: const isShape = (value: any): value is Shape => typeof value === 'object' && value !== null && typeof value.kind === 'string' /* && etc... */;
const shape2: unknown = JSON.parse('{ "kind": "square", "size" : 10 }');
if (isShape(shape2)) {
console.log(shape2.kind); // compiles, as expected
console.log(shape2.size); // compiler error, as expected
if (shape2.kind === "square") {
console.log(shape2.size); // compiler error, NOT expected
}
} |
The Alternately, duplicate #30557 |
TypeScript Version: 2.0.3
Code
Expected behavior:
Discriminated unions would work with type-guards in this way.
Actual behavior:
It doesn't, and I have to write a load of stuff like this in unit-tests:
rather than doing it in one step with an assertion throwing version of
narrows
:The text was updated successfully, but these errors were encountered: