Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Union type on object's property behaves different than having a union of the whole object with all possible values for this field #61202

Open
Llois41 opened this issue Feb 17, 2025 · 2 comments

Comments

@Llois41
Copy link

Llois41 commented Feb 17, 2025

πŸ”Ž 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

@MartinJohns
Copy link
Contributor

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.

@Llois41
Copy link
Author

Llois41 commented Feb 17, 2025

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 :)

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants