3

I already know how to add "newable" (i.e. has constructor) constraint for function arguments (like argument for foo function below), but same techique doesn't apply to generic type parameters.

Why is that and how to fix it ?

enter image description here

type NoParameterCtor<T> = { new(): T }

function foo<T>(ctor: NoParameterCtor<T>) { }

interface Bar<T extends NoParameterCtor<T>> { }

class Zoo { }

foo(Zoo) 
// no compiler error

class Zar implements Bar<Zoo> { }
// Type 'Zoo' does not satisfy the constraint 'NoParameterCtor<Zoo>'
Nandin Borjigin
  • 2,094
  • 1
  • 16
  • 37
  • 3
    No ordinary `T` will satisfy the constraint `T extends NoParameterCtor`, because you are requiring an instance of type `T` to also be a no-parameter constructor function that is capable of creating new instances of type `T`. What were you trying to achieve here? – Matt McCutchen Oct 09 '18 at 19:10
  • 1
    `T extends NoParameterCtor` is a strange constraint on `T`. What are you actually trying to do? Remember that the type `Zoo` is not newable, so `Zoo extends NoParameterCtor` is false. The *value* `Zoo` has type `typeof Zoo` and is newable, but `typeof Zoo extends NoParameterCtor` is also false because `new Zoo()` does not produce a newable thing. Do you just want `T` to be any newable thing? What are you trying to do? – jcalz Oct 09 '18 at 19:11
  • Yeah, what I want is "`T` to be any newable thing", how should I write that constraint? – Nandin Borjigin Oct 10 '18 at 10:42

1 Answers1

4

As mentioned in the comments, T extends NoParameterCtor<T> is an unusual constraint that means "T is a constructor that makes new instances of itself". Unless you are trying to describe self-replicating constructors, this is not what you mean.

If you just want T do be "anything newable", then you don't need to care about the instance type. Assuming you're using TS3.0 or later you can use unknown to mean any type, although you can use also any. So perhaps you want Bar to be

interface Bar<T extends NoParameterCtor<unknown>> { }

The following still won't work though:

class Zar implements Bar<Zoo> { } // error! 
// Zoo does not satisfy the constraint NoParameterCtor<unknown>

That's because the type Zoo is not newable; it's the instance type of the Zoo class. I don't know if you're confused about the difference between named values and named types in TypeScript, but you're in good company if so. In short, class Zoo {} introduces a type named Zoo, which is the type of instances of the class, and a value named Zoo, which is the constructor of such instances. And the type of the Zoo value is not the Zoo type. To refer to the type of the Zoo constructor value, you need to use typeof Foo instead:

class Zar implements Bar<typeof Zoo> { } // okay

Also I assume you've stripped out the contents of Bar, Zar and Zoo because they're not relevant here. But just to be clear, empty interfaces match just about everything because TypeScript uses structural typing. If Bar needs access to the instance type of T, then you can use the built-in library type alias InstanceType<> to get 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
}

Hope that helps. Good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360