Skip to content

Introduce do expressions #2195

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 4 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum ExperimentalFeature: String, CaseIterable {
case referenceBindings
case thenStatements
case typedThrows
case doExpressions

/// The name of the feature, which is used in the doc comment.
public var featureName: String {
Expand All @@ -26,6 +27,8 @@ public enum ExperimentalFeature: String, CaseIterable {
return "'then' statements"
case .typedThrows:
return "typed throws"
case .doExpressions:
return "'do' expressions"
}
}

Expand Down
48 changes: 48 additions & 0 deletions CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,54 @@ public let EXPR_NODES: [Node] = [
]
),

Node(
kind: .doExpr,
base: .expr,
experimentalFeature: .doExpressions,
nameForDiagnostics: "'do' block",
documentation: """
A `do` block with one of more optional `catch` clauses.

This represents do blocks in both expression and statement postitions
(where the latter are wrapped in ExpressionStmtSyntax).

### Examples

```swift
do {
let x = 0
print(x)
}
```

```swift
let x = do {
try someThrowingFn()
} catch {
defaultValue
}
```
""",
traits: [
"WithCodeBlock"
],
children: [
Child(
name: "doKeyword",
kind: .token(choices: [.keyword(.do)])
),
Child(
name: "body",
kind: .node(kind: .codeBlock),
nameForDiagnostics: "body"
),
Child(
name: "catchClauses",
kind: .collection(kind: .catchClauseList, collectionElementName: "CatchClause", defaultsToEmpty: true)
),
]
),

