-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
π Search Terms
union, undefined, keys, non-shared, properties
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
I think unions should enforce that keys can't be assign undefined
if neither half of the union accepts it. If I can't assign an object to either interface, I wouldn't expect to be able to assign it to the union of the 2.
As an example, let's say I have these 2 interfaces:
interface IForumPost {
id: string;
title: string;
description: string;
}
// A new forum post doesn't have an ID yet until it is persisted in the database
interface INewForumPost extends Omit<IForumPost, 'id'> {}
I don't have a scenario where the id
field should be allowed to exist, but be undefined
. Both of these object declarations will throw an error:
const post: IForumPost = {
id: undefined, // error, needs to be a string for this interface
title: 'Hello, world!',
description: 'This is a forum post'
};
const post: INewForumPost = {
id: undefined, // error, obj doesn't exist on this interface
title: 'Hello, world!',
description: 'This is a forum post'
};
The problem I had is that id
can be undefined if I have a union of these 2:
const post: IForumPost | INewForumPost = {
id: undefined, // TypeScript is fine with this
title: 'Hello, world!',
description: 'This is a forum post'
};
The part that had this lead to a bug in my code is that TS then doesn't let me simply do an if (post.id)
or if (typeof post.id === 'string')
check to determine which side of the union I have (those checks would be safe here), but it makes me do if ('id' in post)
check (which is not safe here because it's true for undefined
).
π Motivating Example
Let's say I have a function that tries to route the changes correctly to either "update" or "create":
const saveForumPost = (post: IForumPost | INewForumPost) => {
if ('id' in post) {
updateForumPost(post);
} else {
createForumPost(post);
}
};
And I am creating this post
object using a form that works for both editing or creating:
const postFromFormData: IForumPost | INewForumPost = {
id: isNew ? formData.id : undefined,
title: formData.title,
description: formData.description
};
That id
assignment is a pretty innocent ternary, but actually should not be accepted. saveForumPost
now treats this as an existing entry and attempts to update instead of create. This change would require me to either provide a proper value for id
or omit the key completely, leading to safer code.
π» Use Cases
1. What do you want to use this for?
I would like to use this to ensure stronger type safety in my code.
2. What shortcomings exist with current approaches?
Current approaches allow assigning invalid objects to unions.
3. What workarounds are you using in the meantime?
I have fixed the code, as needed, but not until after my bug was found. The workaround for now is just to be extra careful in this type of situation.