Skip to content

Filtering keys by property type doesn't narrow the actual property types #28111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
the-ress opened this issue Oct 24, 2018 · 8 comments
Closed
Labels
Domain: Conditional Types The issue relates to conditional types

Comments

@the-ress
Copy link

TypeScript Version: 3.2.0-dev.20181023

Search Terms:
keyof, filter, type is not assignable to type

Code

type WantedTypes = string | number

type WantedKeys<T> = { [K in keyof T]: T[K] extends WantedTypes ? K : never }[keyof T]

function foo<T> (obj: T, key: WantedKeys<T>): WantedTypes {
    return obj[key]
}

class DerivedProperty<T extends WantedTypes> { }

type Derived<T> = {
    [K in WantedKeys<T>]: DerivedProperty<T[K]>
}

Expected behavior:
Compiles without errors.

Actual behavior:

test.ts:6:5 - error TS2322: Type 'T[{ [K in keyof T]: T[K] extends string | number ? K : never; }[keyof T]]' is not assignable to type 'string | number'.
  Type 'T[{ [K in keyof T]: T[K] extends string | number ? K : never; }[keyof T]]' is not assignable to type 'number'.

6     return obj[key]
      ~~~~~~~~~~~~~~~

test.ts:12:43 - error TS2344: Type 'T[K]' does not satisfy the constraint 'string | number'.
  Type 'T[K]' is not assignable to type 'number'.

12     [K in WantedKeys<T>]: DerivedProperty<T[K]>
                                             ~~~~

I couldn't find any similar issues here.

@DanielRosenwasser
Copy link
Member

I don't necessarily know what we could do to keep track of that information. By the time the keys from WantedKeys are produced, we don't have any way to track constraints developed around T[K]. Any ideas for alternative ways to write this @ahejlsberg?

@weswigham weswigham added Needs Investigation This issue needs a team member to investigate its status. Domain: Conditional Types The issue relates to conditional types labels Oct 25, 2018
@jacobmadsen
Copy link

jacobmadsen commented Jan 16, 2019

I have a similar problem.

Why is str not assignable to notAssignable1, when it's type union includes the type of str?

type StringLiteral = 'a'
type Value = { [key in StringLiteral]: string }
type StringKeys<V extends Value> = { [K in keyof V]: V[K] extends string ? K : never }[keyof V]

function test<V extends Value>(str: StringKeys<V>) {
  const assignable: string = str as string

 // TS2322: Type 'number' is not assignable to type 'string'.
  const notAssignable1: string | number | undefined = str
  const notAssignable2: StringLiteral = str
}

@RyanCavanaugh
Copy link
Member

@jacobmadsen same issue as Daniel cited above - by the time the final indexing [keyof V] occurs, there isn't any information being trafficked around to represent that only string-producing keys haven't been filtered to never.

@jacobmadsen
Copy link

@RyanCavanaugh

Currently there is no way to infer that all keys of type Value is of type string ?

@jacobmadsen
Copy link

Maybe the error message should be:

Type 'string | number | symbol' is not assignable to type ...

@gdh1995
Copy link
Contributor

gdh1995 commented Jun 7, 2019

With TypeScript v3.5.1, I want to write:

type PossibleKeys<T, V, K extends keyof T = keyof T> = T[K] extends V ? K : never;

But finally I can only write

type PossibleKeys<T, V, K extends keyof T = keyof T> =
    K extends keyof T
    ? T[K] extends V ? K : never
    : never;

@ahejlsberg
Copy link
Member

The original example works when written as follows:

type WantedTypes = string | number

type WantedKeys<T> = { [K in keyof T]: T[K] extends WantedTypes ? K : never }[keyof T]

function foo<T extends Record<K, WantedTypes>, K extends WantedKeys<T>>(obj: T, key: K) {
    return obj[key];
}

class DerivedProperty<T extends WantedTypes> { }

type Derived<T extends Record<K, WantedTypes>, K extends WantedKeys<T> = WantedKeys<T>> = {
    [K in WantedKeys<T>]: DerivedProperty<T[K]>
}

The key here is to ensure T is constrained to a type where all properties are of type WantedTypes such that T[K] ends up being constrained to WantedTypes. In order for the original example to work as written we would need K in T[K] to somehow carry a constraint for T which is not an ability we have, nor one I could imagine us implementing.

@ahejlsberg
Copy link
Member

@gdh1995 That's the intended behavior. The first form is not a distributive conditional type, but the second form is (see #21316 for definition). You could also write it as any one of the following:

K extends K ? T[K] extends V ? K : never : never;
K extends any ? T[K] extends V ? K : never : never;
K extends unknown ? T[K] extends V ? K : never : never;

@ahejlsberg ahejlsberg removed the Needs Investigation This issue needs a team member to investigate its status. label Jun 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Conditional Types The issue relates to conditional types
Projects
None yet
Development

No branches or pull requests

7 participants