I have an object type that can have any properties it wants, but one of its properties must be a function (if defined). The problem is: I want this function type to include all of the other properties as its argument. See the playground to understand better.
// https://github.com/microsoft/TypeScript/issues/14829#issuecomment-504042546
type NoInfer<T> = [T][T extends any ? 0 : never];
type Indexable = {
[key: string]: any
}
type Events<T extends Indexable = Indexable> = {
onFoo?: (arg: Omit<T, keyof Events>) => void
}
type Foo<T extends Indexable> = T & Events<NoInfer<T>>
declare function test<T extends Indexable>(foo: T & Foo<T>): void
test({
a: 1,
onFoo(arg) {
// "typeof arg" should be "{ a: number }"
}
})
I imagine it's an issue with circular types, since this basic example also doesn't work.
I can't get the type inference to work the way you want. No matter what I do, either T fails to infer, or it infers but the onFoo property's argument isn't properly infer. I'd probably consider this less of a compiler bug and more of a design limitation. Possibly it's related to not having a genuine non-inferential type parameter usage site for the onFoo parameter. And possibly it has something to do with how many passes the type inference algorithm needs to make in order to succeed. I'm really not sure. I do know that when I fight with the compiler like this, I usually lose. So, it might be worth trying to do something less controversial.
A more straightforward approach is to use two function parameters (which I see you don't want to do), as in:
const makeOnFoo = <T>(
foo: T,
onFoo?: (arg: T) => void
): T & { onFoo?: (arg: T) => void } => Object.assign(foo, { onFoo });
That function will infer exactly the way you want, I believe, where the inferred arg will be of type T. And if you have test() defined like this:
declare function test<T>(
foo: T & { onFoo?: (arg: Omit<T, "onFoo">) => void }
): void;
You can at least use makeFoo() to get the inference you want:
test(
makeOnFoo({ a: 1 }, arg => {
console.log(arg.a.toFixed()); // okay
})
);
I don't know if that's acceptable for you, but at least it plays more nicely with the compiler.
Oh well, hope that helps somewhat. Good luck!
Link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With