Node(
kind: .editorPlaceholderExpr,
base: .expr,
Expand Down
4 changes: 2 additions & 2 deletions CodeGeneration/Sources/SyntaxSupport/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public class Node {
return []
}
var childIn: [(node: SyntaxNodeKind, child: Child?)] = []
for node in SYNTAX_NODES {
for node in SYNTAX_NODES where !node.isExperimental {
if let layout = node.layoutNode {
for child in layout.children {
if child.kinds.contains(self.kind) {
Expand Down Expand Up @@ -248,7 +248,7 @@ public class Node {

let list =
SYNTAX_NODES
.filter { $0.base == self.kind }
.filter { $0.base == self.kind && !$0.isExperimental }
.map { "- ``\($0.kind.syntaxType)``" }
.joined(separator: "\n")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public enum SyntaxNodeKind: String, CaseIterable {
case discardStmt
case documentationAttributeArgument
case documentationAttributeArgumentList
case doExpr
case doStmt
case dynamicReplacementAttributeArguments
case editorPlaceholderDecl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import SyntaxSupport
import Utils

let childNameForDiagnosticFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
DeclSyntax("import SwiftSyntax")
DeclSyntax("@_spi(ExperimentalLanguageFeatures) import SwiftSyntax")

try! FunctionDeclSyntax(
"private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String?"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import SyntaxSupport
import Utils

let buildableNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
DeclSyntax("import SwiftSyntax")
DeclSyntax("@_spi(ExperimentalLanguageFeatures) import SwiftSyntax")

for node in SYNTAX_NODES.compactMap(\.layoutNode) {
let type = node.type
Expand Down
58 changes: 42 additions & 16 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extension TokenConsumer {
backtrack.eat(handle)

// These can be parsed as expressions with try/await.
if backtrack.at(anyIn: IfOrSwitch.self) != nil {
if backtrack.at(anyIn: SingleValueStatementExpression.self) != nil {
return true
}
// Note we currently pass `preferExpr: false` to prefer diagnosing `try then`
Expand Down Expand Up @@ -525,21 +525,20 @@ extension Parser {
flavor: ExprFlavor,
pattern: PatternContext = .none
) -> RawExprSyntax {
// First check to see if we have the start of a regex literal `/.../`.
// tryLexRegexLiteral(/*forUnappliedOperator*/ false)

// Try parse an 'if' or 'switch' as an expression. Note we do this here in
// parseUnaryExpression as we don't allow postfix syntax to hang off such
// expressions to avoid ambiguities such as postfix '.member', which can
// currently be parsed as a static dot member for a result builder.
if self.at(.keyword(.switch)) {
return RawExprSyntax(
parseSwitchExpression(switchHandle: .constant(.keyword(.switch)))
)
} else if self.at(.keyword(.if)) {
return RawExprSyntax(
parseIfExpression(ifHandle: .constant(.keyword(.if)))
)
// Try parse a single value statement as an expression (e.g do/if/switch).
// Note we do this here in parseUnaryExpression as we don't allow postfix
// syntax to hang off such expressions to avoid ambiguities such as postfix
// '.member', which can currently be parsed as a static dot member for a
// result builder.
switch self.at(anyIn: SingleValueStatementExpression.self) {
case (.do, let handle)?:
return RawExprSyntax(self.parseDoExpression(doHandle: .noRecovery(handle)))
case (.if, let handle)?:
return RawExprSyntax(self.parseIfExpression(ifHandle: .noRecovery(handle)))
case (.switch, let handle)?:
return RawExprSyntax(self.parseSwitchExpression(switchHandle: .noRecovery(handle)))
default:
break
}

switch self.at(anyIn: ExpressionPrefixOperator.self) {
Expand Down Expand Up @@ -2045,6 +2044,33 @@ extension Parser.Lookahead {
}
}

// MARK: Do-Catch Expressions

extension Parser {
/// Parse a do expression.
mutating func parseDoExpression(doHandle: RecoveryConsumptionHandle) -> RawDoExprSyntax {
precondition(experimentalFeatures.contains(.doExpressions))
let (unexpectedBeforeDoKeyword, doKeyword) = self.eat(doHandle)
let body = self.parseCodeBlock(introducer: doKeyword)

// If the next token is 'catch', this is a 'do'/'catch'.
var elements = [RawCatchClauseSyntax]()
var loopProgress = LoopProgressCondition()
while self.at(.keyword(.catch)) && self.hasProgressed(&loopProgress) {
// Parse 'catch' clauses
elements.append(self.parseCatchClause())
}

return RawDoExprSyntax(
unexpectedBeforeDoKeyword,
doKeyword: doKeyword,
body: body,
catchClauses: RawCatchClauseListSyntax(elements: elements, arena: self.arena),
arena: self.arena
)
}
}

// MARK: Conditional Expressions

extension Parser {
Expand Down
19 changes: 15 additions & 4 deletions Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ extension Parser {
case (.repeat, let handle)?:
return label(self.parseRepeatStatement(repeatHandle: handle), with: optLabel)

case (.do, let handle)?:
// If we have 'do' expressions enabled, we parse a DoExprSyntax, and wrap
// it in an ExpressionStmtSyntax.
if self.experimentalFeatures.contains(.doExpressions) {
let doExpr = self.parseDoExpression(doHandle: handle)
let doStmt = RawExpressionStmtSyntax(
expression: RawExprSyntax(doExpr),
arena: self.arena
)
return label(doStmt, with: optLabel)
}
// Otherwise parse a regular DoStmtSyntax.
return label(self.parseDoStatement(doHandle: handle), with: optLabel)
case (.if, let handle)?:
let ifExpr = self.parseIfExpression(ifHandle: handle)
let ifStmt = RawExpressionStmtSyntax(
Expand Down Expand Up @@ -107,8 +120,6 @@ extension Parser {
return label(self.parseThrowStatement(throwHandle: handle), with: optLabel)
case (.defer, let handle)?:
return label(self.parseDeferStatement(deferHandle: handle), with: optLabel)
case (.do, let handle)?:
return label(self.parseDoStatement(doHandle: handle), with: optLabel)
case (.yield, let handle)?:
return label(self.parseYieldStatement(yieldHandle: handle), with: optLabel)
case (.then, let handle)? where experimentalFeatures.contains(.thenStatements):
Expand Down Expand Up @@ -634,8 +645,8 @@ extension Parser {
if self.at(anyIn: NotReturnExprStart.self) != nil {
return false
}
// Allowed for if/switch expressions.
if self.at(anyIn: IfOrSwitch.self) != nil {
// Allowed for single value statement expressions, e.g do/if/switch.
if self.at(anyIn: SingleValueStatementExpression.self) != nil {
return true
}
if self.atStartOfStatement(preferExpr: true) || self.atStartOfDeclaration() {
Expand Down
15 changes: 9 additions & 6 deletions Sources/SwiftParser/TokenSpecSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -754,12 +754,14 @@ enum ExpressionModifierKeyword: TokenSpecSet {
}
}

enum IfOrSwitch: TokenSpecSet {
enum SingleValueStatementExpression: TokenSpecSet {
case `do`
case `if`
case `switch`

init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
switch PrepareForKeywordMatch(lexeme) {
case TokenSpec(.do) where experimentalFeatures.contains(.doExpressions): self = .do
case TokenSpec(.if): self = .if
case TokenSpec(.switch): self = .switch
default: return nil
Expand All @@ -768,6 +770,7 @@ enum IfOrSwitch: TokenSpecSet {

var spec: TokenSpec {
switch self {
case .do: return .keyword(.do)
case .if: return .keyword(.if)
case .switch: return .keyword(.switch)
}
Expand Down Expand Up @@ -947,7 +950,7 @@ enum ExpressionStart: TokenSpecSet {
case awaitTryMove(ExpressionModifierKeyword)
case expressionPrefixOperator(ExpressionPrefixOperator)
case primaryExpressionStart(PrimaryExpressionStart)
case ifOrSwitch(IfOrSwitch)
case singleValueStatement(SingleValueStatementExpression)

init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
if let subset = ExpressionModifierKeyword(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
Expand All @@ -956,8 +959,8 @@ enum ExpressionStart: TokenSpecSet {
self = .expressionPrefixOperator(subset)
} else if let subset = PrimaryExpressionStart(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
self = .primaryExpressionStart(subset)
} else if let subset = IfOrSwitch(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
self = .ifOrSwitch(subset)
} else if let subset = SingleValueStatementExpression(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
self = .singleValueStatement(subset)
} else {
return nil
}
Expand All @@ -967,15 +970,15 @@ enum ExpressionStart: TokenSpecSet {
return ExpressionModifierKeyword.allCases.map(Self.awaitTryMove)
+ ExpressionPrefixOperator.allCases.map(Self.expressionPrefixOperator)
+ PrimaryExpressionStart.allCases.map(Self.primaryExpressionStart)
+ IfOrSwitch.allCases.map(Self.ifOrSwitch)
+ SingleValueStatementExpression.allCases.map(Self.singleValueStatement)
}

var spec: TokenSpec {
switch self {
case .awaitTryMove(let underlyingKind): return underlyingKind.spec
case .expressionPrefixOperator(let underlyingKind): return underlyingKind.spec
case .primaryExpressionStart(let underlyingKind): return underlyingKind.spec
case .ifOrSwitch(let underlyingKind): return underlyingKind.spec
case .singleValueStatement(let underlyingKind): return underlyingKind.spec
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftParser/TopLevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ extension Parser {
if at(.keyword(.as)),
let expr = stmt.as(RawExpressionStmtSyntax.self)?.expression
{
if expr.is(RawIfExprSyntax.self) || expr.is(RawSwitchExprSyntax.self) {
if expr.is(RawDoExprSyntax.self) || expr.is(RawIfExprSyntax.self) || expr.is(RawSwitchExprSyntax.self) {
let (op, rhs) = parseUnresolvedAsExpr(
handle: .init(spec: .keyword(.as))
)
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftParser/generated/ExperimentalFeatures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ extension Parser.ExperimentalFeatures {

/// Whether to enable the parsing of typed throws.
public static let typedThrows = Self(rawValue: 1 << 2)

/// Whether to enable the parsing of 'do' expressions.
public static let doExpressions = Self(rawValue: 1 << 3)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax

private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String? {
switch keyPath {
Expand Down Expand Up @@ -92,6 +92,8 @@ private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String? {
return "value type"
case \DifferentiabilityWithRespectToArgumentSyntax.arguments:
return "arguments"
case \DoExprSyntax.body:
return "body"
case \DoStmtSyntax.body:
return "body"
case \DocumentationAttributeArgumentSyntax.label:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ extension SyntaxKind {
return "'@differentiable' arguments"
case .discardStmt:
return "'discard' statement"
case .doExpr:
return "'do' block"
case .doStmt:
return "'do' statement"
case .documentationAttributeArgumentList:
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,20 @@ public func childName(_ keyPath: AnyKeyPath) -> String? {
return "expression"
case \DiscardStmtSyntax.unexpectedAfterExpression:
return "unexpectedAfterExpression"
case \DoExprSyntax.unexpectedBeforeDoKeyword:
return "unexpectedBeforeDoKeyword"
case \DoExprSyntax.doKeyword:
return "doKeyword"
case \DoExprSyntax.unexpectedBetweenDoKeywordAndBody:
return "unexpectedBetweenDoKeywordAndBody"
case \DoExprSyntax.body:
return "body"
case \DoExprSyntax.unexpectedBetweenBodyAndCatchClauses:
return "unexpectedBetweenBodyAndCatchClauses"
case \DoExprSyntax.catchClauses:
return "catchClauses"
case \DoExprSyntax.unexpectedAfterCatchClauses:
return "unexpectedAfterCatchClauses"
case \DoStmtSyntax.unexpectedBeforeDoKeyword:
return "unexpectedBeforeDoKeyword"
case \DoStmtSyntax.doKeyword:
Expand Down
14 changes: 14 additions & 0 deletions Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,20 @@ open class SyntaxAnyVisitor: SyntaxVisitor {
visitAnyPost(node._syntaxNode)
}

#if compiler(>=5.8)
@_spi(ExperimentalLanguageFeatures)
#endif
override open func visit(_ node: DoExprSyntax) -> SyntaxVisitorContinueKind {
return visitAny(node._syntaxNode)
}

#if compiler(>=5.8)
@_spi(ExperimentalLanguageFeatures)
#endif
override open func visitPost(_ node: DoExprSyntax) {
visitAnyPost(node._syntaxNode)
}

override open func visit(_ node: DoStmtSyntax) -> SyntaxVisitorContinueKind {
return visitAny(node._syntaxNode)
}
Expand Down
Loading