TypeScript: Treating `undefined` parameters as optional
tsc: 2.2.0-dev.20161116
Discover this behavior from https://github.com/acdlite/flux-standard-action/pull/45/commits/78a9065914b2ca4848dfba8fc0b47c54e2d0e319
This is the original code:
export interface FSA<Payload, Meta> {
...
payload?: Payload;
...
}
However, this does not work well with type guard:
function isSomeAction(action: any): action is FSA<{ message: string }, void> {
return true;
}
let action = {...};
if (isSomeAction(action)) {
// `action.payload` may be undefined.
console.log(action.payload.message);
}
Since generic type can be anything, including undefined
, I’m considering to remove the optional designation:
export interface FSA<Payload, Meta> {
...
payload: Payload;
...
}
// now type guard works
let action = {...};
if (isSomeAction(action)) {
console.log(action.payload.message);
}
However, now the creation code fail:
function createSomeAction(): FSA<undefined, undefined> {
// error: `payload` and `meta` are required
return { type: 'SOME_ACTION' };
}
The workaround is to depends on infer type:
function createSomeAction() {
return { type: 'SOME_ACTION' };
}
or specify it:
function createSomeAction(): FSA<undefined, undefined> {
return {
type: 'SOME_ACTION',
payload: undefined,
meta: undefined
};
}
Is there a better solution? 🌷
About this issue
- Original URL
- State: open
- Created 8 years ago
- Reactions: 75
- Comments: 40 (13 by maintainers)
Approved. This will probably be a hairy change so we’ll take a first stab at it unless someone wants to jump in. We don’t think there will be breaking changes but we’ll need to investigate
For anyone still waiting for optional function arguments, it is now possible to simulate that using new tuple types and spread expressions:
it has a limitation with inferred argument type though, which is solved by explicitly specifying undefined argument type
There is nothing specific to generics here. for example:
With generic defaults (https://github.com/Microsoft/TypeScript/pull/13487) landed, more people will encounter this issue. Should this be fixed?
i.e.:
A simpler example:
For anyone wanting to make all properties that are
undefined
-able optional, here you go. The magic sauce is the last type,UndefinedOptional<T>
.Updated Gist: https://gist.github.com/travigd/18ae344a6bc69074b17da11333835c3d#file-undefined-optional-ts
Example
Naming is hard and I don’t think
OnlyKeys
is a great name (it acts sort of in reverse - only keys to whichU
are assignable are included… which feels backwards but it’s what’s needed to do this).One more solution for making undefined fields optional:
Playground
Hi, everyone!
Just found some workaround, but need to change type from
undefined
tovoid
maybe it could help someone (when it’s possible to change types):even works with generics, like:
But have some error with more complex example: link
maybe someone from TS team can help with this? 🙏
Adding a little more fuel to the fire. We have our own Promise library (Microsoft/SyncTasks on GitHub) and are running across bugs that devs are introducing now that we’ve switched our codebase to strict null-compliant.
In a perfect world, we would like:
But there isn’t currently any SFINAE-style way to select valid methods based on T types. If T is void, then you shouldn’t be passing anything to resolve, and the function will just get an undefined parameter passed.
Right now, the function signature is “resolve(param?: T)”, but that lets you do:
SyncTasks.Resolved<number>() – which then has a resolved synctask<number> with an undefined value, which will then explode anything downstream that doesn’t allow an undefined value.
We’re contemplating, for now, changing synctasks to require always taking a parameter, and just adding some void helpers to make the code less annoyingly verbose, but it’s ugly compared to what we could do with method signature selectors.
Any update on this issue as of TypeScript 4.1.2?
@phaux your example works if you just use void instead… playground
In my case I wanted to make object properties optional. I had an API spec from which I automatically generated query and URL parameters (using conditional types), and sometimes one or both would be
undefined
(not required).Just sharing how I was able to make
undefined
object parameters optional in case somebody else runs into the same issue:Example:
I stumbled upon this while writing some code similar to the following. I’m gonna copy-paste it here as an additional motivating example.
Output
Compiler Options
Playground Link: Provided
There are lots of workarounds here, Maybe all that’s really needed is some good documentation or utility types?
Mental note for myself and maybe others:
When this is available and working with inferred arguments we will be able to typecast return types depending on inferred generics 🥳
demo in TS playground
Anyway this can be used (abused?) to allow for making generics where the arg is optional if the object contains no required properties…
playground
Yea… Anyone know what version
void
got released with? I Want to read the release notes to see if that’s intentional…in the mean time…
playground
@lonewarrior556 Okaaaayyy… Thanks. But now I’m only even more confused about the differences between
void
andundefined
😅 I thought it only makes a difference as a return type.@iamolegga You are having the same issue as this #29131. If you have generics, void parameters are not treated as optional. This is due to the fact that arity is checked before generic type inference. (I tried to take a stab at a PR to fix this, but changing this is likely to have a large perf impact as far as I recall)
@agalazis no, it didn’t make it in for 3.0. In any case, Typescript doesn’t accurately model the distinction between missing and undefined right now, so there’s no guarantee fixing just this special case of it will work.
A complete solution would require the introduction of a new subtype of undefined, the missing type. And last time we discussed that at a design meeting, nobody thought it was worth the complexity that would introduce.
To be clear, I don’t actually want fully SFINAE-able method selectors – this is JS after all, you can’t do that. But I think the real answer is that if you have a function parameter whose type is void, then it should auto-change the signature for that parameter to optional, and error if you pass anything other than undefined to it, if you DO pass a value.
Hi there 😄
So I just wanted to add a simple example with a few iterations I have been reflecting on.
We are creating a signal. A signal is simply a function that returns an object with a
value
property. The typing reflects thevalue
of the signal, though you can pass in aninitialValue
as a param to the function.Example 1
Example 2
Example 3
So just wanted to share this to add to the discussion.
For me, as the typing reflects the actual value of the signal, I have chosen to go with Example 1 in this case. It arguably gives the most predictable result and developer experience.
It is still the same issue. What I’m suggesting is that the solution should not “loosen” the TypeScript semantics. There should be an alternative to describe precisely what it is.
There are workarounds for some cases such as the one suggested here: https://github.com/microsoft/TypeScript/issues/12400#issuecomment-758523767
But it would be great if TS can provide a way to describe it out of the box without causing more confusion.
Additional food of thoughts: what is
void
and what isnever
? Doesvoid
maps tounit
in Haskell andnever
maps toVoid
in Haskell?Is there a way to use them in generics to precisely describe what we want here?
For those who are interested in type theory, I would recommend checking out Category Theory for Programmers by Bartosz Milewski
@freakzlike , for parameters it works in a little bit another way:
See in Playground
Also I suggest to make
ActionContext
generic because it is the same for any possible Mutations:This would be really helpful to make non-breaking changes to APIs when introducing generics.
Edit: Actually, I realized (after re-reading some of the Playgrounds above) that what I wanted was to use
void
instead ofundefined
, which worked in my simple case.any chance for 3.0?