-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Union types (Foo | Bar) inconsistently becomes Partial<Foo & Bar> #59819
Comments
TS is working as designed and you've run into a known inconsistency/unsoundness in the language. #12936: object types in TS are not "exact" (e.g., the objects you don't like are both valid |
See also the FAQ: |
If I'm understanding that correctly in these two failing cases: baz({ foo: "test", bar: 123 }); // undefined
baz({ foo: "test", zaz: "dsfdf1" }); // test both objects are In that case, isn't it the narrowing that cannot be trusted since it shouldn't allow you to narrow it down to In which case the only way to narrow it would be to eliminate interface Foo {
foo: string;
jaz: number;
}
interface Bar {
bar: number;
zaz: string;
}
function baz(props: Foo | Bar ) {
if (!("foo" in props)) {
console.log(props.zaz);
}
else {
console.log(props.jaz);
}
}
baz({ bar: 123, jaz: 1234, foo: "blah" }); // 1234 Ideally, I'd like user to specify one object or the other when calling the function and have it fail if it doesn't have all the properties of one of the objects. Is that even possible? |
That's correct. And excess property checks don't kick in as per #20863 (linked by jcalz already).
jcalz already linked the relevant issue here too: #34975. The
That's... how it works with the code you have already. |
I think I found a solution: interface Foo {
foo: string;
zaz?: never;
bar?: never;
}
interface Bar {
foo?: never;
bar: number;
zaz: string;
}
function baz(props: Foo | Bar ) {
if ("bar" in props) {
console.log(props.zaz);
} else {
console.log(props.foo);
}
}
baz({ foo: "test" }); // test
baz({ bar: 1234, zaz: "yuh" }); // yuh
// oops! you can pass a subset of "both" rather than "one or the other".
baz({ foo: "test", bar: 123 }); // FAILS
baz({ foo: "test", zaz: "dsfdf1" }); // FAILS It might be helpful if the docs were updated with something like that. Like you can make your unions safer by specifying all the known properties of the other values. |
I wouldn't call this a "solution", but rather a crude workaround / hack. This call is still legit and will blow up: baz({ foo: "test", get bar(): never { throw 1 } , get zaz(): never { throw 1 } }); A property typed |
@MartinJohns Is there an issue for having a syntax for "property does not exist" ? That seems like less of a lift than #12936 right? |
There is #55143. |
@MartinJohns I think we've had this disagreement before about things like |
FWIW I find it rather unintuitive that Excess Property Checks occur, but not if the property just so happens to be part of another object in the union. Take for instance this execution: baz({ foo: "test", bar: 123 }); // undefined
baz({ foo: "test", saz: "test" }); // FAIL Why is it that excess checks just get disabled if the property happens to be |
looks meaningfully at #20863 |
@jcalz I see, this is the same issue. My apologies, I was tripped up by some of the language used in the issue. Thank you for your help! |
π Search Terms
"union type partial", "type union narrowing"
π Version & Regression Information
β― Playground Link
https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true&ts=5.5.4#code/JYOwLgpgTgZghgYwgAgGIHt3IN4ChkHIyYBcyAzmFKAOYDcuAvrrqJLIigEJxQ76EARrzIgArgFtB0BoWQAvOPLKVqIekxYwxIBGGDoQyYfIAUAByjpz5MhiwAfZDz4BKfnOAxkpgETCoX2RQZEtrcnc8OTkEQ3J0ABsIADoE9BoLKxtkxXlXWUJGZAgE8hQo6IJYkHik1PTM8OTidHyBAmZmXBNTbCJSZF9ISiDGfOQAegnkYbBupV7jEWQARgAmAGYAFgAaBSUyXwBPMQALUfGp5BPTlivMGwBCa-QxZAQ4I3M4cnJkOAoYkEZTAyHQ3n86DA52QUDg0OgM1On0GhhQ6D4CLBCMCyRYPT6LUOs18ewCZHWG2QYzok2mOgAJhAYKAIAz5mZCQMhhARntcocGeQYAyYCsLrSrrMgA
π» Code
π Actual behavior
I was surprised that I would pass in an object that is neither
Foo
norBar
and TypeScript didn't complain about. This caused a bug at runtime that it seems like TypeScript should have caught?Interestingly, the function definition is well aware that only one or the other could be passed. I could not write a scenario where that wasn't the case, but calling the funciton is where this fails.
π Expected behavior
I expected to be able to pass
Foo
orBar
but not both and certainly not neither.Additional information about the issue
I couldn't find a previous version of TypeScript where this worked as I would expect it too. I was really wondering if I was doing something wrong or this was a well known issue, but I was struggling to find anything on the topic.
It looks like this also fails:
in which case
data
is neitherFoo
norBar
but TypeScript doesn't complain about it.You can see this with both of these that fail:
I would think that if they fail seperately, a union should also fail?
The text was updated successfully, but these errors were encountered: