-
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
Distinguish missing and undefined #13195
Comments
I agree, in JS there is a difference between the two const a = { prop: undefined };
a.prop // undefined
a.hasOwnProperty('prop') // true
'prop' in a // true
for (k in a) {
console.log(k) // logs 'prop'
}
const b = { };
b.prop // undefined
b.hasOwnProperty('prop') // false
'prop' in b // false
for (k in b) {
console.log(k) // never hit
} |
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 Instead of looking at keys, you can accomplish the type inference you want right now by looking at values. interface Foo {
bar?: string;
}
function baz(bar: string) {};
let foo1: Foo = {bar: undefined}; // would become type error: string? is incompatible with undefined
let foo2: Foo = {}; // Would still, however, be allowed
if (typeof foo1.bar !== 'undefined') {
baz(foo1.bar); // Control flow already correctly narrows type to string
baz(foo2.bar); // Control flow correctly warns that this could be undefined (under strictNullChecks)
} 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. for (let k of foo) {
let val = foo1[k]; // Would still be any under strictOptionalMember, and still an error under noImplicitAny
} 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. |
I have met JavaScript libraries that used |
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. |
yeah... if I was designing javascript from scratch, I'd probably leave out the whole concept of |
The current missing/undefined convolution does seem potentially problematic...
|
Is it possible to introduce |
Where does this issue currently stand? Is it something under consideration, or are there reasons why we can't fix this? |
Update: separate issue with reduced test case #35983 Another example where TypeScript's inability to distinguish missing from A real world example of this pattern is a type Props = {
foo: string;
bar: string
}
type InputProps = {
foo?: string;
bar: string;
}
const defaultProps: Pick<Props, 'foo'> = { foo: 'foo' };
const inputProps: InputProps = { foo: undefined, bar: 'bar' };
// Type of `foo` property is `string` but actual value is `undefined`
const completeProps: Props = { ...defaultProps, ...inputProps };
Ideally, this would throw a type error: // `{ foo: undefined; bar: string; }` is not assignable to `{ foo?: string; bar: string; }`
const inputProps: InputProps = { foo: undefined, bar: 'bar' }; On the other hand, if TypeScript did purposely not distinguish between missing and undefined, TypeScript should instead emit an error when spreading: // `{ foo: string | undefined; bar: string }` is not assignable to `Props`
const completeProps: Props = { ...defaultProps, ...inputProps }; |
This comment has been minimized.
This comment has been minimized.
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 set<S, K extends keyof S>(state: Pick<S, K>): void; Now TS will fail if you pass That being said I really believe that differentiate "missing" from |
@InExtremaRes Wow 🤩 Thanks a lot. That really solves my particular problem, but it's not very obvious 😄 I totally agree with you 👍 |
Sharing my workaround:
|
This feature is now implemented in #43947. |
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! ❤️ |
TypeScript Version: 2.1.4
Code
Current, with
--strictNullChecks
on, the typescript compiler seems to treatand
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 includeundefined
- 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:
Actual behavior:
The text was updated successfully, but these errors were encountered: