Union type on object's property behaves different than having a union of the whole object with all possible values for this field
🔎 Search Terms
union inference
🕗 Version & Regression Information
I use v5.7.3 and never tried it out before (but sanity-checking against random older versions does not seem to resolve the issue)
⏯ Playground Link
https://www.typescriptlang.org/play/?#code/KYDwDg9gTgLgBDAnmYcDyAjAVsAxjANQEMAbAV2AGcAeAFQD44BeOWgbQGthEIAzVgLoBuAFAjQkWHFwQAdpXgBxAJYwAMkQwARYDCLKSwACYBZYFADmwAMp6YZSszgBvEXDgmAogCVFngIIAQmqeAFxwAOQAtuZWmoYRADRucABynp5aAPrenoH+1mGRssDGWVDAGESUwEkiAL5w1dJyCqIS0PBIKHAq6po6egbGZpY2dg5OmDj4xORU1N3AfL2qGtq6+oamseNE9pT0ouLgnQjIqKNW3sAAjhQKTq7uykbhClDKshai7jFjWQU+wc4T660GWxGu1swMoonqxw6UiWHl2N3uVBgACYnilXu8YJ9vr84P8rICJpRwksVmCBpthjsxjCDgA6dKZHJ5AqeEmNAA+LjxbzgHy+PxSZOAFNh1IutLW9KG2yuezZXl8AWCvIaYl4ZFk+GUcjgRggtjIvF4AAoolBwqr0Q8YABKIUvG121lSmUHZhMFh0jbKqHMynsjLZXL5Qpu57uBMVexQWRNMDKADCpBItqgLr5upE+sNMGNqbNFqtWNzDrRd2dWLjeM9UG9u19kwDgcVwchTKsLIcEc50Z5TYTid0ZBTacz2dz+ZS9ULxaNJqI6azJBzdtrYydmLgADIXD6gQc5SgFf1e4zVYPKMOo9zCnB6nH6kA
💻 Code
export type ObjectValues<T> = T[keyof T];
export const GitLabDetailedMergeStatus = {
MERGEABLE: 'mergeable',
NEED_REBASE: 'need_rebase',
} as const;
export type GitLabDetailedMergeStatus = ObjectValues<typeof GitLabDetailedMergeStatus>;
export type MergeRequest = {
id: string;
merge_status: GitLabDetailedMergeStatus;
};
export type MergeRequest2 = {
id: string;
merge_status: typeof GitLabDetailedMergeStatus.NEED_REBASE;
} | {
id: string;
merge_status: typeof GitLabDetailedMergeStatus.MERGEABLE;
}
function doStuff(mr: MergeRequest) {
if(mr.merge_status === GitLabDetailedMergeStatus.NEED_REBASE) {
return apiCall(mr);
}
}
function doStuff2(mr: MergeRequest2) {
if(mr.merge_status === GitLabDetailedMergeStatus.NEED_REBASE) {
return apiCall(mr);
}
}
function apiCall(mr: MergeRequest & {merge_status: typeof GitLabDetailedMergeStatus.NEED_REBASE }) {}
🙁 Actual behavior
In doStuff() the function call throws an error saying Types of property 'merge_status' are incompatible. regardless of the check in the if statement above and also the correctly inferred type (if you hover over it or add the same if statement again (it tells you that this is useless because it always will be true)).
In doStuff2() there is no error.
🙂 Expected behavior
TypeScript should infer both types correctly when passing it to the function.
If I am misunderstanding something obvious here, please let me know.
Additional information about the issue
No response
This is working as intended. MergeRequest is not a union type that can be narrowed. Checking individual properties on a non-union type will not created new types.
Ok, thanks for the quick answer.
I find this very confusing (from a developer experience standpoint) given the following (simplified) example:
function fun(str: 'bar') {}
function fun2(obj: {foo: 'bar'}) {}
function doStuff(obj: {foo: 'bar' | 'baz'}) {
if(obj.foo === 'bar') {
fun(obj.foo) // no error, 'bar' gets correctly inferred
fun2(obj) // error, TypeScript knows for sure that `obj` here has the type {foo: 'baz'}
}
}
I get that TypeScript must not create a new type for primitves since string already exists. But still I am confused where the limitation on objects here comes from. But maybe I have the wrong understanding or expectation on what TypeScript actually does in this situation. I'm fine with closing this MR and happy to read more in-depth about what's happing here :)
This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.