Indeed, it is possible to make this happen automatically.
However, a specification for the Sub
or Nope
types was not provided, so I will define them. This distinction is crucial due to TypeScript's structural typing nature; it focuses on structure rather than names. If both Sub
and Nope
share the same properties, they will be considered the same type by the compiler, making it impossible to differentiate between them based on their class properties. Therefore, let's define them as follows:
// It's important that Sub and Nope are structurally distinct types
// for proper differentiation by the compiler
class Sub {
sub!: string;
}
class Nope {
nope!: string;
}
Now, Sub
has a key named "sub"
, while Nope
has a key named "nope"
, allowing the compiler to distinguish between them.
You can introduce a type alias called KeysMatching<T, V>
, which retrieves keys from T
where the property matches type V
:
type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T];
This implementation utilizes mapped and conditional types. You can now properly type your method by replacing keyof this
with KeysMatching<this, Sub>
:
class Parent {
method<T extends KeysMatching<this, Sub>>(keys: T[]){
}
}
class Child extends Parent {
a = new Sub;
b = new Sub;
c = new Sub;
d = new Nope;
e = new Nope;
}
Let's confirm its effectiveness:
const child = new Child;
child.method(['a', 'b', 'c']); // okay
child.method(['d','e']); // error!
It appears to be working correctly. You can test it further using this Playground link. Hopefully, this explanation helps you achieve your objectives. Best of luck!