Repository navigation
Distinguish missing and undefined #13195
Description
TypeScript Version: 2.1.4
Code
Current, with --strictNullChecks on, the typescript compiler seems to treat
interface Foo1 {
bar?: string;
}
and
interface Foo2 {
bar?: string | undefined;
}
as being the same type - in that let foo: Foo1 = {bar: undefined}; is legal. However, this does not seem to be the original intent of the language - while some people have used ? as a shortcut for | undefined (e.g. #12793 (comment)), it seems that the intent of the operator was to signify that the property either did not appear, or appeared and is of the specified type (as alluded in #12793 (comment)) - which might not include undefined - and in particular it would let us write types that either have a property that, if enumerable is not undefined, or is not present on the object.
In other places it might make sense for ? to keep serving as a shorthand for | undefined, e.g. for function parameters. (or maybe for consistency it would make sense to stick with it meaning "always pass something undefined if you pass anything"? not sure what's best there. Happy to discuss.)
Expected behavior:
interface Foo1 {
bar?: string;
}
function baz(bar: string) {};
let foo: Foo1 = {bar: undefined}; // type error: string? is incompatible with undefined
if (foo.hasOwnProperty("bar")) {
baz(foo.bar); // control flow analysis should figure out foo must have a bar attribute here that's not undefined
}
if ("bar" in foo) {
baz(foo.bar); // control flow analysis should figure out foo must have a bar attribute here that's not undefined
}
Actual behavior:
interface Foo1 {
bar?: string;
}
function baz(bar: string) {};
let foo: Foo1 = {bar: undefined}; // compiler is happy
if (foo.hasOwnProperty("bar")) {
baz(foo.bar); /* error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'*/
}
if ("bar" in foo) {
baz(foo.bar); /* error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'*/
}
Activity
felixfbecker commentedon Dec 28, 2016
I agree, in JS there is a difference between the two
masonk commentedon Dec 29, 2016
I wrote that comment you referenced. I go back and forth on this. The devil's advocate position is that, under the proposed stricter interpretation of
?, I'll still be able to assign{}toFoo, and thus, I still must treatfoo.barasstring | undefinedat all places in my code. The only thing I can see being achieved with this higher level of strictness is a new way of type guarding a value for which a perfectly good type guard is already available.Instead of looking at keys, you can accomplish the type inference you want right now by looking at values.
So, if there is a specific key in mind, there is already a clean way of type guarding it.
Is the difference more pertinent when enumerating a whole object by keys? I couldn't construct a case in which it is.
Possibly some meaningfully different expression could be constructed using lookup types. But I almost think it's a code smell to care about whether a key exists on an object.
The bird's eye view is that JavaScript intends for the developer to equivocate between "no key" and "key: undefined". Thinking about the existence of a key is generally not a very useful paradigm in JavaScript. Instead, what really matters are the possible types of the values. So while this new way might be more faithful to the mechanics of JavaScript, it's not more faithful to the intended usage of JavaScript.
felixfbecker commentedon Dec 29, 2016
I have met JavaScript libraries that used
'prop' in optionsoroptions.hasOwnProperty('prop')to check for the presence of an option, and then when you passundefined(because you just pass your own optional parameter for example) it results in bugs TypeScript didn't catch.masonk commentedon Dec 29, 2016
That's a strong argument. It's good for TypeScript's type system to be able to express whatever is possible in JS, whether or not those possibilities are a good idea.
dgoldstein0 commentedon Jan 21, 2017
yeah... if I was designing javascript from scratch, I'd probably leave out the whole concept of
undefined. But it exists in js, and I don't think typescript is willing to try to change that, so we have to live with it. Making it possible to distinguish, in the type system, between an attribute existing or not without making that attribute optionally undefined would be useful, since we are working with a language that has these (perhaps unnecessary) complexities.[-][enhancement] distinguish between optional members and undefined members[/-][+]Distinguish missing and undefined[/+]KiaraGrouwstra commentedon Jul 20, 2017
The current missing/undefined convolution does seem potentially problematic...
jcalz commentedon Aug 29, 2017
Is it possible to introduce
voidto mean "missing" and leaveundefinedas undefined? So{ foo?: string }would be equivalent to{ foo: string | void }?OliverJAsh commentedon Mar 14, 2018
Where does this issue currently stand? Is it something under consideration, or are there reasons why we can't fix this?
OliverJAsh commentedon Mar 14, 2018
Update: separate issue with reduced test case #35983
Another example where TypeScript's inability to distinguish missing from
undefinedleads to inconsistencies between the types and JavaScript's actual behaviour is usage of object spread.A real world example of this pattern is a
withDefaultshigher-order React component.Ideally, this would throw a type error:
On the other hand, if TypeScript did purposely not distinguish between missing and undefined, TypeScript should instead emit an error when spreading:
OliverJAsh commentedon Mar 21, 2018
Fabianopb commentedon May 15, 2018
I believe this is related to an issue I'm having exporting a hoc
withRouterfrom a component library to another project (using TS 2.8.3).This hoc has own props like (notice that
prop3isoptional):which causes the component to be default exported in the compiled code as:
Raising the error:
I had to work around making the optional props mandatory, explicitly accepting undefined as their union type:
Does this make sense? Is this still up?
leighman commentedon Jun 8, 2018
It would be useful now to be able to define whether a property should be on an object or not based on some conditional type.
You can use
undefinedcurrently but as discussed it's not quite the same.50 remaining items
🤖 Merge PR DefinitelyTyped#46226 react-big-calendar: improve types, f…
alexweej commentedon Jan 20, 2021
Any updates on this one? Does seem to be unfortunate that
permits 3 possible runtime states to handle.
OliverJAsh commentedon Feb 9, 2021
I scanned through all of the above comments to see if somebody else had already mentioned this use case regarding index signatures, but I couldn't find anything, so here goes.
Update: the above error no longer reproduces in 4.2 due to this change: https://devblogs.microsoft.com/typescript/announcing-typescript-4-2-rc/#relaxed-rules-between-optional-properties-and-string-index-signatures (#41921).
and
Here is the same issue reported on StackOverflow: https://stackoverflow.com/questions/64505407/coercing-type-with-optional-properties-to-an-indexable-type
If TypeScript distinguished between missing and
undefinedthen I believe this would not produce an error. That is because the optional property would not addundefinedto the property value type.Real world use case: I'm trying to convert an object with an optional property into a
Jsontype.Workarounds I'm aware of:
JsonRecord, addundefinedto the index signatureinterface JsonRecord { - readonly [key: string]: Json; + readonly [key: string]: Json | undefined; }undefinedvalues, but this type will allow objects which containundefinedvalues.JsonRecord, replace the index signature with a mapped type and mark all properties as optional:undefinedvalues, due to the fact that TypeScript does not distinguish between missing andundefined. Example of a library using this workaround: Allow all keys to be optional in theJsonObjecttype sindresorhus/type-fest#65evelant commentedon Feb 17, 2021
Here's a stripped down version of a real world example I just ran into:
In this example the
_.compactis superfluous. There are never going to be keys with undefined values returned frommakeSomeDefaultsbut TS does not allow me to describe that.vikingair commentedon Feb 17, 2021
Here's another real world example from one of my libraries called react-use-sub.
The latter operation would make the type of
fooon state invalid if I would override it. Hence I have to ignore anyundefinedvalues and optional values have to be done withnullinstead 👎This was the one of the few things that was better handled by Facebooks flow.
InExtremaRes commentedon Mar 18, 2021
Sorry for a new not-so-relevant post but I just wanted to reply to last @fdc-viktor-luft comment.
The specific case you presented is better handled by
Pickinstead ofPartial. I would made a signature more like this:Now TS will fail if you pass
undefinedto a prop that doesn't allow it explicitly. See playground. BTW, this is howsetStateis typed in React.That being said I really believe that differentiate "missing" from
undefinedis necessary. Wish to see some progress on this.vikingair commentedon Mar 19, 2021
Daniel Contreras (@InExtremaRes) Wow 🤩 Thanks a lot. That really solves my particular problem, but it's not very obvious 😄
I totally agree with you 👍
kahoot-karl commentedon Apr 16, 2021
Sharing my workaround:
ahejlsberg commentedon May 4, 2021
This feature is now implemented in #43947.
MartinJohns commentedon May 4, 2021
I didn't expect this feature coming any time soon, especially since it's not even on the roadmap, and then BAM! Anders suddenly drops a feature-bomb on us.. again! ❤️