Comments (13)
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 Foo
objects)
#20863: excess property checks don't occur for non-discriminated unions
#34975: the in
operator is intentionally unsound
from typescript.
both objects are
Foo
with abar
andzaz
excess property respectively?
That's correct. And excess property checks don't kick in as per #20863 (linked by jcalz already).
In that case, isn't it the narrowing that cannot be trusted since it shouldn't allow you to narrow it down to
Bar
in the function declaration since it could beFoo
with an excess property?
jcalz already linked the relevant issue here too: #34975. The in
operator is intentionally unsound. You either accept this, or check the entire structure of your object, or (IMO the best option) add a discriminator property to your types.
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... how it works with the code you have already.
from typescript.
from typescript.
There is #55143.
from typescript.
@MartinJohns I think we've had this disagreement before about things like {bar?: never}
. If the possibility of a getter that throws is something to worry about then you always have to worry about it because every type T
is equivalent to T | never
. I would just check typeof props.bar !== "undefined"
(instead of using in
, which I agree isn't the right check) and if it blows up there then I'm fine, right?
from typescript.
See also the FAQ:
(Indirect) Excess Properties Are OK
What is structural typing?
from typescript.
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 Foo
with a bar
and zaz
excess property respectively?
In that case, isn't it the narrowing that cannot be trusted since it shouldn't allow you to narrow it down to Bar
in the function declaration since it could be Foo
with an excess property?
In which case the only way to narrow it would be to eliminate Foo
?
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?
from typescript.
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.
from typescript.
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 never
does not mean the property doesn't exist..
from typescript.
@MartinJohns Is there an issue for having a syntax for "property does not exist" ? That seems like less of a lift than #12936 right?
from typescript.
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 bar
? Clearly it's an excess property of Foo
and TypeScript knows that and does it correctly when it's a different property name.
from typescript.
looks meaningfully at #20863
from typescript.
@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!
from typescript.
Related Issues (20)
- Inferred project that currently use "current directory of tsserver host" needs some special handling
- Unable to use rest on generic that has been narrowed to an object HOT 5
- Error out if using node builtin modules and global variables in browser environment HOT 2
- False positive for "Unreachable code detected" for code following `switch`/`case` with `allowUnreachableCode: false` HOT 3
- "const" was transformed to "var" when target is "esnext" HOT 3
- Understanding type checker cache HOT 5
- Allow `.js` file generated from `.tsx` file to be runnable in browser with less configuration HOT 5
- TS7022 (implicity 'any' type) popping up in a random scenario HOT 10
- Add `preparePaste` method to check if copied text should potentially have smart paste enabled or not HOT 1
- Organize Imports feature sort order disagrees with eslint import/order HOT 3
- Allows private property access without warnings HOT 1
- Overload order affects assignability of abstract constructors overloaded with regular constructors in interfaces
- Removing properties signatures from 'this' HOT 6
- Unexpected compile error when accessing prop via super.prop HOT 7
- Allow marking `export { x as y }` as `@deprecated` HOT 1
- [NewErrors] 5.7.0-dev.20240904 vs 5.5.4 HOT 69
- Provide option to not include `sourceMappingURL` in the generated `.js` file when `sourceMap` is `true` HOT 3
- [ServerErrors][JavaScript] 5.7.0-dev.20240904 vs 5.5.4 HOT 13
- [ServerErrors][TypeScript] 5.7.0-dev.20240904 vs 5.5.4 HOT 18
- Removing optional modifier in homomorphic mapped types does not work in generic contexts since 5.5.x
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typescript.