Skip to content

Contravariant inference breaks on generic conditional types. #56344

Not planned
@rzvc

Description

@rzvc

🔎 Search Terms

mapped types templates contravariant inference

🕗 Version & Regression Information

  • This changed between versions 4.1.5 and 4.2.3.
  • This is the behavior in every version I tried.

⏯ Playground Link

https://tsplay.dev/NVrBBN

💻 Code

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

function test<T>(value: T)
{
    type I = UnionToIntersection<
          { a: number }
        | { b: number }
        | (T extends number ? { c: T } : { c: T })
    >;

    let i : I = null as any;

    i.a = 1;    // With c defined, i.a is available only up to version 4.1.5. Afterwards, it breaks.
    i.b = 1;    // Same for i.b.
    i.c = value;
}

🙁 Actual behavior

The non-generic dependent properties were not available.

🙂 Expected behavior

At least the non-generic dependent properties to be available. Ideally, c should have been available too and value should have been assignable to it, but that's less "expected" than the first part.

Additional information about the issue

It completely breaks more complex mapped types that used to work up to 4.1.5. The conditional generic type was still not available, but it was better than nothing.

If the previous behaviour cannot be restored, perhaps there's a way to detect that you're dealing with a generic type which will be deferred, and allow the programmer to decide what to do with it?

Activity

jcalz

jcalz commented on Nov 8, 2023

@jcalz
Contributor

You might consider coming up with an example that doesn't depend on UnionToIntersection since that tends to get issues closed: #43077 (comment); #56185; see #29594 (comment) also. Can you reproduce the basic issue with a less problematic use of contravariant inference?

rzvc

rzvc commented on Nov 8, 2023

@rzvc
Author

@jcalz, not sure if this is what you meant by less problematic, but would this do it?

function test<T>(value: T)
{
    type I = (
          ((arg: { a: number }) => void)
        | ((arg: { b: number }) => void)
        | (T extends number ? (arg: { c: T }) => void : (arg: { c: T }) => void)
    ) extends ((arg: infer I) => void) ? I : never;

    let i : I = null as any;

    i.a = 1;    // With c defined, i.a is available only up to version 4.1.5. Afterwards, it breaks.
    i.b = 1;    // Same for i.b.
    i.c = value;
}

https://tsplay.dev/w2QOrW

jcalz

jcalz commented on Nov 8, 2023

@jcalz
Contributor

I imagine the type should have some obvious real-world utility for a use case the TS team is interested in supporting, but I can't say what that is on first glance. 🤷‍♂️

rzvc

rzvc commented on Nov 8, 2023

@rzvc
Author

@jcalz, well, that kinda pulls UnionToIntersection back into the conversation, because it's useful when you have to break a type apart with something like this, and then put the result back together:

type Remap<T> = {
    [K in keyof T]: T[K] extends number
        ? { [P in K]: T[K] }
        : { [P in K]: T[K] }
}[keyof T];
fatcerberus

fatcerberus commented on Nov 9, 2023

@fatcerberus

I’m confused why you’re writing T extends U ? V : V (presumably this is just to get the distributive behavior?) instead of something like T extends unknown ? V : never

Although in the case of Remap there’s seemingly no reason to write that conditional type at all since T[K] extends … is not distributive…

rzvc

rzvc commented on Nov 9, 2023

@rzvc
Author

@fatcerberus, Remap is just an example, not a real world thing. I wrote it to demonstrate the type of mapping with conditional types, that would require contravariant inference to work when one of the properties of the type being mapped has a generic type.

In its extends clause, I'm not looking for enabling the distributive behaviour, but to show that the types I'm having problems with are doing some extends logic in the remapping process, which is a necessary ingredient to break inference.

typescript-bot

typescript-bot commented on Nov 12, 2023

@typescript-bot
Collaborator

This issue has been marked as "Not a Defect" 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @jcalz@fatcerberus@RyanCavanaugh@rzvc@typescript-bot

        Issue actions

          Contravariant inference breaks on generic conditional types. · Issue #56344 · microsoft/TypeScript