Skip to content

Different behavior exhibited between homomorphic mapped types #44850

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

Closed
corymharper opened this issue Jul 1, 2021 · 5 comments
Closed

Different behavior exhibited between homomorphic mapped types #44850

corymharper opened this issue Jul 1, 2021 · 5 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@corymharper
Copy link

Bug Report

🔎 Search Terms

mapped, generic, string, union

🕗 Version & Regression Information

  • This is the behavior in every version I tried. (I tried every version the Typescript Playground has listed, and didn't see a pertinent answer in the FAQ)

⏯ Playground Link

Playground link with relevant code

💻 Code

// API Types
type NewParameters<EndpointParameters = never> = { [key in keyof EndpointParameters]: (NonNullable<EndpointParameters[key]>)[] };

// Test type v1
interface TestV1 {
	foo: 'foo' | 'bar' | 'baz';
}

// Test type v2
interface Split1 {
	foo: 'foo';
}

interface Split2 {
	foo: 'bar';
}

interface Split3 {
	foo: 'baz';
}

type TestV2 = Split1 | Split2 | Split3;

// Tests
// This throws an error
const test1: NewParameters<TestV2> = { foo: ['baz', 'foo', 'bar'] };

// These do not
const test2: { [key in keyof TestV2]: (NonNullable<TestV2[key]>)[] } = { foo: ['baz', 'foo', 'bar'] };

const test3: NewParameters<TestV1> = { foo: ['baz', 'foo', 'bar'] };

🙁 Actual behavior

Test examples 1 and 2 produce different types.

🙂 Expected behavior

Behavior should be consistent between the first two test examples.

@whzx5byb
Copy link

whzx5byb commented Jul 1, 2021

I'm not sure but it seems like an intended behavior caused by an unwritten rule.

According to #23345 (comment), homomorphic mapped types are also distributive, though this behavior is never mentioned in the documentation.

If TestV2 is a distributive union here, so NewParameters<TestV2>is mapped over to NewParameters<Split1> | NewParameters<Split2> | NewParameters<Split3>, which resolves to {foo: 'foo'[]} | {foo: 'bar'[] } | foo: 'baz'[]}.

@corymharper
Copy link
Author

I'm not sure but it seems like an intended behavior caused by an unwritten rule.

According to #23345 (comment), homomorphic mapped types are also distributive, though this behavior is never mentioned in the documentation.

If TestV2 is a distributive union here, so NewParameters<TestV2>is mapped over to NewParameters<Split1> | NewParameters<Split2> | NewParameters<Split3>, which resolves to {foo: 'foo'[]} | {foo: 'bar'[] } | foo: 'baz'[]}.

That would explain the behavior, but I don't exactly understand why the behavior in NewParameters<TestV2> differs from { [key in keyof TestV2]: (NonNullable<TestV2[key]>)[] } as I would expect that to be distributive as well if that were the case.

@whzx5byb
Copy link

whzx5byb commented Jul 1, 2021

I'm not sure but it seems like an intended behavior caused by an unwritten rule.
According to #23345 (comment), homomorphic mapped types are also distributive, though this behavior is never mentioned in the documentation.
If TestV2 is a distributive union here, so NewParameters<TestV2>is mapped over to NewParameters<Split1> | NewParameters<Split2> | NewParameters<Split3>, which resolves to {foo: 'foo'[]} | {foo: 'bar'[] } | foo: 'baz'[]}.

That would explain the behavior, but I don't exactly understand why the behavior in NewParameters<TestV2> differs from { [key in keyof TestV2]: (NonNullable<TestV2[key]>)[] } as I would expect that to be distributive as well if that were the case.

I guess this is because TestV2 appears as a generic type parameter in NewParameters<TestV2>, but does not in { [key in keyof TestV2]: (NonNullable<TestV2[key]>)[] }.
This is similar to the rule described in the documentation.

@corymharper
Copy link
Author

the documentation

I had seen that in the documentation, but it describes what happens in the case of a conditional type, of which this is not, but you are right that the behavior seems pretty much in line with what is described there.

@sandersn sandersn added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 2, 2021
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants