Skip to content
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

Guarded if statements #13426

Closed
ChiriVulpes opened this issue Jan 11, 2017 · 4 comments
Closed

Guarded if statements #13426

ChiriVulpes opened this issue Jan 11, 2017 · 4 comments
Labels
Duplicate An existing issue was already created

Comments

@ChiriVulpes
Copy link

ChiriVulpes commented Jan 11, 2017

I tend to write functions that have multiple overloads. The function shouldn't be called using the original, overloaded signature, and is restricted to the overloaded signatures. This already works great. The problem is that some overloads change multiple arguments, and these arguments will be a specific type if the first argument is of that overload signature.

It's okay if that first paragraph doesn't make sense. Here's a simple example.

function doXtoY(x: "subtract", y: number): number;
function doXtoY(x: "uppercase", y: string): string;
function doXtoY(x: "subtract" | "uppercase", y: number | string): number | string {
  if (x == "subtract") {
    // the overload signatures only allow y to be a number when x is "subtract", but the base signature doesn't know that
    return y - 1;
  } else {
    return y.toUpperCase();
  }
}

This won't work, as the base signature doesn't know anything about the types being narrowed when x changes. This is still fine, but as this doesn't work, this is where a guarded statement could come in handy.

My proposal is guarded if statements. They do not require functions to operate, and directly modify the types of variables in their scope. Here is the same example, but functional, using them.

function doXtoY(x: "subtract", y: number): number;
function doXtoY(x: "uppercase", y: string): string;
function doXtoY(x: "subtract" | "uppercase", y: number | string): number | string {
  if (x == "subtract"): y is number {
    // y is narrowed to number
    return y - 1;
  } else {
    // y is narrowed to string
    return y.toUpperCase();
  }
}

This specific example could be done using a guard function, but they can be a bit unwieldy to add (an extra function that you have to put somewhere to only use once in the code, sometimes), they can't modify multiple types simultaneously, and you have to give them both the argument you're guarding with and the argument you need to guard the type of.

Language Feature Checklist

  • Syntactic
    • Grammar:
      Adds an optional clause : <variable-name> is <type expression> between ) { in if blocks. Does not work for single-expression if blocks.
    • There are no backwards-compatibility issues, and this syntax should not interfere with future ES changes.
  • Semantic
    Adds custom type narrowing to if blocks. Else blocks following an if block are not narrowed. This functionality mirrors the functionality of type guard functions.
  • Emit
    Emit should not be impacted by this feature.
  • Compatibility
    This should not be incompatible with any past or future versions of TypeScript or ECMAScript.
  • Other
    • Can the feature be implemented without negatively affecting compiler performance? Yes.
    • This addition will increase the usability of type guards and allow users additional options for type narrowing when the language is unable to do so.
@RyanCavanaugh RyanCavanaugh added the External Relates to another program, environment, or user action which we cannot control. label Jan 12, 2017
@ChiriVulpes ChiriVulpes reopened this Jan 12, 2017
@RyanCavanaugh RyanCavanaugh removed the External Relates to another program, environment, or user action which we cannot control. label Jan 12, 2017
@ChiriVulpes ChiriVulpes changed the title Add guarded if statements Guarded if statements Jan 12, 2017
@HerringtonDarkholme
Copy link
Contributor

HerringtonDarkholme commented Jan 12, 2017

I wonder what's the difference between type assertion when guarded if statement is not used along with overloading. In the example, you can simply cast y to <number>y - 1. So did your proposal imply narrowing statement should only be used in overloading implementation?

Also, what if two nesting guarded if blocks conflict?

@ChiriVulpes
Copy link
Author

ChiriVulpes commented Jan 12, 2017

No, it can be used in all parts of the code, not necessarily overload implementations. That's just a use case.
The purpose of a type guard (in general) is to narrow the type so that you don't have to cast. Technically an if block's inner lines could use casting instead of having their types narrowed, while using a type guard function as well. Type guard functions solve a different problem, as they also function as the condition in an if statement.

If a nested guarded if block conflicts then it would merge the types (is x and is y would produce x & y). This mirrors what type guard functions already do.

@RyanCavanaugh
Copy link
Member

I think #10421 / #9946 / #6474 cover this area fairly well already?

@ChiriVulpes
Copy link
Author

Yes, they do, thanks! I couldn't find them when searching. Well, here's another issue that someone can stumble upon instead of making a new one, at least. =P

(And another syntax suggestion as well.)

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jan 12, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants