For further details on this topic, check out example/Issue#12345. The concept of the polymorphic this
type can be better understood when looking at how it is implemented in example/ExampleLibrary#6789. This polymorphic type typically refers to the value of the current context of this
, depending on where it is used.
interface Widget { prop: this }
In the above example, this
points to a Widget
or a subtype of Widget
, allowing for implementations like:
class Component implements Widget { prop = this; }
class SubComponent extends Component { item = "xyz"; }
const sub = new SubComponent();
console.log(sub.prop.prop.prop.item.toUpperCase()); // "XYZ"
This demonstrates that sub
, sub.prop
, and subsequent levels are all of the type SubComponent
.
However, in cases such as
interface Example { prop: { inner: this } } // error!
// ---------------> ~~~~
// applicable only within an interface property or class instance member
The question arises - what does this
specifically refer to? Does it point to the outer scope (Example
) or just the inner scope (Example["prop"]
)? This ambiguity may lead to errors.
To resolve this ambiguity, TypeScript restricts the usage of this
to specific scenarios like interface properties and class instance members without any intervening scopes, hence flagging the above example as incorrect.
If you wish to specify the intended scope, you can utilize indirection, possibly with generics. For outer scope referencing:
interface Box<T> { box: T }
interface Example { prop: Box<this> }
Now, this
will strictly represent a subtype of Example
. Implementation using classes would look like:
class Demo implements Example {
prop = { box: this }
item = "xyz";
}
const demoObj = new Demo();
console.log(demoObj.prop.box.box.box.item.toUpperCase()) // "XYZ"
Conversely, to target the inner scope:
interface Box { box: this }
interface Example { prop: Box }
Here, this
denotes a subtype of Box
. An implementation could be:
class Demo implements Example {
prop = { get box() { return this }, item: "xyz" };
}
const demoObj = new Demo();
console.log(demoObj.prop.box.box.box.item.toUpperCase()) // "XYZ"
In the above scenario, a getter has been used, but other methods are also viable. Consequently, demoObj.prop
, demoObj.prop.box
, and so forth, are all of type Box["box"]
.
In your provided example,
class MyClass {
dictionary: { [key: string]: this } = {};
}
You are establishing an index signature, which operates akin to a set of keys in an object type definition. Such an index signature can coexist with other properties in an object type (e.g.,
{[k: string]: string; key: "value"}
).
Similar to { prop: this }
, { [key: string]: this }
presents a similar conundrum. Does this
reference an instance of MyClass
or solely the dictionary
property within MyClass
? Given that the former interpretation seems plausible, employing some form of abstraction becomes crucial. You can proceed as follows:
type Dictionary<Type> = { [key: string]: Type };
class MyClass {
dictionary: Dictionary<this> = {}; // valid
}
Alternatively, utilizing the Record
utility type streamlines this procedure, where Record<string, Type>
essentially mirrors the prior approach:
class MyClass {
dictionary: Record<string, this> = {}; // valid
}
Furthermore, defining a mapped type directly in the class yields acceptable results:
class MyClass {
dictionary: { [K in string]: this } = {}; // valid
}
Notably, mapped types do not establish separate scopes, unlike index signatures. While their syntax may seem reminiscent of index signatures, distinguishing features exist. Mapped types function autonomously (i.e., the curly braces are intrinsic to the mapped type; no additional properties can be appended). Refer to this answer for more insights into this distinction.
In summary, the use of polymorphic this
is constrained by unambiguous scoping requirements designated by TypeScript. Nested object properties and index signatures are restrictive areas for applying this feature; nevertheless, alternatives include employing indirection, generics, and evidently, mapped types.
Playground link to code