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
Void parameter still required when type extends generic #29131
This is a design limitation in the way the checks are implemented.
Checking for void parameters that can be elided is done prior to generic instantiation, which means that instantiations that produce void parameters are ignored, as is the case in fails.
This choice was made to avoid having to check every generic signature twice, and there were some issues with this breaking existing overload selections.
I would be interested in understanding real-word examples of this behaviour. Currently I'm not sure whether this behaviour can be implemented using existing methods (overloads), or whether we really need to extend the void checks.
It was a poor mans method overloading, it just seemed very strange to me that it would not work even though the compiled method signatures were exactly the same. Now that I know when the void eliding is done I was able to solve it using the following:
Thanks for your help in understanding the situation! Feel free to close this if you see no reason to fix this as (at least in this case) it can be avoided.
Thanks for your help in understanding the situation! Feel free to close this if you see no reason to fix this as (at least in this case) it can be avoided.
It's not for me to open/close issues, that's for the team to decide. I would suggest leaving this open as the canonical thread that tracks use-cases that need the generic example to work too. If enough people find it useful then I'd be happy to add it.
I have a case where I'm implicitly overloading a function, but can't do explicit overloading due to maintaining closure.
I'm trying to generically create Redux actions. The load action can have a parameter of a specifiable type, but should possibly have no parameters.
neopostmodern I think the void parameters is not required really be longs in the not deprecated but don't use category of TS features (but keep in mind I an just a random guy on the internet with an opinion 😋) . I think today, we can do much better with tuples in rest parameters (which did not exist when the void trick was implemented:
I intentionally made this accept one parameter to keep the same API as your original, but with this approach, you could accept a variable number of parameter.
I appreciate the flexibility this offers, but IMHO using void in this scenario should still work (and even seems preferable to me), for several reasons:
I feel like it's more explicit (void is more concise in my view than [] to indicate absence)
The above (when trying to re-insert parameter names) breaks when the type passed in, for example for ParameterType, is itself MyFancyType | void to indicate omittability on a different level.
(This might work with your approach of passing in arrays of types, but I feel it's less readable. The logic does not accept "any number of parameters" but two very specific, yet omittable, ones.)
Last but not least it confuses the type hints in WebStorm (and possibly other tooling):
I guess it thinks the parameters are a mixed-array (this will probably be fixed at some point though, I'd guess)
Edit Plus, void as a parameter type has some tradition in C
TL;DR Yes, alternatives exist – but void parameters should work too.
This feature would really help the following use-case:
Our react codebase has some route objects, of type Route<UrlData, ComponentData>, which describes the data needed do navigate to that route. It is very nice to be able to type-safely navigate around the web-app like this:
Some pages are very simple, and they don't need any data. In this case, the type of the second argument of navigate will expand to { urlProps?: undefined, componentProps?: undefined }. This means you navigate to them via navigate(routes.welcomePage, {}).
What we wish we could do was transform that type into void so that you can't pass any data into the page, like this:
typeMakeForbiddenArgument<T>=T[Exclude<keyofT,undefined>]extendsundefined
? void
: T;navigate(routes.createItem);// This should work!
Came across this recently myself while trying to think up a workaround for #58977.
Here's a few more examples of weirdness, and an interesting 'escape hatch' from the problem via subclassing.
// This is equivalent to 'void', but tsc treats it weirdlytypeAlwaysVoid<P>=Pextendsstring ? void : void;functionsimple_void(_x: void): void{}functionweird_void<P>(_x: AlwaysVoid<P>): void{}classVoidCtor<P>{constructor(x: AlwaysVoid<P>){use(x)}}classVoidCtorSubclassextendsVoidCtor<unknown>{}functiontest_void_weirdness(){constvoid_value=(()=>{})();// get a value of type 'void'// This works just finesimple_void();// @ts-expect-error But this doesn't workweird_void();weird_void(void_value);// but we can demonstrate that `void` is indeed accepted here// @ts-expect-error This doesn't worknewVoidCtor<unknown>();newVoidCtor<unknown>(void_value);// ... even though 'void' is the argument type// ...but for some reason this works, even though it's the very same functionnewVoidCtorSubclass();}
And you might say: "Just use overloads" - and while that does work from a typechecking perspective, for some reason VSCode just does not play nice with overloads and will not give you intelligent auto-complete, if typing, for example:
await client.GET("/store/order/{orderId}", // pressing ctrl+space here results in broken intellisense; VSCode seems unable to deduce which overload it should use
Maybe long term overloads are the correct solution, if VSCode could fix intellisense for overloaded TS functions.
I also faced similar issues when trying to make function arguments optional bases on context via generics.
When playing around I found out the current ts behaviour isn't really consistent:
constf1=(arg: void)=>{};constr1=f1();// no errorconstf2=<T=void>(arg: T)=>{};constr2=f2();// Expected 1 arguments, but got 0, An argument for 'arg' was not provided.typeF<T=void>=(arg: T)=>void;constf3: F=arg=>{};constr3=f3();// no errorconstf4=<T=undefined>(arg: Textendsundefined ? void : T)=>{};constr4=f4<undefined>();// Expected 1 arguments, but got 0, An argument for 'arg' was not provided.classKlass<T=void>{constructor(arg: T){}f(arg: T){return;}}constk=newKlass();// Expected 1 arguments, but got 0, An argument for 'arg' was not provided.constr5=k.f();// no error
The reasoning in #29131 (comment) kinda explains the above (inconsistent) behaviour. But from a ts user perspective I think it would still make sense to have the ability to conditionally make function arguments optional.
Activity
jack-williams commentedon Dec 23, 2018
This is a design limitation in the way the checks are implemented.
Checking for void parameters that can be elided is done prior to generic instantiation, which means that instantiations that produce void parameters are ignored, as is the case in
fails.This choice was made to avoid having to check every generic signature twice, and there were some issues with this breaking existing overload selections.
I would be interested in understanding real-word examples of this behaviour. Currently I'm not sure whether this behaviour can be implemented using existing methods (overloads), or whether we really need to extend the void checks.
dested commentedon Dec 23, 2018
It was a poor mans method overloading, it just seemed very strange to me that it would not work even though the compiled method signatures were exactly the same. Now that I know when the void eliding is done I was able to solve it using the following:
Thanks for your help in understanding the situation! Feel free to close this if you see no reason to fix this as (at least in this case) it can be avoided.
jack-williams commentedon Dec 23, 2018
It's not for me to open/close issues, that's for the team to decide. I would suggest leaving this open as the canonical thread that tracks use-cases that need the generic example to work too. If enough people find it useful then I'd be happy to add it.
lifaon74 commentedon May 29, 2019
Jack Williams (@jack-williams)
As you asked for an example:
It's simply an iterator that send everytime the same value provided in its constructor.
What's the strangest: the 'foo' is perfectly valid but the 'constructor' errors.
jack-williams commentedon May 29, 2019
Thanks for the example!
The constructor is generic at the call-site;
foois concrete at the call-site becauseTis known to bevoid.As a workaround you can do:
dragomirtitian commentedon Dec 14, 2019
Parzh (org) (@parzh) You can get the parameter names back (mostly) if you don't mind writing a bit more:
Playground Link
neopostmodern commentedon Feb 14, 2020
I have a case where I'm implicitly overloading a function, but can't do explicit overloading due to maintaining closure.
I'm trying to generically create Redux actions. The
loadaction can have a parameter of a specifiable type, but should possibly have no parameters.But then it fails at
with
(Notice that the differing return types are completely fine – it's about the
parameters: voidpart)Playground
dragomirtitian commentedon Feb 14, 2020
neopostmodern I think the
voidparameters is not required really be longs in the not deprecated but don't use category of TS features (but keep in mind I an just a random guy on the internet with an opinion 😋) . I think today, we can do much better with tuples in rest parameters (which did not exist when thevoidtrick was implemented:Playground Link
I intentionally made this accept one parameter to keep the same API as your original, but with this approach, you could accept a variable number of parameter.
neopostmodern commentedon Feb 18, 2020
I appreciate the flexibility this offers, but IMHO using
voidin this scenario should still work (and even seems preferable to me), for several reasons:voidis more concise in my view than[]to indicate absence)ParameterType, is itselfMyFancyType | voidto indicate omittability on a different level.(This might work with your approach of passing in arrays of types, but I feel it's less readable. The logic does not accept "any number of parameters" but two very specific, yet omittable, ones.)
I guess it thinks the parameters are a mixed-array (this will probably be fixed at some point though, I'd guess)
voidas a parameter type has some tradition in CTL;DR Yes, alternatives exist – but
voidparameters should work too.ATheCoder commentedon Nov 16, 2020
JSDocs don't work either using tuple types.
PietroCarrara commentedon Sep 21, 2023
This feature would really help the following use-case:
Our react codebase has some route objects, of type
Route<UrlData, ComponentData>, which describes the data needed do navigate to that route. It is very nice to be able to type-safely navigate around the web-app like this:Some pages are very simple, and they don't need any data. In this case, the type of the second argument of navigate will expand to
{ urlProps?: undefined, componentProps?: undefined }. This means you navigate to them vianavigate(routes.welcomePage, {}).What we wish we could do was transform that type into void so that you can't pass any data into the page, like this:
So, yeah, another interesting use-case for this.
allisonkarlitskaya commentedon Jul 17, 2024
Came across this recently myself while trying to think up a workaround for #58977.
Here's a few more examples of weirdness, and an interesting 'escape hatch' from the problem via subclassing.
RPGillespie6 commentedon Aug 18, 2024
Another real use case is something like openapi-fetch
Where you want to be able to make the request body optional in some cases:
This could be achieved with a generics lookup with something like:
...except this issue prevents it.
And you might say: "Just use overloads" - and while that does work from a typechecking perspective, for some reason VSCode just does not play nice with overloads and will not give you intelligent auto-complete, if typing, for example:
Maybe long term overloads are the correct solution, if VSCode could fix intellisense for overloaded TS functions.
JelleRoets-Work commentedon Nov 16, 2024
I also faced similar issues when trying to make function arguments optional bases on context via generics.
When playing around I found out the current ts behaviour isn't really consistent:
Playground Link
The reasoning in #29131 (comment) kinda explains the above (inconsistent) behaviour. But from a ts user perspective I think it would still make sense to have the ability to conditionally make function arguments optional.