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 type inside function resolves differently than at top-level

See original GitHub issue

Bug Report

🔎 Search Terms

regression conditional type inside function behaves difference

🕗 Version & Regression Information

  • This changed between versions 4.2.3 and 4.3.5

⏯ Playground Link

Playground link

💻 Code

{
  type A = Record<string, number> 
  type B = Record<'a' | 'b', number> 
  const x: A extends B ? true : false = false; // ok
}

function test() {
  type A = Record<string, number> 
  type B = Record<'a' | 'b', number> 
  const x: A extends B ? true : false = false; // error
}

🙁 Actual behavior

The conditional type resolves differently in global vs local scope.

🙂 Expected behavior

Both should resolve the same way, preferably false in both cases.

Additional Details

This seems to have something to do with the way typescript compares aliases, wrapping one of the Record types in Omit<Record<...>, never> causes the comparison to behave normally

Playground Link

function test() {
  type A = Record<string, number> 
  type B = Record<'a' | 'b', number> 
  const x: A extends B ? true : false = false; // error
}

function test2() {
  type A = Omit<Record<string, number>, never>
  type B = Record<'a' | 'b', number> 
  const x: A extends B ? true : false = false; // ok
}

function test3() {
  type A = Record<string, number>;
  type B = Omit<Record<'a' | 'b', number>, never>
  const x: A extends B ? true : false = false; // ok
}

function test4() {
  type A = Omit<Record<string, number>, never>;
  type B = Omit<Record<'a' | 'b', number>, never>
  const x: A extends B ? true : false = false; // error
}

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:6
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
fatcerberuscommented, Jul 12, 2022

Wow… it feels so wrong that an undocumented internal implementation detail like that should affect assignability. Not that that doesn’t happen elsewhere (variance measurement being what it is), but this one is particularly hard to come up with theoretical justification for without popping the hood of the compiler.

0reactions
fatcerberuscommented, Jul 14, 2022

No worries, I understand it’s a design limitation; it’s not really the unsoundness that bothers me but moreso that I’m trying to figure out the rationale for the discrepency below so I can try to invent a theoretical explanation for it that doesn’t involve popping the hood 😉

the global scope A and B types have new alias symbols associated with them and therefore appear not to be instantiations of the same generic type. Thus we relate them structurally. However, the local A and B types aren’t given new alias symbols (for unrelated reasons) and therefore appear to both be instantiations of Record<K, T>. Thus we relate them according to variance.

edit: I just realized this probably only affects the case where you’re creating type aliases for generic instantiations, and things directly typed as Records (or whatever else) will relate more consistently (i.e. always variance-based). That’s not so bad, in that case.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - TypeScript 2.8
A conditional type T extends U ? X : Y is either resolved to X or Y , or deferred because the condition...
Read more >
Resolvers - Apollo GraphQL Docs
The resolver map has top-level fields that correspond to your schema's types (such as Query above). Each resolver function belongs to whichever type...
Read more >
Conditional result is incorrect when providing generics ...
Resolving of conditional types involving unbound generic type arguments is deferred. At that point the compiler doesn't know what ...
Read more >
Why does a Typescript type conditional on `T extends ...
When T extends undefined is false, T appears to turns into never in the else branch, but only inside a function parameter list......
Read more >
React conditional rendering: 9 methods with examples
Conditional rendering refers to delivering elements & components based on certain conditions. Learn how to do conditional rendering in ...
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