Skip to content

Relating keys and field constraints of a generic super type constructed with mapped type filtering out properties #47447

Open
@Igorbek

Description

@Igorbek

Bug Report

🔎 Search Terms

  • indexing related keys of mapped types
  • supertype mapped type
  • excluding keys mapped type

🕗 Version & Regression Information

  • This is the behavior in every version I tried (4.4, 4.5, 4.6-dev), and I reviewed the FAQ for entries about mapped types

⏯ Playground Link

Playground link with relevant code

💻 Code

// SuperType<T, C> constructs a super type of T by filtering out properties of T that do not extend C

// Option 1 (the most correct, removes keys)
type SuperType<T, C> = { [K in keyof T as (T[K] extends C ? K : never)]: T[K] }

// Option 2 (not correct, resets fields to never)
// type SuperType<T, C> = { [K in keyof T]: T[K] extends C ? T[K] : never }

// Option 3 (Option 1 and 2 combinded)
// type SuperType<T, C> = { [K in keyof T as (T[K] extends C ? K : never)]: T[K] extends C ? T[K] : never }

// Option 4 (I saw this used in the wild)
// type SuperType<T, C> = Pick<T, { [K in keyof T]: T[K] extends C ? K : never }[keyof T]>

type Nums<T> = SuperType<T, number>

// examples
type A = { a: 10, b: true }
type CA = Nums<A>   // Option 1, 3, 4: { a: 10 }
                    // Option 2: { a: 10, b: never }
type CAK = keyof CA // Option 1, 3, 4: 'a'
                    // Option 2: 'a' | 'b'

function f<T, U extends keyof Nums<T>>(t: T, m: U) {
    // This should be reduced to T, because T extends SuperType<T, *>
    type ShouldBeT = T & Nums<T>

    // This should be reduced to U, since it is keysof T extends U
    type ShouldBeU = U & keyof T

    // n1 expected to be bounded to number, because m is keyof Nums<T>
    const n1 = t[m] // Option 1, 2: T[U]
                    // Option 3: any, error Type 'U' cannot be used to index type 'T'.(ts2536)
    n1.toFixed()    // Option 1, 2, 4: bounded to unknown
                    // Option 3: any type

    // same, n1 expected to be bounded to number, but with a hint
    const n2 = t[m] as SuperType<T, number>[U] // Option 1, 2: ok
                                               // Option 3: Type 'U' cannot be used to index type 'T'.(2536)
    n2.toFixed()    // Option 1, 4: bounded to unknown
                    // Option 2: bounded to number (success!)
                    // Option 3: bounded to number (because of type assertion)
}

🙁 Actual behavior

  • TS cannot relate a key that is a key of a supertype (with same or lesser keys) constructed by mapped type removing some keys
  • TS cannot index a generic type with a key of its generic supertype

🙂 Expected behavior

  • TS can relate a key that is a key of a supertype (with same or lesser keys) constructed by mapped type removing some keys
  • TS can index a generic type with a key of its generic supertype

Reasoning

  • any field with key U of SuperType<T, C> is also a key of T and its type extends C
  • therefore T[U] should be allowed because U is keyof T
  • therefore T[U] should be C because T[K] extends C for any key in U

That is a very common task that you need to filter out some properties from a type. I saw people call it a subtype when in fact it is a supertype. But weirdly enough indexing be keys of the mapped type is not allowed. I saw it as a regression in a big codebase when upgraded to 4.5, but turned out the values were considered any. So now they're bounded to unknown which is better but still cannot do much further.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions