so originally to enforce a constraint, i\'d just apply an interface like this
// this is the constraint
interface Topic {
[key: string]: (...args: any[]) =>
The way to deal with this is not to annotate your variable at all, but let the compiler infer its type. Then, sometime later, when you use that variable somewhere that expects a Topic
, you'll get the desired error there:
const topic = {
async actuate(a: boolean) { }
};
topic.actuate(true);
const badTopic = {
oops(a: boolean) { }
}
badTopic.oops(true);
// later ...
function topicTaker(t: Topic) {
// do something with a Topic
}
topicTaker(topic); // okay
topicTaker(badTopic); // error!
// ------> ~~~~~~~~
// Property 'oops' is incompatible with index signature.
So this works, and in some use cases this is really sufficient: you don't actually care if the variable is of the right type until you try to use it.
But what if this error check is too far from the object declaration to be helpful to you?
Well, if you want to catch the error earlier, you can do what I usually do and make a helper function:
const asTopic = <T extends Topic>(topic: T) => topic;
This constrained generic identity function just returns its argument at runtime. But at compile time it will require that topic
is assignable to Topic
without actually widening it to topic
. Instead of annotating your variables, just initialize them to the return value of the helper function:
const topic = asTopic({
async actuate(a: boolean) { }
});
topic.actuate(true); // okay
const badTopic = asTopic({
oops(a: boolean) { } // error!
// ~~~~
// void is not assignable to type Promise<any>
})
You can look at asTopic()
two ways: as a replacement for annotation to avoid widening (i.e., use asTopic()
instead of : Topic
), or as an early warning that your object is not of the type you want (i.e., asTopic()
acts like a topicTaker()
that you call immediately at the creation site instead of deferring it).
Re-reading the question: this asTopic()
helper function is probably the thing you were feeling around for with your TopicConstraint
.
Playground link to code
One option is to use a no-op function that has the relevant generics in the argument so it constrains in place:
(playground)
// this is the constraint
interface Topic {
[key: string]: (...args: any[]) => Promise<any>
}
function ensureTopic<T extends Topic>(topic: T){
return topic;
}
// object that must pass the constraint
const topic = ensureTopic({
// GOOD: topic methods must conform
async actuate(a: boolean) {}
})
related: Use function interface to ensure parameters but infer more specific return type