It's logical that the function fails when the default value is set, but succeeds when a value is passed to it.
When defining the function, the type T
is unknown, making
T & ((...args: any[]) => any)
also mysterious.
Intersection types:
An intersection type, Person & Serializable & Loggable, for example,
is a Person and Serializable and Loggable. That means an object of
this type will have all members of all three types.
But in your scenario, what does T
represent?
By passing a value when calling the function, the compiler can deduce what T
is, avoiding failures and inferring that T
is () => { type: string; }
.
The same outcome occurs with:
function fn<T>(a: T & string = "str") { // fails
return null;
}
fn2("str"); // doesn't fail
However, using union types prevents failure:
function fn<T>(a: T | string = "str") {
return null;
}
Since "str" is a string, the specific type of T
becomes irrelevant.
Edit
If I understand correctly, intersection types may not be suitable for your needs.
A potentially better solution could be:
type MyCallback = (...args: any[]) => any;
let defaultFn: MyCallback = () => { return { type: 'abc' }; };
function doSomething<T extends MyCallback>(fn: T = defaultFn as T) {
...
}
You could replace the type
with an interface:
interface MyCallback {
(...args: any[]): any;
}
(code in playground)