Background
Last week I asked a question about how to declare a class member that is a function whose parameter is covariant with the class, for which using the polymorphic this type was a perfect solution.
My actual code has a class with a member which is a dictionary of such functions. In adapting the solution, ran in to an unexpected type error.
Question
Given the following class declaration:
class C {
public dict1: {[x: string]: this} = {}; // Error: A 'this' type is available only in a non-static member of a class or interface.
public dict2: Record<string, this> = {}; // OK
public dict3: {[P in string]: this} = {}; // OK?!
}
Why does the declaration of dict1, using an index signature, provoke the error
A 'this' type is available only in a non-static member of a class or interface
but the seemingly-equivalent declarations of dict2 (using the Record<> utility type), and dict3 (using a different index signature based on the definition of Record<>) not provoke the same error?
See microsoft/TypeScript#47868 for a canonical answer to most of this question. The polymorphic
thistype, as implemented in microsoft/TypeScript#4910, generally refers to the type of thethisvalue in the scope in which it is mentioned. For a type likethisrefers to aFooor some subtype ofFoo, so you could implement it likeSo
baz,baz.a,baz.a.a, etc. are all of typeBaz. That's great.But for a type like
what does
thisrefer to? What's the relevant scope? One could possibly expect it to refer to the outer scope, and therefore it'sQux(or a subtype), but maybe it refers to just the inner scope and thus it'sQux["a"](or a subtype). It's ambiguous.TypeScript sidesteps the ambiguity by only allowing
thisto be used in an interface property or a class instance member, with no intervening scopes. That's why the above is an error.In order to specify which one you mean, you have to use some indirection, possibly with generics. If you want the outer scope, you can do this:
Now
thiscan only refer to a subtype ofQux. You can implement it with a class likeSo
q,q.a.b,q.a.b.a.b, etc, are all of typeQuux.If you want the inner scope, you can do this:
Now
thiscan only refer to a subtype ofB. You can implement it likeHere I've used a getter but you could do it with another class if you want. Anyway now
q.a,q.a.b,q.a.b.b, etc, are of typeQuux["a"].For your example,
you've got an index signature, which behaves like a range of keys in an object type. An index signature can sit alongside other properties in an object type (e.g.,
{[k: string]: string; foo: "bar"}).And
{ [x: string]: this }is just as ambiguous as{ a: this }. Doesthisrefer to an instance ofCor just to an thedictproperty ofC? It looks like you intended the former. Thus we need some kind of indirection. You could do so likeBut of course you don't have to write your own
Dicttype. TheRecordutility type can be pressed into service this way, whereRecord<string, T>is essentially the same thing:And finally, if you define a mapped type inline like
it also works, because apparently a mapped type does not introduce its own scope. Note that while the syntax for mapped types looks similar to that of index signatures, they are not the same. (The question refers to this as an index signature, but it is not an index signature.) An index signature can be thought of a piece of an object type (e.g., other properties and signatures can appear inside an object type with it), while a mapped type is its own separate thing (e.g., the curly braces are part of the mapped type; you can't add other properties). See this answer to another question for more about that.
So there you go. You can only use polymorphic
thisin a place where the scope is not ambiguous according to the compiler. You can't do it inside a nested object property, or an index signature, but you can do it with indirection, generics, and apparently mapped types.Playground link to code