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
Unstable type inference when generic is inferred from a return type #29771
This image is probably the most egregious example (state is { foo: string }):
But if I then type a whitespace or something to get the compiler to update, sometimes it starts seeing state as {}:
There are also some cases I can't screenshot properly. For example, this is the full derivation when it is almost working correctly:
But if I cause some input to happen, the derivation might reset to <{}>. However, pressing command to be able to use the screenshot tool causes the tooltip to update and it goes back to the full derivation.
The only thing that is consistent is that type of the previous argument seems to always be derived as {}.
Related Issues:#29638 and probably a bunch of others that look similar but don't quite show this odd / pathological behavior.
Giving makeFoo directly as the argument to the function avoids the problem but the current state is a minimum reproducible example of a problem I found that can't just be changed to that.
A samller example of what you are trying to do would be:
declarefunctionuseDerivedState<T>(initializer: (previous: T)=>T): Tconststate=useDerivedState(previous=>0)// state is {}
Just as an opinion, I don't think typescript can deal with this scenario well at the moment. It's probably trying to figure out the type of the callback, and to do that it needs a type for the parameter, and there is nowhere to get that from so goes with the default of {}.
Yeah, that is probably the central cause, but the odd thing is that the inference was so unstable and would change by just interacting with the IDE even without changing the source code.
Luckily, because I only actually needed a small portion of the previous that has a very simple type to be able to calculate the next value, I managed to refactor things such that I can use ReturnType to get the fairly complex derived type of previous itself.
Our inference system can't really deal with a t => t' kind of function to do inferences from both sides to figure out what's going on. Sometimes language service operations manage to avoid noticing, but this is really a side effect.
Oddly, when you give the compiler less information it gets the inference correct. I don't know why this is, and perhaps is related to the "unreliability" that others were talking about:
interfaceApple<T>{parse: (value: string)=>T,stringify: (value: T)=>string,};declarefunctioneatApple<T>(apple: Apple<T>): voideatApple({// infers `T` as `unknown`parse: value=>Number.parseInt(value),// errors because `value` is `unknown`stringify: value=>value.toString(),})eatApple({// infers `T` as `number`parse: (value: string)=>Number.parseInt(value),// works because `value` is `number`stringify: value=>value.toString(),})interfaceBanana<T>{parse: (value: string)=>T}declarefunctioneatBanana<T>(banana: Banana<T>): voideatBanana({// `T` correctly inferred as `number`parse: value=>Number.parseInt(value),})
Of note is that an instance of Apple incorrectly inferred T as unknown when given an untyped parse function, however Banana correctly inferred T as number when given the exact same function. The only difference between the two is that Apple has additional properties.
Activity
dragomirtitian commentedon Feb 6, 2019
A samller example of what you are trying to do would be:
Just as an opinion, I don't think typescript can deal with this scenario well at the moment. It's probably trying to figure out the type of the callback, and to do that it needs a type for the parameter, and there is nowhere to get that from so goes with the default of
{}.Jessidhia commentedon Feb 6, 2019
Yeah, that is probably the central cause, but the odd thing is that the inference was so unstable and would change by just interacting with the IDE even without changing the source code.
Luckily, because I only actually needed a small portion of the
previousthat has a very simple type to be able to calculate the next value, I managed to refactor things such that I can useReturnTypeto get the fairly complex derived type ofpreviousitself.RyanCavanaugh commentedon Feb 14, 2019
Our inference system can't really deal with a
t => t'kind of function to do inferences from both sides to figure out what's going on. Sometimes language service operations manage to avoid noticing, but this is really a side effect.flaviaan commentedon Mar 11, 2019
Same here, which is very sad 😞
Expected behavior:
ServerConfigto be of typeActual behavior:
Playgroung link
The return types are sometimes inferred, sometimes not, which makes it unreliable 😢
MicahZoltu commentedon Feb 9, 2020
Oddly, when you give the compiler less information it gets the inference correct. I don't know why this is, and perhaps is related to the "unreliability" that others were talking about:
Of note is that an instance of
Appleincorrectly inferredTasunknownwhen given an untypedparsefunction, howeverBananacorrectly inferredTasnumberwhen given the exact same function. The only difference between the two is thatApplehas additional properties.threehams commentedon Feb 9, 2020
That makes sense, though. Inference only breaks down when the generic is used in both a return type position and another position.