question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Conditional types fail to distribute in properties of mapped types

See original GitHub issue

TypeScript Version: 3.7.0-dev.20190928

Search Terms: conditional mapped property union

Code

type NullifyStrings<T> = T extends string ? null : T
type NullifyStringsInPropsWorking<T> = { [K in keyof T]: NullifyStrings<T[K]> }
type NullifyStringsInPropsBroken<T> = { [K in keyof T]: T[K] extends string ? null : T[K] }

type TestType = { a: number | string }
// { a: number | null }
type WorkingReplaceProps = NullifyStringsInPropsWorking<TestType>
// { a: string | number }
type BrokenReplaceProps = NullifyStringsInPropsBroken<TestType>

Expected behavior: NullifyStringsInPropsWorking and NullifyStringsInPropsBroken should be functionally identical - expanding the NullifyStrings type alias in NullifyStringsInPropsWorking results in the same definition as NullifyStringsInPropsBroken.

Actual behavior: NullifyStringsInPropsWorking and NullifyStringsInPropsBroken have different behaviour - BrokenReplaceProps has type { a: string | number } instead of the expected { a: number | null }.

Playground Link: Link

Related Issues:

#28339, but seems to be different.

#22945 mentions

Since type aliases are equivalent to writing the expansion inline […]

but that is not the case here.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:2
  • Comments:5

github_iconTop GitHub Comments

2reactions
mcpowercommented, Sep 30, 2019

Did you see this section of the handbook: distributive-conditional-types?

I don’t mean this in a snide way — lots of people have been tripped up on this and if they are reading the handbook and still getting confused then I think that is good to know.

I didn’t, actually! After reading that section of the handbook, I think I still wouldn’t have understood it - the section focuses on some nice working examples and doesn’t show any examples for when things go wrong. IMO these things would be nice to have in the handbook for better understanding:

  • examples of naked and non-naked type parameters - “naked type parameter” is not explicitly defined anywhere, even though the definition seems obvious
  • mentioning the main counter-intuitive “gotcha” - that inlining a distributive conditional type alias may result in different behaviour
  • some examples of when conditional types are expected to be distributive, but actually aren’t (the example above, #22945, #23046, #32274) - and how to work around it by using an additional type alias or extends infer.
1reaction
jack-williamscommented, Sep 30, 2019

Duplicate of #23022, #23046, and #32274.

Regarding:

Since type aliases are equivalent to writing the expansion inline […]

the subtext is that this only applies when inlining a type alias instantiated with a type parameter.

Did you see this section of the handbook: distributive-conditional-types?

I don’t mean this in a snide way — lots of people have been tripped up on this and if they are reading the handbook and still getting confused then I think that is good to know.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Conditional Types - TypeScript
Conditional types help describe the relation between the types of inputs and outputs.
Read more >
Typescript conditional mapped types loses return type ...
I'm trying to wrap my head around conditional mapped types. If I have a type defined as: type StringyProps<T> = { [K in...
Read more >
Conditional Types in TypeScript - Marius Schulz
If the relationship test in the conditional type checks a naked type parameter, the conditional type is called a distributive conditional type, ...
Read more >
Conditional types in TypeScript - Artsy Engineering
Then calling .toUpperCase() on the result would be an error. Let's add basic types to this function so we can let TypeScript worry...
Read more >
Advanced Types
Similar to union and intersection types, conditional types are not permitted to reference themselves recursively. For example the following is an error. Example ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found