Skip to content
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

Polymorphic this type does not work with conditional mapped types #32550

Closed
mcsrobert opened this issue Jul 25, 2019 · 6 comments
Closed

Polymorphic this type does not work with conditional mapped types #32550

mcsrobert opened this issue Jul 25, 2019 · 6 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@mcsrobert
Copy link

mcsrobert commented Jul 25, 2019

TypeScript Version: 3.5.3

Search Terms: Polymorphic this mapped conditional

Code

type NumberedKeys<T> = { [K in keyof T]:
    T[K] extends number ? K : never }[keyof T];

type X = NumberedKeys<Example>;

class Example {
    public a: number;
    public b: string;
    public c: number;

    public constructor() {
        this.a = 3;
        this.b = "hello";
        this.c = 42;
    }

    public getNumber(key: NumberedKeys<this>): number {
        return this[key];
    }
}

Expected behavior: NumberedKeys<Example> works fine, but NumberedKeys<this> does not.

Actual behavior:

Type 'this[{ [K in keyof this]: this[K] extends number ? K : never; }[keyof this]]' is not assignable to type 'number'.
  Type 'this[this[keyof this] extends number ? keyof this : never]' is not assignable to type 'number'.
    Type 'this[keyof this]' is not assignable to type 'number'.
      Type 'this[string] | this[number] | this[symbol]' is not assignable to type 'number'.
        Type 'this[string]' is not assignable to type 'number'.

Playground Link: link

Related Issues: n/a

@jack-williams
Copy link
Collaborator

jack-williams commented Jul 25, 2019

I'm not sure if this approach is possible because a key type may not constrain the type of values it maps to, it can only say that is a key of some type. (If someone does have a solution I'd be interested to see it!)

Would this work for your use-case?

public getNumber<K extends keyof this>(this: Record<K, number>, key: K): number {
    return this[key];
}

@mcsrobert
Copy link
Author

@jack-williams I cannot seem to get that to work. When constructing the class I get:

The 'this' context of type 'Example' is not assignable to method's 'this' of type 'Record<"getNumber", number>'.
  Types of property 'getNumber' are incompatible.
    Type '<K extends "a" | "b" | "c" | "getNumber">(this: Record<K, number>, key: K) => number' is not assignable to type 'number'.

@jack-williams
Copy link
Collaborator

Can you post an extended example?

Here is my version: playground

@mcsrobert
Copy link
Author

mcsrobert commented Jul 25, 2019

@jack-williams I got your example working eventually. The problem is that I need access to other properties of this. This is not possible with Record.

The problem I'm trying to solve is a generalised, inheritable version of DataLoader priming.

I can use keyof this here, but I was trying to see if there was a way to have stricter typing for this, when I ran into the issue above. I've posted the extended example here.

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

We can tell from inspection that NumberedKeys returns keys where the looked up property is of type number, but TypeScript can't do the kind of higher-order reasoning to realize that this means the resulting type, when indexed back in to the same type, has that effect in the general case.

@EduardoRFS
Copy link

EduardoRFS commented Aug 10, 2019

I also hit that today when was trying to make an amazing type definition, minimum example:

type PickProperties<T, P> = Pick<T, {
  [K in keyof T]: T[K] extends P ? K : never;
}[keyof T]>;
type Numbers<T> = PickProperties<T, number>;
type Identity<T extends number> = T;

const use = <T, K extends keyof Numbers<T>>(
  service: K,
  fn: Identity<Numbers<T>[K]>
): void => {}

@RyanCavanaugh it's that hard to implement a constrain based on keyof? Because the constrain that Numbers<T> will always returns { [key: string]: number } so + keyof will result in T[K] being always a number.

edit, workaround found:

// same code as above just append it
type IsNumbers<T> = T extends number ? T : never;
const use = <T, K extends keyof Numbers<T>, U extends IsNumbers<T[K]>>(
  service: K,
  fn: Identity<U>
): void => {}

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

4 participants