In the discussions, it was pointed out that
T extends NoParameterCtor<T>
is a peculiar constraint implying "
T
is a constructor that creates new instances of itself". Unless you are aiming to define constructors that can replicate themselves, this may not be what you intend.
If your goal is simply for T
to be "anything that can be instantiated", then you don't necessarily need to specify the instance type. In TypeScript version 3.0 or newer, you can use unknown
to represent any type, although any
can also be used. So perhaps you want Bar
to be
interface Bar<T extends NoParameterCtor<unknown>> { }
However, the following will still encounter issues:
class Zar implements Bar<Zoo> { } // error!
// Zoo does not fulfill the constraint NoParameterCtor<unknown>
This occurs because the type Zoo
cannot be instantiated; it represents the instance type of the Zoo
class. You might be confused about the distinction between named values and named types in TypeScript, but if so, rest assured that you're not alone. Essentially, class Zoo {}
introduces a type called Zoo
, which denotes instances of the class, as well as a value named Zoo
, which serves as the constructor for such instances. The type of the Zoo
value differs from the Zoo
type. To reference the type of the Zoo
constructor value, you should utilize typeof Foo
instead:
class Zar implements Bar<typeof Zoo> { } // okay
I'm assuming you removed the contents of Bar
, Zar
, and Zoo
as they aren't pertinent here. However, just to clarify, empty interfaces generally match any structure due to TypeScript's structural typing. If Bar
needs access to the instance type of T
, you can make use of the predefined library type alias InstanceType<>
to obtain it:
interface Bar<T extends NoParameterCtor<unknown>> {
theConstructor: T,
theInstance: InstanceType<T>
}
class Zar implements Bar<typeof Zoo> {
theConstructor = Zoo; // the constructor
theInstance = new Zoo(); // an instance
}
Hopefully, this explanation proves helpful. Best of luck!