Ok, look at the definition of ErrorConstructor :
interface ErrorConstructor { new(message?: string): Error;
So, ErrorConstructor must be a constructor that takes an optional string argument, and must also be called as a function, and it must have a Error prototype. Take a look at your FooError class:
class FooError extends Error { } FooError.prototype;
It has a FooError prototype, which is great, because FooError is a subtype of Error . This is a constructor, and it takes an argument, since it overloads the superclass Error , which is ErrorConstructor . But this is not called a function. Thus, FooError not an ErrorConstructor .
At this point, you need to decide whether you really care that it is an ErrorConstructor . Do you plan to call it FooError('msg') instead of new FooError('msg') ? I doubt it. In this regard, do you care that the prototype constructor is of type FooError ? Probably no. In this case, do not use ErrorConstructor . Instead, use the following interface, which only cares about being a constructor with an optional single-line-arg:
interface NoFrillsErrorConstructor { new(message?: string): Error; }
Now your code will work:
const map = new Map<NoFrillsErrorConstructor, any> ([ [Error, 'do this'], [FooError, 'do that'] ])
And everything will be fine if you use only map keys as constructors:
map.forEach((val, err) => { err.prototype; // exists, but is type any. Who cares, right? new err(); // okay err(); // not okay })
(If you care about a FooError corresponding to ErrorConstructor , this can be organized, but it is a bit more annoying. Let me know if you want me to clarify.)
Anyway, hope this helps; good luck!