Skip to content

Type parameter not usable as type argument in identical type; union in constraint becomes intersectionΒ #60892

Description

@jcalz

πŸ”Ž Search Terms

indexed access, constraints, generic, unions, ts2344

πŸ•— Version & Regression Information

⏯ Playground Link

Playground link

πŸ’» Code

interface Foo<T extends { x: string }, U extends (T | { x: "a" })['x']> {
    z: Foo<T, U> // error!
    //        ~ Type 'U' does not satisfy the constraint 'T["x"] & "a"'. πŸ™ƒ
}

πŸ™ Actual behavior

The U type parameter is rejected as a type argument with a TS2344 error about how it doesn't satisfy the constraint. The constraint seems to have shifted from a union to an intersection, even though it comes from an identical place.

πŸ™‚ Expected behavior

The U type parameter should be accepted as a type argument.

Additional information about the issue

Distilled from SO question.

I expect this is a consequence of #30769 as per #31731 (comment), but it is at least somewhat surprising that this should happen when the types involved are identical. One could rewrite the constraint to U extends T["x"] | "a", of course, but this is distilled from the above SO question which is presumably distilled from some use case. What's happening here, exactly, and is it intended, a design limitation, or a bona fide bug?

Activity

  1. Andarist commented on Dec 31, 2024

    @Andarist
    Contributor

    FWIW, both come from distributeIndexOverObjectType - the union/intersection output is determined based on the writing option. When doing the relationship check the compiler checks if U is assignable to the constraint of the target type parameter - that's (T | { x: "a"; })["x"]. So far so good. But then it's normalized/simplified close to the top of isRelatedTo (using transitively mentioned distributeIndexOverObjectType) and the output of that is an intersection. At this stage, the source itself is still U - but later on its constraint goes through the same normalization... this time it becomes a union. But at this stage, the target is already heavily disassociated from its origin anyway. I suspect that if this indexed access on the target side would stay deferred then it would just work but implementing that might prove tricky.

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

    BugA bug in TypeScriptDomain: Indexed Access TypesThe issue relates to accessing subtypes via index accessHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions