4

We have created the following type according to this post : https://stackoverflow.com/a/49752227

export type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp? P : never}[keyof T];

In this example

class A {
   public prop1: number;
   public prop2: string;
   public prop3: string;
}
class B<O extends A>
{
   private prop: KeysOfType<O, string>;
   private prop2: KeysOfType<A, string>;

   public method(): void
   {
      let o: O;
      let notTypedAsString = o[this.prop];
      let a: A;
      let typedAsString = a[this.prop2];
    }
}

The expression o[this.prop] is not typed as string despite the fact that this.prop is typed as a "string" property of O.

Link to the playground

Is there a way to make it work ?

G.T.
  • 1,557
  • 1
  • 12
  • 24
Eelilag
  • 41
  • 1
  • I think you're asking for too much cleverness from the compiler. When `O` is generic it tends to defer evaluating types like `O[K]` or `keyof O` until after `O` is specified. The compiler is therefore faced with `O[KeysOfType]` and is not smart enough to simplify that to a subtype of `string` (think of how would you go about programming a compiler to notice that causing a major performance hit). – jcalz Oct 16 '18 at 15:50
  • `class B>` and using directly `keyof O` will work but it will not enforce the fact that `O extends A` – Titian Cernicova-Dragomir Oct 16 '18 at 17:05

1 Answers1

1

The answer you cite (always nice to be cited :) ) works well for usage site. So when you use the class or the function that uses KeysOfType you will get the expected behavior. The problem is that inside the class or function where the generic type is not known, typescript can't reason about what the indexing operation will produce.

We can use an approach similar to here. Where the generic parameter itself extends something that only has string keys (Record<K, string>). We will need an extra parameter K to capture only the string keys of the O type but it all works as expected:

export type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp ? P : never }[keyof T];

class A {
    public prop1: number;
    public prop2: string;
    public prop3: string;
}
class B<O extends Record<K, string> & A, K extends string | number | symbol = KeysOfType<O, string> >
{
    constructor() {

    }
    private prop: K;
    private prop2: keyof A

    public method(): void {
        let o!: O;
        let notTypedAsString = o[this.prop]; // if you hover over it it will be O[K] 
        notTypedAsString.bold();//but it behaves as string
        o.prop1 // number, A memebrs that arae not strings still work as expected
    }
}

new B<A>()
class A2 extends A {        
    public prop4: number;
    public prop5: string;
}
new B<A2>()
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357