Yes, you can get this to happen automatically.
Caveat: You didn't give a type definition for Sub or Nope, so I will add some. It's actually important to do so in the example, because the TypeScript type system is based on stucture, not names. Meaning that the mere fact that the names Sub and Nope are distinct doesn't mean that the compiler sees them as distinct types. If they have the same properties, the compiler will see them as the same type, and then the task of distinguishing class properties of type Sub from those of type Nope would be impossible. So let's do this:
// important that Sub and Nope are stucturally distinct types
// so the compiler can tell the difference
class Sub {
sub!: string;
}
class Nope {
nope!: string;
}
Now Sub has a "sub" key and Nope has a "nope" key, and the compiler will be able to tell them apart.
You can create the following type alias KeysMatching<T, V>, which will produce all keys of T where the property at that key is assignable to the type V:
type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T];
It makes use of mapped and conditional types. Now your method can be typed the way you want 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 make sure it works:
const child = new Child;
child.method(['a', 'b', 'c']); // okay
child.method(['d','e']); // error!
Looks good to me. Here's a Playground link to this code. Hope that helps; good luck!