10
votes

I've been playing a bit with interfaces with construct signatures in TypeScript, and I became a bit confused when the following failed to type check:

class Foo {
    constructor () {
    }
}

interface Bar {
    new(): Bar;
}

function Baz(C : Bar) {
    return new C()
}

var o = Baz(Foo);

The type error is:

Supplied parameters do not match any signature of call target: Construct signatures of types 'new() => Foo' and 'Bar' are incompatible: Type 'Bar' requires a construct signature, but Type 'Foo' lacks one (C: Bar) => Bar

The type of Foo's constructor is () => Foo, and that is what I thought that Bar said. Am I missing something here?

5

5 Answers

5
votes

Here is an updated version of your code with a subtle change.

We define the Bar interface with whatever functions and variables we expect to be present.

Next, we extend the Bar interface with the NewableBar interface. This just defined a constructor that returns a Bar.

Because Foo implements Bar and has a constructor and Baz requires a NewableBar, everything is checked.

This is a little more verbose than any - but gives you the checking you want.

interface Bar {

}

interface NewableBar extends Bar {
    new();
}

class Foo implements Bar {
    constructor () {

    }
}

function Baz(C : NewableBar) {
    return new C()
}

var o = Baz(Foo);
2
votes

The problem (at least from the TypeScript compiler's point of view) is the signature of Bar's new method. If you replace the definition of Bar with the following,

interface Bar {
  new (): any;
}

it works. You might as well use new (): Foo, just Bar as return value does not work.

1
votes

I think I know where you are taking this and I think you need a subtly different approach.

This example says the following:

  • Baz must be passed an item that is newable.
  • Baz will return a Bar
  • Not all Bar's need to be newable, only those being passed to Baz

Here is the example:

interface Bar {
    sayHello(name: string): void;
}

interface Newable {
    new();
}

class Foo implements Bar {
    constructor () {

    }

    sayHello(name: string) {
        window.alert('Hello ' + name);
    }
}

function Baz(C : Newable) {
    return <Bar> new C()
}

var o = Baz(Foo);
o.sayHello('Bob');

The only danger of this approach is that you could pass something newable that wasn't a Bar to the Baz function. As you are using a dynamic feature by creating an object from an argument, this is largely unavoidable unless you are willing to pass in a pre-initialized object, in which case Baz would quite happily just accept a Bar, rather than a newable.

1
votes

I know this is an old question, but I had a need for something similar today and came across post. After some trial and error I came up with the following solution:

interface Bar { 
    sayHello(name: string);
}

class Foo implements Bar {
    sayHello(name: string) {
        window.alert("Hello " + name);
    }
}

function Baz(c: new() => Bar) {
    return new C();
}

var o = Baz(Foo);
o.sayHello("Bob");

Basically interfaces can only define the contract of an instance of an object, so requiring a certain constructor to exist must be done at the function that will be calling the constructor. This method also plays nicely with generics:

function Baz<T extends Bar>(c: new() => T) {
    return new c();
}

var o = Baz(Foo);

In the example above the variable "o" will be inferred as type Foo.

0
votes

There is a simple answer really. Look at your Bar interface:

interface Bar {
    new(): Bar;
}

Let's say you have managed to create some Magic class that implements this interface.

class Magic implements Bar {
    // ...
}

What this means is that any instance of Magic must adhere to the specification of Bar. So let's say you have an instance of Magic called magic.

Since magic adheres to the specification of Bar, magic must be newable, which means the following must work:

const foo = new magic() // lowercase m!

Also, the result of that call, i.e. foo, must itself adhere to Bar. This means that the following must also work:

const zzz = new foo();

And then it is clear that the following must also work:

new (new (new (new (new (new (new (new Magic())))))))

And it is clear that your code doesn't fulfill this condition. Therefore, your code does not type check.