Skip to content

Commit 1f3618d

Browse files
authored
Merge pull request swiftlang#2195 from hamishknight/do-it
2 parents 81bc982 + 00c45d0 commit 1f3618d

27 files changed

+892
-36
lines changed

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public enum ExperimentalFeature: String, CaseIterable {
1616
case referenceBindings
1717
case thenStatements
1818
case typedThrows
19+
case doExpressions
1920

2021
/// The name of the feature, which is used in the doc comment.
2122
public var featureName: String {
@@ -26,6 +27,8 @@ public enum ExperimentalFeature: String, CaseIterable {
2627
return "'then' statements"
2728
case .typedThrows:
2829
return "typed throws"
30+
case .doExpressions:
31+
return "'do' expressions"
2932
}
3033
}
3134

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

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

743+
Node(
744+
kind: .doExpr,
745+
base: .expr,
746+
experimentalFeature: .doExpressions,
747+
nameForDiagnostics: "'do' block",
748+
documentation: """
749+
A `do` block with one of more optional `catch` clauses.
750+
751+
This represents do blocks in both expression and statement postitions
752+
(where the latter are wrapped in ExpressionStmtSyntax).
753+
754+
### Examples
755+
756+
```swift
757+
do {
758+
let x = 0
759+
print(x)
760+
}
761+
```
762+
763+
```swift
764+
let x = do {
765+
try someThrowingFn()
766+
} catch {
767+
defaultValue
768+
}
769+
```
770+
""",
771+
traits: [
772+
"WithCodeBlock"
773+
],
774+
children: [
775+
Child(
776+
name: "doKeyword",
777+
kind: .token(choices: [.keyword(.do)])
778+
),
779+
Child(
780+
name: "body",
781+
kind: .node(kind: .codeBlock),
782+
nameForDiagnostics: "body"
783+
),
784+
Child(
785+
name: "catchClauses",
786+
kind: .collection(kind: .catchClauseList, collectionElementName: "CatchClause", defaultsToEmpty: true)
787+
),
788+
]
789+
),
790+
743791
Node(
744792
kind: .editorPlaceholderExpr,
745793
base: .expr,

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public class Node {
199199
return []
200200
}
201201
var childIn: [(node: SyntaxNodeKind, child: Child?)] = []
202-
for node in SYNTAX_NODES {
202+
for node in SYNTAX_NODES where !node.isExperimental {
203203
if let layout = node.layoutNode {
204204
for child in layout.children {
205205
if child.kinds.contains(self.kind) {
@@ -248,7 +248,7 @@ public class Node {
248248

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

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

CodeGeneration/Sources/generate-swift-syntax/templates/swiftparserdiagnostics/ChildNameForDiagnosticsFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SyntaxSupport
1616
import Utils
1717

1818
let childNameForDiagnosticFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
19-
DeclSyntax("import SwiftSyntax")
19+
DeclSyntax("@_spi(ExperimentalLanguageFeatures) import SwiftSyntax")
2020

2121
try! FunctionDeclSyntax(
2222
"private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String?"

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntaxbuilder/BuildableNodesFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SyntaxSupport
1616
import Utils
1717

1818
let buildableNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
19-
DeclSyntax("import SwiftSyntax")
19+
DeclSyntax("@_spi(ExperimentalLanguageFeatures) import SwiftSyntax")
2020

2121
for node in SYNTAX_NODES.compactMap(\.layoutNode) {
2222
let type = node.type

Sources/SwiftParser/Expressions.swift

Lines changed: 42 additions & 16 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,20 @@ 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)) {
536-
return RawExprSyntax(
537-
parseSwitchExpression(switchHandle: .constant(.keyword(.switch)))
538-
)
539-
} else if self.at(.keyword(.if)) {
540-
return RawExprSyntax(
541-
parseIfExpression(ifHandle: .constant(.keyword(.if)))
542-
)
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, let handle)?:
535+
return RawExprSyntax(self.parseDoExpression(doHandle: .noRecovery(handle)))
536+
case (.if, let handle)?:
537+
return RawExprSyntax(self.parseIfExpression(ifHandle: .noRecovery(handle)))
538+
case (.switch, let handle)?:
539+
return RawExprSyntax(self.parseSwitchExpression(switchHandle: .noRecovery(handle)))
540+
default:
541+
break
543542
}
544543

545544
switch self.at(anyIn: ExpressionPrefixOperator.self) {
@@ -2045,6 +2044,33 @@ extension Parser.Lookahead {
20452044
}
20462045
}
20472046

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

20502076
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
)

Sources/SwiftParser/generated/ExperimentalFeatures.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ extension Parser.ExperimentalFeatures {
3232

3333
/// Whether to enable the parsing of typed throws.
3434
public static let typedThrows = Self(rawValue: 1 << 2)
35+
36+
/// Whether to enable the parsing of 'do' expressions.
37+
public static let doExpressions = Self(rawValue: 1 << 3)
3538
}

Sources/SwiftParserDiagnostics/generated/ChildNameForDiagnostics.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import SwiftSyntax
15+
@_spi(ExperimentalLanguageFeatures) import SwiftSyntax
1616

1717
private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String? {
1818
switch keyPath {
@@ -92,6 +92,8 @@ private func childNameForDiagnostics(_ keyPath: AnyKeyPath) -> String? {
9292
return "value type"
9393
case \DifferentiabilityWithRespectToArgumentSyntax.arguments:
9494
return "arguments"
95+
case \DoExprSyntax.body:
96+
return "body"
9597
case \DoStmtSyntax.body:
9698
return "body"
9799
case \DocumentationAttributeArgumentSyntax.label:

Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ extension SyntaxKind {
131131
return "'@differentiable' arguments"
132132
case .discardStmt:
133133
return "'discard' statement"
134+
case .doExpr:
135+
return "'do' block"
134136
case .doStmt:
135137
return "'do' statement"
136138
case .documentationAttributeArgumentList:

Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,20 @@ public func childName(_ keyPath: AnyKeyPath) -> String? {
10151015
return "expression"
10161016
case \DiscardStmtSyntax.unexpectedAfterExpression:
10171017
return "unexpectedAfterExpression"
1018+
case \DoExprSyntax.unexpectedBeforeDoKeyword:
1019+
return "unexpectedBeforeDoKeyword"
1020+
case \DoExprSyntax.doKeyword:
1021+
return "doKeyword"
1022+
case \DoExprSyntax.unexpectedBetweenDoKeywordAndBody:
1023+
return "unexpectedBetweenDoKeywordAndBody"
1024+
case \DoExprSyntax.body:
1025+
return "body"
1026+
case \DoExprSyntax.unexpectedBetweenBodyAndCatchClauses:
1027+
return "unexpectedBetweenBodyAndCatchClauses"
1028+
case \DoExprSyntax.catchClauses:
1029+
return "catchClauses"
1030+
case \DoExprSyntax.unexpectedAfterCatchClauses:
1031+
return "unexpectedAfterCatchClauses"
10181032
case \DoStmtSyntax.unexpectedBeforeDoKeyword:
10191033
return "unexpectedBeforeDoKeyword"
10201034
case \DoStmtSyntax.doKeyword:

Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,20 @@ open class SyntaxAnyVisitor: SyntaxVisitor {
728728
visitAnyPost(node._syntaxNode)
729729
}
730730

731+
#if compiler(>=5.8)
732+
@_spi(ExperimentalLanguageFeatures)
733+
#endif
734+
override open func visit(_ node: DoExprSyntax) -> SyntaxVisitorContinueKind {
735+
return visitAny(node._syntaxNode)
736+
}
737+
738+
#if compiler(>=5.8)
739+
@_spi(ExperimentalLanguageFeatures)
740+
#endif
741+
override open func visitPost(_ node: DoExprSyntax) {
742+
visitAnyPost(node._syntaxNode)
743+
}
744+
731745
override open func visit(_ node: DoStmtSyntax) -> SyntaxVisitorContinueKind {
732746
return visitAny(node._syntaxNode)
733747
}

0 commit comments

Comments
 (0)