1

I am wrangling with this mixin-creating TypeScript code:

function applyMixins(derivedCtor: Function, constructors: Function[]) {
    //Copies methods
    constructors.forEach((baseCtor) => {
      Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
        Object.defineProperty(
          derivedCtor.prototype,
          name,
          Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
            Object.create(null)
        );
      });
    });
    //Copies properties
    constructors.forEach((baseCtor) => {
      let empty = new baseCtor();
      Object.keys(empty).forEach((name) => {
        Object.defineProperty(
          derivedCtor.prototype,
          name,
          Object.getOwnPropertyDescriptor(empty, name) ||
            Object.create(null)
        );
      });
    });
  }

It does not compile, complaining about this row: let empty = new baseCtor();: TS2351: This expression is not constructable. Type 'Function' has no construct signatures.. I know that this can be fixed by swapping both Function type references in the 1st row with any, but I'm trying to live my life without any, as it often is a sloppy way to tell TypeScript to shut up.

Is there a way to implement this code without using any?

2
  • 1
    Function isn't known to be constructable, while, say, new () => object or {new(): object} is. Does this work for your use case? If so I'll write up an answer when I get a chance. If not, please elaborate in the question text with relevant examples. Good luck! Commented Jan 10, 2021 at 18:18
  • Thanks, I added declare type ctorType = new() => object; in the module, and then use ctorType instead of Function. But what is the difference in comparison with {new(): object}? Commented Jan 10, 2021 at 23:25

1 Answer 1

1

The problem is that Function is a very wide type and includes any function, including ones which cannot be called via new. Therefore the compiler is complaining that Function is not constuctible. The solution is therefore to use a type which is known to have a construct signature. In TypeScript, such a signature is represented by prepending the keyword new to a function signature:

type NewableFunctionSyntax = new () => object;
type NewableMethodSyntax = { new(): object };

Those types both represent a constructor that accepts no arguments and produces an instance of type assignable object. Note that while those syntaxes are different, they are essentially the same. (To see this, note that the compiler allows you to declare a var multiple times but will complain if you annotate it with different types. The fact that the following compiles with no error,

var someCtor: NewableFunctionSyntax;
var someCtor: NewableMethodSyntax; // no error

is an indication that the compiler treats NewableFunctionSyntax and NewableMethodSyntax as essentially interchangeable.)


By changing Function to one of these, your code now compiles with no errors:

function applyMixins(derivedCtor: { new(): object }, constructors: { new(): object }[]) {
    //Copies methods
    constructors.forEach((baseCtor) => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
            Object.defineProperty(
                derivedCtor.prototype,
                name,
                Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
                Object.create(null)
            );
        });
    });
    //Copies properties
    constructors.forEach((baseCtor) => {
        let empty = new baseCtor();
        Object.keys(empty).forEach((name) => {
            Object.defineProperty(
                derivedCtor.prototype,
                name,
                Object.getOwnPropertyDescriptor(empty, name) ||
                Object.create(null)
            );
        });
    });
}

Let's test out calling applyMixins() to make sure we understand what {new(): object} does and does not match:

class Works {
    x = 1;
    constructor() { }
}
applyMixins(Works, []); // okay

Works is fine because it is a class constructor which takes no parameters.

class CtorRequiresArg {
    y: string;
    constructor(y: string) { this.y = y; }
}

applyMixins(CtorRequiresArg, []); // error!
// -------> ~~~~~~~~~~~~~~~
// Type 'new (y: string) => CtorRequiresArg' 
// is not assignable to type 'new () => object'

CtorRequiresArg fails because you have to pass a string argument when you construct it, like new CtorRequiresArg("hello")... but applyMixins() only accepts constructors that can be called without any arguments.

And finally:

function NotACtor() { }

applyMixins(NotACtor, []); // error!
// -------> ~~~~~~~~
// Type '() => void' provides no match 
// for the signature 'new (): object'

NotACtor fails because it is not considered constructible. This may be surprising because at runtime nothing will stop you from calling new NotACtor(), but it is the compiler's opinion that if you wanted a class constructor you would be using class notation in .ts files... even when targeting an ES5 runtime, since TypeScript will down-level it for you automatically. (See microsoft/TypeScript#2310 for more information)


Playground link to code

Sign up to request clarification or add additional context in comments.

1 Comment

Does there exist a type declaration that captures any constuctible, no only constuctibles with specific signatures? I tried with new(...args: unknown[]) => object;, but that does not allow constructors with specificly typed parameters. With new(...args: any[]) => object; the compiler doesn't complain any more, but then we are using reference any that defies the purpose of my question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.