Skip to content

Commit 7c40c39

Browse files
committed
Introduce 'do' expressions
1 parent b2cbece commit 7c40c39

File tree

8 files changed

+455
-23
lines changed

8 files changed

+455
-23
lines changed

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SwiftSyntax
1515
public enum ExperimentalFeature: String, CaseIterable {
1616
case referenceBindings
1717
case thenStatements
18+
case doExpressions
1819

1920
/// The name of the feature, which is used in the doc comment.
2021
public var featureName: String {
@@ -23,6 +24,8 @@ public enum ExperimentalFeature: String, CaseIterable {
2324
return "reference bindings"
2425
case .thenStatements:
2526
return "'then' statements"
27+
case .doExpressions:
28+
return "'do' expressions"
2629
}
2730
}
2831

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,32 @@ public let EXPR_NODES: [Node] = [
740740
]
741741
),
742742

743+
Node(
744+
kind: .doExpr,
745+
base: .expr,
746+
// FIXME: This ought to be marked experimental.
747+
experimentalFeature: nil,
748+
nameForDiagnostics: "'do' statement",
749+
traits: [
750+
"WithCodeBlock"
751+
],
752+
children: [
753+
Child(
754+
name: "doKeyword",
755+
kind: .token(choices: [.keyword(.do)])
756+
),
757+
Child(
758+
name: "body",
759+
kind: .node(kind: .codeBlock),
760+
nameForDiagnostics: "body"
761+
),
762+
Child(
763+
name: "catchClauses",
764+
kind: .collection(kind: .catchClauseList, collectionElementName: "CatchClause", defaultsToEmpty: true)
765+
),
766+
]
767+
),
768+
743769
Node(
744770
kind: .editorPlaceholderExpr,
745771
base: .expr,

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public enum SyntaxNodeKind: String, CaseIterable {
107107
case discardStmt
108108
case documentationAttributeArgument
109109
case documentationAttributeArgumentList
110+
case doExpr
110111
case doStmt
111112
case dynamicReplacementAttributeArguments
112113
case editorPlaceholderDecl

Sources/SwiftParser/Expressions.swift

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ extension TokenConsumer {
2020
backtrack.eat(handle)
2121

2222
// These can be parsed as expressions with try/await.
23-
if backtrack.at(anyIn: IfOrSwitch.self) != nil {
23+
if backtrack.at(anyIn: SingleValueStatementExpression.self) != nil {
2424
return true
2525
}
2626
// Note we currently pass `preferExpr: false` to prefer diagnosing `try then`
@@ -525,21 +525,26 @@ extension Parser {
525525
flavor: ExprFlavor,
526526
pattern: PatternContext = .none
527527
) -> RawExprSyntax {
528-
// First check to see if we have the start of a regex literal `/.../`.
529-
// tryLexRegexLiteral(/*forUnappliedOperator*/ false)
530-
531-
// Try parse an 'if' or 'switch' as an expression. Note we do this here in
532-
// parseUnaryExpression as we don't allow postfix syntax to hang off such
533-
// expressions to avoid ambiguities such as postfix '.member', which can
534-
// currently be parsed as a static dot member for a result builder.
535-
if self.at(.keyword(.switch)) {
528+
// Try parse a single value statement as an expression (e.g do/if/switch).
529+
// Note we do this here in parseUnaryExpression as we don't allow postfix
530+
// syntax to hang off such expressions to avoid ambiguities such as postfix
531+
// '.member', which can currently be parsed as a static dot member for a
532+
// result builder.
533+
switch self.at(anyIn: SingleValueStatementExpression.self) {
534+
case (.do, _)?:
536535
return RawExprSyntax(
537-
parseSwitchExpression(switchHandle: .constant(.keyword(.switch)))
536+
self.parseDoExpression(doHandle: .constant(.keyword(.do)))
538537
)
539-
} else if self.at(.keyword(.if)) {
538+
case (.if, _)?:
540539
return RawExprSyntax(
541-
parseIfExpression(ifHandle: .constant(.keyword(.if)))
540+
self.parseIfExpression(ifHandle: .constant(.keyword(.if)))
542541
)
542+
case (.switch, _)?:
543+
return RawExprSyntax(
544+
self.parseSwitchExpression(switchHandle: .constant(.keyword(.switch)))
545+
)
546+
default:
547+
break
543548
}
544549

545550
switch self.at(anyIn: ExpressionPrefixOperator.self) {
@@ -2046,6 +2051,33 @@ extension Parser.Lookahead {
20462051
}
20472052
}
20482053

2054+
// MARK: Do-Catch Expressions
2055+
2056+
extension Parser {
2057+
/// Parse a do expression.
2058+
mutating func parseDoExpression(doHandle: RecoveryConsumptionHandle) -> RawDoExprSyntax {
2059+
assert(experimentalFeatures.contains(.doExpressions))
2060+
let (unexpectedBeforeDoKeyword, doKeyword) = self.eat(doHandle)
2061+
let body = self.parseCodeBlock(introducer: doKeyword)
2062+
2063+
// If the next token is 'catch', this is a 'do'/'catch' statement.
2064+
var elements = [RawCatchClauseSyntax]()
2065+
var loopProgress = LoopProgressCondition()
2066+
while self.at(.keyword(.catch)) && self.hasProgressed(&loopProgress) {
2067+
// Parse 'catch' clauses
2068+
elements.append(self.parseCatchClause())
2069+
}
2070+
2071+
return RawDoExprSyntax(
2072+
unexpectedBeforeDoKeyword,
2073+
doKeyword: doKeyword,
2074+
body: body,
2075+
catchClauses: RawCatchClauseListSyntax(elements: elements, arena: self.arena),
2076+
arena: self.arena
2077+
)
2078+
}
2079+
}
2080+
20492081
// MARK: Conditional Expressions
20502082

20512083
extension Parser {

Sources/SwiftParser/Statements.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ extension Parser {
7777
case (.repeat, let handle)?:
7878
return label(self.parseRepeatStatement(repeatHandle: handle), with: optLabel)
7979

80+
case (.do, let handle)?:
81+
// If we have 'do' expressions enabled, we parse a DoExprSyntax, and wrap
82+
// it in an ExpressionStmtSyntax.
83+
if self.experimentalFeatures.contains(.doExpressions) {
84+
let doExpr = self.parseDoExpression(doHandle: handle)
85+
let doStmt = RawExpressionStmtSyntax(
86+
expression: RawExprSyntax(doExpr),
87+
arena: self.arena
88+
)
89+
return label(doStmt, with: optLabel)
90+
}
91+
// Otherwise parse a regular DoStmtSyntax.
92+
return label(self.parseDoStatement(doHandle: handle), with: optLabel)
8093
case (.if, let handle)?:
8194
let ifExpr = self.parseIfExpression(ifHandle: handle)
8295
let ifStmt = RawExpressionStmtSyntax(
@@ -107,8 +120,6 @@ extension Parser {
107120
return label(self.parseThrowStatement(throwHandle: handle), with: optLabel)
108121
case (.defer, let handle)?:
109122
return label(self.parseDeferStatement(deferHandle: handle), with: optLabel)
110-
case (.do, let handle)?:
111-
return label(self.parseDoStatement(doHandle: handle), with: optLabel)
112123
case (.yield, let handle)?:
113124
return label(self.parseYieldStatement(yieldHandle: handle), with: optLabel)
114125
case (.then, let handle)? where experimentalFeatures.contains(.thenStatements):
@@ -634,8 +645,8 @@ extension Parser {
634645
if self.at(anyIn: NotReturnExprStart.self) != nil {
635646
return false
636647
}
637-
// Allowed for if/switch expressions.
638-
if self.at(anyIn: IfOrSwitch.self) != nil {
648+
// Allowed for single value statement expressions, e.g do/if/switch.
649+
if self.at(anyIn: SingleValueStatementExpression.self) != nil {
639650
return true
640651
}
641652
if self.atStartOfStatement(preferExpr: true) || self.atStartOfDeclaration() {

Sources/SwiftParser/TokenSpecSet.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -754,12 +754,14 @@ enum ExpressionModifierKeyword: TokenSpecSet {
754754
}
755755
}
756756

757-
enum IfOrSwitch: TokenSpecSet {
757+
enum SingleValueStatementExpression: TokenSpecSet {
758+
case `do`
758759
case `if`
759760
case `switch`
760761

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

769771
var spec: TokenSpec {
770772
switch self {
773+
case .do: return .keyword(.do)
771774
case .if: return .keyword(.if)
772775
case .switch: return .keyword(.switch)
773776
}
@@ -947,7 +950,7 @@ enum ExpressionStart: TokenSpecSet {
947950
case awaitTryMove(ExpressionModifierKeyword)
948951
case expressionPrefixOperator(ExpressionPrefixOperator)
949952
case primaryExpressionStart(PrimaryExpressionStart)
950-
case ifOrSwitch(IfOrSwitch)
953+
case singleValueStatement(SingleValueStatementExpression)
951954

952955
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
953956
if let subset = ExpressionModifierKeyword(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
@@ -956,8 +959,8 @@ enum ExpressionStart: TokenSpecSet {
956959
self = .expressionPrefixOperator(subset)
957960
} else if let subset = PrimaryExpressionStart(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
958961
self = .primaryExpressionStart(subset)
959-
} else if let subset = IfOrSwitch(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
960-
self = .ifOrSwitch(subset)
962+
} else if let subset = SingleValueStatementExpression(lexeme: lexeme, experimentalFeatures: experimentalFeatures) {
963+
self = .singleValueStatement(subset)
961964
} else {
962965
return nil
963966
}
@@ -967,15 +970,15 @@ enum ExpressionStart: TokenSpecSet {
967970
return ExpressionModifierKeyword.allCases.map(Self.awaitTryMove)
968971
+ ExpressionPrefixOperator.allCases.map(Self.expressionPrefixOperator)
969972
+ PrimaryExpressionStart.allCases.map(Self.primaryExpressionStart)
970-
+ IfOrSwitch.allCases.map(Self.ifOrSwitch)
973+
+ SingleValueStatementExpression.allCases.map(Self.singleValueStatement)
971974
}
972975

973976
var spec: TokenSpec {
974977
switch self {
975978
case .awaitTryMove(let underlyingKind): return underlyingKind.spec
976979
case .expressionPrefixOperator(let underlyingKind): return underlyingKind.spec
977980
case .primaryExpressionStart(let underlyingKind): return underlyingKind.spec
978-
case .ifOrSwitch(let underlyingKind): return underlyingKind.spec
981+
case .singleValueStatement(let underlyingKind): return underlyingKind.spec
979982
}
980983
}
981984
}

Sources/SwiftParser/TopLevel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ extension Parser {
186186
if at(.keyword(.as)),
187187
let expr = stmt.as(RawExpressionStmtSyntax.self)?.expression
188188
{
189-
if expr.is(RawIfExprSyntax.self) || expr.is(RawSwitchExprSyntax.self) {
189+
if expr.is(RawDoExprSyntax.self) || expr.is(RawIfExprSyntax.self) || expr.is(RawSwitchExprSyntax.self) {
190190
let (op, rhs) = parseUnresolvedAsExpr(
191191
handle: .init(spec: .keyword(.as))
192192
)

0 commit comments

Comments
 (0)