Consider the following code, which uses TypeScript language features introduced in v2.8 (conditional types):
type P<TObject, TPropertySuperType> = {
[K in keyof TObject]: TObject[K] extends TPropertySuperType ? K : never;
}[keyof TObject];
function g<
T,
K extends keyof Pick<T, P<T, string>>
>(obj: T, prop: K): void { }
class C {
public alpha: string;
public beta: number;
public f(): void {
g(this, "alpha"); // <-- does not compile!
g(this, "beta");
g<C, "alpha">(this, "alpha");
g<C, "beta">(this, "beta");
g(new C(), "alpha");
g(new C(), "beta");
this.g2("alpha");
this.g2("beta");
this.g2<"alpha">("alpha");
this.g2<"beta">("beta");
}
public g2<
K extends keyof Pick<C, P<C, string>>
>(prop: K) { }
}
The idea behind the type P is that it selects the names of the properties of TObject that satisfy the constraint that the type of the property extends TPropertySuperType. The functions g and g2 then use the type P in a type parameter constraint, such that:
g when the prop parameter is the name of a extends string-typed property of obj
g2 when the prop parameter is the name of a extends string-typed property of C.Here, because C.alpha is of type string and C.beta is of type number, I would expect all five invocations of g/g2 with prop === "alpha" to compile, and all five invocations with prop === "beta" not to compile.
However, the invocation g(this, "alpha") does not compile, as you can see if you paste this code into the TypeScript playground. The error is:
Argument of type '"alpha"' is not assignable to parameter of type 'this[keyof this] extends string ? keyof this : never'.
Why does this particular invocation fail? I'm guessing it has something to do with how TypeScript infers the type of this, but the details are fuzzy to me.
I agree with arthem the most likely culprit is polymorphic this. While obvious the type of this will be polymorphic this. While you can say for sure C['alpha'] is of type string, for this['alpha'] you can't say that, all you can say is that this['alpha'] extends string which is a more complicated relation for the compiler to follow. Not sure if this analogy helps, but polymorphic this acts like a hidden type parameter to the class, and using it is subject to similar limitations. For example, inside g the type of obj['prop'] is not known to be string again because of limitations of what can be said for generic type parameters:
function g<
T,
K extends keyof Pick<T, P<T, string>>
>(obj: T, prop: K): void { obj[prop].charAt(0) /*error*/}
While the above is speculation (and I'll admit a bit fuzzy), the solution that solves the error above will solve the issue with this namely to put our constraint that only string keys can be passed in in a different way.
function g3<
K extends string | number | symbol,
T extends Record<K, string>
>(obj: T, prop: K): void { obj[prop].charAt(0) /* ok*/ }
class C {
public alpha!: string;
public beta!: number;
public f(): void {
g3(this, "alpha"); // also ok as expected
g3(this, "beta"); //not ok
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With