Skip to content

Generic keyof deduction does not use Interface #39612

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
kwasimensah opened this issue Jul 15, 2020 · 2 comments
Closed

Generic keyof deduction does not use Interface #39612

kwasimensah opened this issue Jul 15, 2020 · 2 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@kwasimensah
Copy link

kwasimensah commented Jul 15, 2020

TypeScript Version: 3.7.x-dev.201xxxxx

Search Terms:
generic deduction interface

Code

export type Param = number | string | boolean | Date | null;

export type ParamFields<T> = {
  [P in keyof Required<T>]: NonNullable<T[P]> extends Param ? P : never;
}[keyof T];

interface IHasId {
    id: number;
}

class HasId implements IHasId {
    id: number = 1;
}

function hasField<T>(x: T, field: ParamFields<T>) {
    return x[field];
}


hasField({ x: 5 }, "x");

function templateHasId<T extends IHasId > (x: T) {
    return hasField(x, "id"); // Doesn't realize id has to be a field of x.
}

Expected behavior:

hasId(x, "id"); compiles because it'd declared to have a field of x thats of type number.

Actual behavior:

Compains because it can't deduce "id" is a keyof T even though the interface says it is.

Playground Link:

https://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=26&pc=1#code/KYDwDg9gTgLgBDAnmYcAKBDKGC2cC8cAdgK44BGwUcAPnAM4xQCWRA5rXORBADbAYinACIYYqOqV68A3ACg5oSLATJUmbDgBizYLwAm9ADwAVAHwE4Abzlw4AbTRxWcANbBEEAGZwASsABHEmYoYH1TMwBdAC44ADkIIjiSaQxyflNHSItQcSJDdCxcOAB+dDhYomAANyp5AF97d08fE0j5OVZxKC8MAGNUAEkACQx6Qf1rWztnfUqySigGhT7eMfo4UfHJ5hwwfhxgIhgNkbGJqZnZ+YoqSwBGZbkvEiI+mGZEuAALMZ09cLmAAUIFiJgANHAvLoDLENLh-gZjOYAJSXGahGAkKBCED2aEA9pyeoKOS-eiI-RAqxwUFwACscHqkIARCAWSiOi83h8vuI9mtxFsJqY4LkjgUzts4BYQWC0TYrpjsUJyZSQazmPoOTI4AB6PVwYQQYD0IgAcngoQwvGYAC9UFqfmMEBAuKgMFCYZNvLSAHTEhRAA

Related Issues:

#19211
#7294

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Jul 15, 2020
@RyanCavanaugh
Copy link
Member

While it's true from a higher-ordering reasoning perspective that "id" would be assignable to any instantiated ParamFields<T> if T extends IHasId, that isn't the operation that happens here. TypeScript has to first produce a type for ParamFields<T> and then see if "id" is assignable to it, but ParamFields<T> is a higher-order operation that can't be resolved to a concrete type because it depends on a free type parameter T - the constraint here is not enough to change this fact here, because it would be unsound for ParamFields<T (extends IHasId)> to just be "id" because T could be instantiated with some type where the correct answer is more like "id" | "name", which is a wider type. As long as the type remains higher-order in a complex way like this, TS isn't able to make determinations about whether or not "id" is always going to be in the resulting type.

Probably your example is more complex in reality, but I would just make templateHasId non-generic since there's no reason for it to be generic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

2 participants