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