Skip to content

[Bug] Conditional types throw error since TS 4.9.xΒ #51614

Description

@jxn-30

Bug Report

πŸ”Ž Search Terms

conditional types, 2339

πŸ•— Version & Regression Information

  • This changed between versions 4.8.4 and 4.93
  • This bug also occurs on nightly version 5.0.0-dev.20221121

⏯ Playground Link

Playground link with relevant code and test cases

πŸ’» Code

// Errors in TS 4.9.3 but not in 4.8.4
interface A {}
type B = A extends Record<'foo', string> ? A['foo'] : string; // Error: Property 'foo' does not exist on type 'A'.(2339)

// Succeeds in TS 4.9.3 and 4.8.4
interface C { foo: number; }
type D = C extends Record<'foo', string> ? C['foo'] : string; // Correct: string

// Succeeds in TS 4.9.3 and 4.8.4
interface E { foo: string; }
type F = E extends Record<'foo', string> ? E['foo'] : string; // Correct: string

// Succeeds in TS 4.9.3 and 4.8.4
interface G { foo: 'bar'; }
type H = G extends Record<'foo', string> ? G['foo'] : string; // Correct: 'bar'

πŸ™ Actual behavior

interface A {}
type B = A extends Record<'foo', string> ? A['foo'] : string;

errors with 2339: Property 'foo' does not exist on type 'A'.

This change causes @vuejs/pinia to fail as one can see in vuejs/pinia#1814

πŸ™‚ Expected behavior

interface A {}
type B = A extends Record<'foo', string> ? A['foo'] : string;

returns with B being an alias for type string.

Activity

  1. MartinJohns commented on Nov 21, 2022

    @MartinJohns
    Contributor

    Workaround: A extends Record<'foo', infer X extends string> ? X : string

  2. ahejlsberg commented on Dec 6, 2022

    @ahejlsberg
    Member

    This is working as intended. In cases where the outcome of a conditional type is known to always be the false branch, we no longer pretend that the type in the true branch has satisfied the extends check. In the example that produces an error above, the outcome of the conditional type is always the false branch string, and in the true branch we no longer pretend it is possible to write A['foo'] because that property is known not to exist. I think this is preferable to allowing nonsense like this:

    type T0 = Number extends String ? Number["length"] : String["length"];  // This was previously ok but now errors

    I understand there's a possible usage pattern where the conditional type is a form of conditional compilation based on types declared elsewhere in the program, but in those cases I think it is reasonable to require an explicit intersection:

    type B = A extends Record<'foo', string> ? (A & Record<'foo', string>)['foo'] : string;
  3. Andarist commented on Dec 6, 2022

    @Andarist
    Contributor

    Anders Hejlsberg (@ahejlsberg) what about interface augmentation though? With it, the type checker can't actually prove that the outcome of this conditional type is always its false branch. The conditional type can live in the library's code and the augmentation might happen in the consuming application. It seems that this is exactly how this was used here.

    In general, I agree with your reasoning but when providing the fix, I assumed that this was meant to be supported exactly because of the interface augmentation.

  4. ahejlsberg commented on Dec 7, 2022

    @ahejlsberg
    Member

    what about interface augmentation though?

    Yeah, although unintended, I agree that's meaningful scenario. Let's call this a regression then and merge your PR.

  5. added
    BugA bug in TypeScript
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on Dec 7, 2022
  6. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions