Skip to content

Introduce if/switch expressions #1228

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

Merged
merged 10 commits into from
Feb 3, 2023

Conversation

hamishknight
Copy link
Contributor

Introduce IfExprSyntax and SwitchExpr syntax, which replace IfStmtSyntax and SwitchStmtSyntax, and can represent both if/switch expressions, as well as regular if/switch statements when wrapped in an ExpressionStmtSyntax. Implement the parsing logic such that we start parsing IfExprSyntax and SwitchExprSyntax in expression position.

@hamishknight
Copy link
Contributor Author

How's this @ahoppen?

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@hamishknight hamishknight force-pushed the express-yourself branch 8 times, most recently from 4776f35 to ba9b120 Compare February 2, 2023 12:47
This was recently removed, but will represent an
if/switch expression when used as a statement.
This replaces IfStmtSyntax, and will be wrapped in
an ExpressionStmtSyntax when used as a statement.
Update the parser to support parsing IfExprSyntax.
For now, this only updates the existing statement
parsing, and does not attempt to parse in
expression position.
This replaces SwitchStmtSyntax, and will be
wrapped in an ExpressionStmtSyntax when used as a
statement.
Update the parser to support parsing SwitchExprSyntax.
For now, this only updates the existing statement
parsing, and does not attempt to parse in
expression position.
Start parsing if and switch expressions as unary
expressions (as we don't allow postfix grammar for
them). In addition, parse if/switch expressions
in statement position if we see a
`try`/`await`/`move`, or a trailing `as Type`.
Clean up some code, allow `if` and `switch` to
count as the start of an expression, and add some
substructure test assertions.
@hamishknight
Copy link
Contributor Author

@gsl-anthonymerle
Copy link

Hello @hamishknight,

I have a question regarding the current way of what differentiates a switch expression from a switch statement:
By reading at your PR's description, I understood that the swift-syntax's differentiation between the two is that a switch statement is enclosed in a ExpressionStmtSyntax while a switch expression isn't.
I wonder if my understanding is correct or not because a lot of examples given to showcase switch expression is on of the following:

var myComputedVar: Bool {
    switch someEnum {
        case 1: true
        case 2: false
    }
}
func myFunc() -> Bool {
    switch someEnum {
        case 1: true
        case 2: false
    }
}

But I found out that, looking at what swift-syntax creates, these aren't actually switch expression but statements because they are being enclosed in a ExpressionStmtSyntax.

If my understanding of the rule to differentiate between the two, is the above actually expected?

Note: Keeping the above examples, to make them considered as switch expressions, I have to write then as follow:

var myComputedVar: Bool {
    let result = switch someEnum {
        case 1: true
        case 2: false
    }
    return result
}
func myFunc() -> Bool {
    let result = switch someEnum {
        case 1: true
        case 2: false
    }
    return result
}

Thank you in advance for your enlightenment 🙏
Anthony.

@hamishknight
Copy link
Contributor Author

But I found out that, looking at what swift-syntax creates, these aren't actually switch expression but statements because they are being enclosed in a ExpressionStmtSyntax.

Indeed, this is because syntactically they appear in statement/expression position, and therefore are considered to be statements by swift-syntax (same as if they weren't the only element in the function body). The compiler is then responsible for determining whether they are semantically expressions, and handles converting into a single expression return. For e.g a switch in the RHS of a binding, it is unambiguously in expression position and is therefore not wrapped in ExpressionStmtSyntax.

@gsl-anthonymerle
Copy link

I see, after your explanation it makes more sense syntactically wise 🙏

I have a follow-up question if you don't mind then: I do want to catch the cases of where switch are syntactically statements but compiled as expressions (as in my previous examples). Do you think I could kind of infer that by looking in the syntactic context of the switch ? (for example, if the switch is enclosed in a computed property and there is nothing else than this switch, etc.) Or would that be a rabbit hole and I would risk to have lots of false-positives because the syntax itself isn't enough to handle all cases ?

@hamishknight
Copy link
Contributor Author

For reference the checks are here and here. In principle, I think they ought to be mostly derivable from the syntax tree (modulo the result builder check), and maybe we could find a way to expose the important parts through swift-syntax one day (though we'll need to wait until at least we have a swift-syntax version of ASTScopes). I would be wary of re-implementing them yourself though, since they're not entirely trivial and you'll need to make sure you keep up-to-date with what the compiler's doing.

I do wonder though if maybe the carve out you're looking for is just switches with single expression cases, even including switch statements, e.g consider if this should be allowed:

func foo(_ x: Int) {
  print("hello")
  switch x {
  case 5: print("5!")
  case 10: print("10!")
  default: print("some other number")
  }
}

(though note that we do allow nested if/switch expressions, so you'll want to decide how you want to handle those since they'll appear as statements)

Conversely, you may not want to accept every same-line case for a switch expression, since we do currently allow multi-line cases that end in a throw e.g:

struct Err: Error {}

func foo() throws -> Int {
  switch Bool.random() {
  case true:
    5
  case false: print("false!")
    throw Err()
  }
}

@gsl-anthonymerle
Copy link

I see, indeed I'm afraid as well to dive into a big hole trying to replicate what the compiler is doing but with the syntax only.
Thank you for all you enlightenment @hamishknight

Best regards,
Anthony

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants