Skip to content

Distinguish missing and undefined also in arguments #44548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
4 of 5 tasks
sabberworm opened this issue Jun 11, 2021 · 9 comments
Open
4 of 5 tasks

Distinguish missing and undefined also in arguments #44548

sabberworm opened this issue Jun 11, 2021 · 9 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@sabberworm
Copy link

sabberworm commented Jun 11, 2021

Suggestion

With the arrival of --strictOptionalProperties, it’s clear that the intent of ? as a modifier isn’t to add | undefined to the type but to distinguish presence from absence.

I propose the same should apply to arguments.

🔍 Search Terms

optional argument, optional arguments, optional parameter, optional parameters

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code (unless behind a flag)
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Given

function optionallyTakesAnArg(arg? : string) {
	return arguments.length;
}

calling optionallyTakesAnArg() is not the same as optionallyTakesAnArg(undefined). TypeScript should be able to model this. Specifically it should forbid the second form and only allow either optionallyTakesAnArg() or optionallyTakesAnArg(stringArg).

📃 Motivating Example

This is mostly an issue when the argument is a generic type that may or may not include an explicit undefined:

function optionallyTakesAnArg<T>(arg? : T) {
	if(arguments.length > 0) {
		// arg passed, do stuff
		doStuffWith(arg!);
	} else {
		// no arg passed, do something else
		doOtherStuff();
	}
}

💻 Use Cases

I want to model that calling optionallyTakesAnArg<string>() and optionallyTakesAnArg<string>(stringArg) both are valid but that optionallyTakesAnArg<string>(undefined) is not, whereas optionallyTakesAnArg<undefined>() and optionallyTakesAnArg<undefined>(undefined) are (but may not do the same thing).

Further, overloads should be able to distinguish between the latter two cases, e.g. for inference of return types.

This allows arguments whose type is a generic that may include undefined to be modeled correctly.

⚡️ Open issues

  • Arrow functions don’t have an arguments object in scope so the only way to implement this would be with rest params and tuples, which means they, too, would need new semantics for ?.
  • This proposal is at odds with default arguments which always treat absent and undefined the same ((function(a = 1) { return a })() and (function(a = 1) { return a })(undefined) both evaluate to 1).
  • It’s also possible to model optional args using optionallyTakesAnArg(arg : string | void), which, AFAICT currently has the same semantics as optionallyTakesAnArg(arg? : string). I’m not sure whether this should continue to be aligned or not.
@RyanCavanaugh
Copy link
Member

This was discussed quite a bit when we were making strictOptionalProperties and we weren't really enthusiastic about it. In addition to the problems you noted, non-trailing undefineds are not even possibly-detectable, so there isn't a runtime behavior to map this on to except in terminal argument positions.

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jun 11, 2021
@KageShiron
Copy link

@RyanCavanaugh
Where can I find that discussion?
I would like to make it an error to be able to specify undefined for an argument with default parameter, similar to this suggestion. This looks to be an unnatural behavior.

Or should I suggest this to typescript-eslint?

func("aaa"); // =>"aaa"
func(); // =>"bbb"
func(null); // => null
func(undefined); //=>"bbb" 🤔 unnatural behavior……

function func( a = "bbb" )
{
    console.log(a);
}

@RyanCavanaugh
Copy link
Member

It was in a meeting.

Passing undefined to a parameter with a default seems very unproblematic and I don't see us banning it under any circumstance. If the function didn't want someone passing an explicit undefined, they shouldn't have put a default in the parameter position.

@KageShiron
Copy link

I got it, thanks!

@zyf0330
Copy link

zyf0330 commented Oct 13, 2021

It was in a meeting.

Passing undefined to a parameter with a default seems very unproblematic and I don't see us banning it under any circumstance. If the function didn't want someone passing an explicit undefined, they shouldn't have put a default in the parameter position.

Hello, I have a function, see playground

And pass undefined make type inferring wrong. So how should I do to get it correct?

@VanCoding
Copy link

I created the issue #47202 and share the view of @sabberworm @KageShiron and @zyf0330.

What I don't get in this suggestion, though, is what's the point of an optional parameter without a default vale? If it's not specified, what's the value of it then if not undefined?

@MartinJohns
Copy link
Contributor

What I don't get in this suggestion, though, is what's the point of an optional parameter without a default vale?

The difference is at the invocation side, and it's exactly the same as your other issue. The difference is whether it should be allowed to invoke the method with an undefined value (e.g. an explicit undefined or a union including undefined), or if the parameter must be omitted.

@VanCoding
Copy link

@MartinJohns Thanks, I get it now. Then I also think that it's good the way it currently is. Sorry for interruping.. 😓

@FrameMuse
Copy link

FrameMuse commented Sep 17, 2024

Passing undefined to a parameter with a default seems very unproblematic and I don't see us banning it under any circumstance. If the function didn't want someone passing an explicit undefined, they shouldn't have put a default in the parameter position.

Seems reasonable, but shouldn't be more obvious? There are definitely different intentions for (arg?: Type) and (arg = Default). Maybe it could be annotated properly? For example (arg: Defaulted<Type>) => void.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants