I'm pretty sure that both examples down below should be working.
interface Test<S> {
func1: (arg) => S;
func2: (arg:S) => any;
}
function createTest<S>(arg: Test<S>): Test<S> {
return arg;
}
createTest({
func1: () => {
return { name: "eliav" };
},
func2: (arg) => {
console.log(arg.name); //works - name is recognized
},
});
createTest({
func1: (arg) => {
return { name: "eliav" };
},
func2: (arg) => {
arg; // type unknown, why?
console.log(arg.name); //ERROR - name is NOT recognized
},
});
the only difference is with arguments of func1
why typescript infers 'unknown' in the second example?
Playground (noImplicitAny=false)
CodePudding user response:
This is a design limitation of TypeScript. See microsoft/TypeScript#11183 for an authoritative answer about what's happening. There is also another similar issue at microsoft/TypeScript#25825.
When you call this:
createTest({
func1: () => ({ name: "eliav" }),
func2: (arg) => console.log(arg.name)
});
The compiler can only possibly infer the S
generic type parameter from the return type of the func1
callback. The type of the func1
callback is immediately inferred to be () => {name: string}
, and so S
is inferred as {name: string}
. Therefore func2
's arg
parameter is also inferred to be {name: string}
, and everything works.
On the other hand, when you call this:
createTest({
func1: (arg) => ({ name: "eliav" }),
func2: (arg) => console.log(arg.name) // error
});
The compiler again tries to infer the type of the func1
callback. But now the type of the callback is context sensitive; there is an arg
parameter with no type annotation, so the compiler needs to infer the type of arg
form the context in which the callback appears. And, unfortunately for you, the current TypeScript inference algorithm defers inference here. It decides to wait until after it tries to infer the generic type parameters (which is S
in this case) before it comes back and uses context to infer the type of arg
.
This fails spectacularly for your use case. Your return value is of type {name: string}
regardless of the type of arg
, but because the compiler tried to infer S
without it, the inference fails and falls back to unknown
. When arg
is finally inferred it is correctly typed as any
, given the context of the createTest()
argument being Test<S>
, but that's not what you care about.
And so S
is unknown
, and therefore the arg
callback parameter of func2
is inferred as unknown
, and you get an error:
createTest({
func1: (arg: any) => ({ name: "eliav" }),
func2: (arg) => console.log(arg.name) // error
});
So that's why it's happening. The inference algorithm TypeScript follows uses heuristics that often work, but sometimes they fail for certain use cases. There is a discussion at microsoft/TypeScript#30134 about the possibility of introducing a "full unification algorithm" which could possibly work in a wider set of use cases. That issue is meant to gather information about use cases that fail with the current algorithm to see if it would be worth the time and effort to implement. If you really care about seeing that happen you might go there, give it a