I have following scenario:
class Dispatcher<R extends (...args: any) => any, T = undefined> {
fn: R
extra: T
constructor(fn: R, extra?: T) {
this.fn = fn;
this.extra = extra;
}
}
const test = new Dispatcher(() => {
return true;
});
// TS type hinting should know that extra is undefined
test.extra;
const test2 = new Dispatcher(() => {
return true;
}, {
graphUrl: "/foo/"
});
// TS type hinting should know that extra exists and has a property graphUrl
test2.extra.graphUrl
In this case I want the extra class member to infer based on whether it was passed to the constructor. If I pass a boolean, it should be a boolean, if I pass a string, it should be a string, if I pass undefined, it should be undefined. Right now the inference works and extra properly types, but TS throws an error in the constructor code when I assign this.extra = extra. Strictly speaking that error makes sense because I'm assigning a type that can be undefined to a type that cannot. The problem is if I update the member definition to be extra?: T then the inference breaks because now it thinks extra is something like string | undefined, which is incorrect. The problem is the extra variable isn't actually "optional" it is the value that it was passed, that value may be undefined... that's different than optional.
Type 'T | undefined' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'T | undefined'.
If I fix the error in the constructor code, then the type inference stops working and I get something like string | undefined which isn't accurate. We know the type because it was passed to the constructor.