Skip to content

Generic index into two types not seen as assignable, even if the two types are identical for all relevant keysΒ #58905

Description

@jcalz

πŸ”Ž Search Terms

generic, indexed access, supertype

πŸ•— Version & Regression Information

⏯ Playground Link

Playground link

πŸ’» Code

interface Foo {
  a: string,
  b: number
}

interface Bar extends Foo {
  c: boolean
}

function bar<K extends keyof Bar>(k: K, v: Bar[K]) { }

function foo<K extends keyof Foo>(k: K, v: Foo[K]) {
  bar(k, v) // error!
  //     ~
  // Argument of type 'Foo[K]' is not assignable to parameter of type 'Bar[K]'.
  //   Property 'c' is missing in type 'Foo' but required in type 'Bar'.(2345)
}

πŸ™ Actual behavior

bar(k, v) fails because Foo[K] is supposedly not assignable to Bar[K].

πŸ™‚ Expected behavior

bar(k, v) should succeed because Foo[K] is identical to Bar[K]; the only difference between Foo and Bar occurs in a key that K can never be.

Additional information about the issue

This is presumably a design limitation introduced with #30769. And while it's similar to #32693, it's different enough that I'd like to see an official word about it. If there is an existing issue that's the same as this, I didn't find it. Can anyone find one this duplicates? I'm investigating a SO question and while I feel certain I've seen this before, I keep hitting things like #32693 which aren't about generics at all.

On the face of it, if Pick<Foo, KC> and Pick<Bar, KC> are identical, then for generic K extends KC, Foo[K] and Bar[K] should also be identical. Yes, if K turns out to be a union, then you run into the soundness issue that #30769 fixed. We already allow T[K] to be assignable to T[K] if K is generic, so it's not really soundness that's at play here.... we've already decided that generic keys are a way of sidestepping this (I'm looking at you, #47109). I'm guessing it's just not worth the performance hit to check assignability of parts of the source and target object types, so it's a design limitation? Maybe?

Activity

  1. RyanCavanaugh commented on Jun 18, 2024

    @RyanCavanaugh
    Member

    the only difference between Foo and Bar occurs in a key that K can never be.

    How does one arrive at this conclusion except via exhaustive analysis of possible values of K? Note that from TS's perspective, this example isn't any different from one with

    interface Foo {
      a: string,
      b: number,
    }
    
    interface Bar {
      a: string,
      b: number,
      c: boolean
    }
  2. jcalz commented on Jun 18, 2024

    @jcalz
    ContributorAuthor

    Right, so it's presumably not worth the performance hit to look at K extends keyof Bar and compare Pick<Foo, keyof Bar> to Pick<Bar, keyof Bar> instead of comparing Foo to Bar. Synthesizing the Pick types for every generic assignment would be equivalent to such "exhaustive analysis", I presume?

  3. RyanCavanaugh commented on Jun 18, 2024

    @RyanCavanaugh
    Member

    Right, exactly

  4. typescript-bot commented on Jun 21, 2024

    @typescript-bot
    Contributor

    This issue has been marked as "Design Limitation" 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

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions