Skip to content

Distinguish missing and undefined also in arguments #44548

Open

Description

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.

Activity

  1. RyanCavanaugh commented on Jun 11, 2021

    @RyanCavanaugh
    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.

  2. KageShiron commented on Jun 24, 2021

    @KageShiron

    Ryan Cavanaugh (@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);
    }
  3. RyanCavanaugh commented on Jun 24, 2021

    @RyanCavanaugh
    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.

  4. KageShiron commented on Jun 24, 2021

    @KageShiron

    I got it, thanks!

  5. zyf0330 commented on Oct 13, 2021

    @zyf0330

    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?

  6. VanCoding commented on Dec 20, 2021

    @VanCoding

    I created the issue #47202 and share the view of Raphael Schweikert (@sabberworm) KageShiron and zhaoyunfeng (@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?

  7. MartinJohns commented on Dec 20, 2021

    @MartinJohns
    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.

  8. VanCoding commented on Dec 20, 2021

    @VanCoding

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

  9. FrameMuse commented on Sep 17, 2024

    @FrameMuse

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions