You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
NoInfer does not work in function parametersΒ #60071
But NoInfer just blocks inference at a location and T is already not inferrable from the unannotated callback param. Iβd say youβre mistakenly attributing the problem to NoInfer. This feels more like #47599 to me.
NoInfer just blocks inference at a location and T is already not inferrable from the unannotated callback param
Clearly generic T inference is not blocked by the function parameter as it's falling back to unknown for no other reason but being an inference point for that generic and the only job of NoInfer is to make sure that it's not happening.
Just to further showcase this, if you drop that function parameter - generic will be inferred as expected from the return type of the function:
declarefunctionfn<T>(cb: ()=>T): T;fn(()=>'123');// We are getting: function fn<string>(cb: () => string): string
Am I missing something here?
Because to me is seems that NoInfer should exactly block inference in the function parameters, just like it does in other scenarios, but it simply does not do anything, which seems to me that this specific use-case was not implemented.
You are expecting data => '123' to be inferred as type (data?: string) => string which is a matter of contextual typing of the data parameter. But TS is unable to infer that because, in general, it needs to know the types of a function's parameters to know its return type. (e.g., what's the return type of data => data??"123"?) In a case like data => '123' this is easy because the return type does not depend on anything. But then you could have written () => '123' instead. And if you do that, your inference will suddenly work with or without NoInfer:
TypeScript looks at data => '123' and defers typing it until it knows what data is, but by then it is too late for inference to happen the way you want. The inference fails and T falls back to unknown. Falling back to unknown is just a failure of inference. It does not mean that TS looks at data and infersunknown from it, so putting NoInfer<T> there doesn't prevent the problem.
It looks like you ran into the problem of context-sensitive generic interactions, such as that discussed in #47599, tried to use NoInfer to "block" inference from a place it wasn't happening anyway, saw that this failed, and interpreted this as a failure of NoInfer to act as advertised. But that's just not the case. If there's ever any solution to your problem, it will probably not involve NoInfer.
And there might never be a solution. I think they've declined previous suggestions to have TS determine whether the return type of a function implementation is independent of any of its unannotated parameter types, which is what you'd need here. The easy cases where someone could simply drop a parameter can be done by the user (e.g., write ()=>'123' instead of data => '123'), and then you have all kinds of more complex cases where TS would have to do lots of extra work.
Reacted by Ryan Cavanaugh, Alex Malkevich and Alvis Tang
Your function return type depends on the type of data. In this case, you have a circular dependency and nothing particularly meaningful can be resolved
Your function return type doesn't depend on the type of data. In this case, you can remove it from the parameter list
Despite appearances, there isn't a third case where data is read but doesn't have a possible effect on the return type. If data is read, then it has impacts on control flow analysis, and CFA can effect which return statements are reachable and which aren't (thus changing the return type).
Thanks Joe Calzaretta (@jcalz) for detailed explanation it actually now makes sense why it's not related to the NoInfer. I think I had a different expectation of what it was doing and it seemed like it was not doing it's job, but it's not even involved in this use-case.
My use-case is like the first one that Ryan Cavanaugh (@RyanCavanaugh) mentioned - I have a function that accepts previous data and creates a new one based on it, something like a reducer, with some minor props access inside. So it's kind of recursive but it makes sense to type the function based on the return type when the type is defined in the return type of the function it calls internally:
functionmakeReducer<T>(fn: (prev?: T)=>T): T;// Here I do not really want to type function parameter but rather infer it from used inner function's return typemakeReducer(prev=>processState(prev.state));// The function typing is defined herefunctionprocessState(state?: State): FullState;
It seems that the underlying issues is that the inner function here has different input and output types while the function parameter expects both input and output types to be the same - so we cannot simply pass fully typed function as a parameter but need to define intermediate function which translates from out input type to another, but it currently requires us to specify full types of that intermediate function even though it's in theory possible to infer it's signature as we have all the necessary information.
I'm wondering if this use-case would be considered in the #47599 which you mentioned above or should I maybe create another issue to track it?
Maybe not #47599; Looks more like #56311 as Martin Johns (@MartinJohns) mentioned (and thus #49618). I don't know if your case is distinct enough from those to open a new issue, but it looks like everyone in those threads are saying it's probably too complex for the case where it actually matters, like foo => f(foo). Lots of extra work for the TS compiler, more potential control flow analysis circularity to trip over, all to avoid annotating your function parameters. It might be nice for x => document.getElementById(x) to infer that x is string, but that's just backwards from how inference currently works in TS, and the workaround is to just write (x: string) => yourself. Anyway I'm not a TS team member so I don't speak authoritatively. Good luck!
// Here I do not really want to type function parameter but rather infer it from used inner function's return typemakeReducer(prev=>processState(prev.state));
is probably a "further down the rabbit hole" bullet item on #47599 where one of the deferral cases is functions whose return expressions are all calls to un-overloaded non-generic functions.
Activity
MartinJohns commentedon Sep 26, 2024
Sounds like a duplicate of #56311.
gund commentedon Sep 26, 2024
It might be related but that issue is old and does not include
NoInferfeature which was released in v5.4, way after that bug was created.jcalz commentedon Sep 26, 2024
But
NoInferjust blocks inference at a location andTis already not inferrable from the unannotated callback param. Iβd say youβre mistakenly attributing the problem toNoInfer. This feels more like #47599 to me.gund commentedon Sep 26, 2024
Joe Calzaretta (@jcalz) I do not understand what do you mean by:
Clearly generic
Tinference is not blocked by the function parameter as it's falling back tounknownfor no other reason but being an inference point for that generic and the only job ofNoInferis to make sure that it's not happening.Just to further showcase this, if you drop that function parameter - generic will be inferred as expected from the return type of the function:
Here is this example in a playground.
Am I missing something here?
Because to me is seems that
NoInfershould exactly block inference in the function parameters, just like it does in other scenarios, but it simply does not do anything, which seems to me that this specific use-case was not implemented.jcalz commentedon Sep 26, 2024
You are expecting
data => '123'to be inferred as type(data?: string) => stringwhich is a matter of contextual typing of thedataparameter. But TS is unable to infer that because, in general, it needs to know the types of a function's parameters to know its return type. (e.g., what's the return type ofdata => data??"123"?) In a case likedata => '123'this is easy because the return type does not depend on anything. But then you could have written() => '123'instead. And if you do that, your inference will suddenly work with or withoutNoInfer:TypeScript looks at
data => '123'and defers typing it until it knows whatdatais, but by then it is too late for inference to happen the way you want. The inference fails andTfalls back tounknown. Falling back tounknownis just a failure of inference. It does not mean that TS looks atdataand infersunknownfrom it, so puttingNoInfer<T>there doesn't prevent the problem.It looks like you ran into the problem of context-sensitive generic interactions, such as that discussed in #47599, tried to use
NoInferto "block" inference from a place it wasn't happening anyway, saw that this failed, and interpreted this as a failure ofNoInferto act as advertised. But that's just not the case. If there's ever any solution to your problem, it will probably not involveNoInfer.And there might never be a solution. I think they've declined previous suggestions to have TS determine whether the return type of a function implementation is independent of any of its unannotated parameter types, which is what you'd need here. The easy cases where someone could simply drop a parameter can be done by the user (e.g., write
()=>'123'instead ofdata => '123'), and then you have all kinds of more complex cases where TS would have to do lots of extra work.RyanCavanaugh commentedon Sep 26, 2024
Agree with Joe Calzaretta (@jcalz) on this. Fundamentally there are two cases here:
data. In this case, you have a circular dependency and nothing particularly meaningful can be resolveddata. In this case, you can remove it from the parameter listDespite appearances, there isn't a third case where
datais read but doesn't have a possible effect on the return type. Ifdatais read, then it has impacts on control flow analysis, and CFA can effect whichreturnstatements are reachable and which aren't (thus changing the return type).gund commentedon Sep 26, 2024
Thanks Joe Calzaretta (@jcalz) for detailed explanation it actually now makes sense why it's not related to the
NoInfer. I think I had a different expectation of what it was doing and it seemed like it was not doing it's job, but it's not even involved in this use-case.My use-case is like the first one that Ryan Cavanaugh (@RyanCavanaugh) mentioned - I have a function that accepts previous data and creates a new one based on it, something like a reducer, with some minor props access inside. So it's kind of recursive but it makes sense to type the function based on the return type when the type is defined in the return type of the function it calls internally:
It seems that the underlying issues is that the inner function here has different input and output types while the function parameter expects both input and output types to be the same - so we cannot simply pass fully typed function as a parameter but need to define intermediate function which translates from out input type to another, but it currently requires us to specify full types of that intermediate function even though it's in theory possible to infer it's signature as we have all the necessary information.
I'm wondering if this use-case would be considered in the #47599 which you mentioned above or should I maybe create another issue to track it?
jcalz commentedon Sep 26, 2024
Maybe not #47599; Looks more like #56311 as Martin Johns (@MartinJohns) mentioned (and thus #49618). I don't know if your case is distinct enough from those to open a new issue, but it looks like everyone in those threads are saying it's probably too complex for the case where it actually matters, like
foo => f(foo). Lots of extra work for the TS compiler, more potential control flow analysis circularity to trip over, all to avoid annotating your function parameters. It might be nice forx => document.getElementById(x)to infer thatxisstring, but that's just backwards from how inference currently works in TS, and the workaround is to just write(x: string) =>yourself. Anyway I'm not a TS team member so I don't speak authoritatively. Good luck!gund commentedon Sep 26, 2024
Thanks for clarification, I will close this issue.
RyanCavanaugh commentedon Sep 26, 2024
The provided example
is probably a "further down the rabbit hole" bullet item on #47599 where one of the deferral cases is functions whose return expressions are all calls to un-overloaded non-generic functions.