Dmitry Papka
Dmitry Papka

Reputation: 1359

Constructor interface: Class without matching constructor is still assignable

Imagine we have an interface which defines what arguments should constructor accept:

interface Ctr {
    new (num: number): any;
}

And then we have a class with default constructor:

class DefCtr {
}

Then, for some reason, I can assign DefCtr class to a variable of type Ctr:

const Instance: Ctr = DefCtr;

const i = new Instance(1);
console.log(i);

// The output is:
// DefCtr {}

So the Instance is a DefCtr class (which has no constructor accepting number). But interface is forcing me to pass a number (which will be ignored) when creating an instance of it.

What's interesting this works only when class has the default constructor. For example, this won't work:

interface Ctr {
    new (num: number): any;
}

class NotDefCtr {
    // Lets create a non-default constructor
    constructor(str: string) {
    }
}

const Instance: Ctr = NotDefCtr; // Error:

// Type 'typeof NotDefCtr' is not assignable to type 'Ctr'.
// Types of parameters 'str' and 'num' are incompatible.
// Type 'number' is not assignable to type 'string'.

Error makes sense. But from my point of view, it would also make sense to receive an error when trying to assign a class with default constructor (since it doesn't have any other constructor matching interface).

Upvotes: 2

Views: 67

Answers (1)

jcalz
jcalz

Reputation: 329453

The TypeScript FAQ has an entry: Why are functions with fewer parameters assignable to functions that take more parameters?. Also see the handbook documentation for function type compatibility. This is the same issue except with a newable instead of a callable. I'll translate that entry to this case:

This is the expected and desired behavior. There is a principle of substitutability that says that if an object X can be used in place of some object Y, then X is a subtype of or assignable to Y.

In this case, DefCtr is a subtype of Ctr because DefCtr's constructor can safely ignore extra number parameter. But NotDefCtr is not a subtype of Ctr because NotDefCtr's type indicates that it expects an argument of type string; a number cannot safely be used as a string, so it's not compatible.

(Aside: it turns out that NotDefCtr's constructor actually ignores its input, so at runtime no explosion would happen. But the type system does not care about the particular implementation of the constructor; it sees the signature a contract saying that NotDefCtr's constructor is within its rights to expect an argument of type string. So it's still an error to substitute a Ctr with NotDefCtr.)

Upvotes: 3

Related Questions