Skip to content

Commit 427d314

Browse files
committed
Add support/tests for Swift 5.5 concurrency syntax.
- `for await` and `for try await` loops - `async` closure expressions - Improve grouping for `async throws` functions/closures - `await` expressions - `async let` declarations
1 parent 5a3fe7a commit 427d314

File tree

9 files changed

+507
-36
lines changed

9 files changed

+507
-36
lines changed

Sources/SwiftFormatPrettyPrint/SequenceExprFolding.swift

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,11 @@ extension SequenceExprSyntax {
9696

9797
case 3:
9898
// A sequence with three elements will not be changed by folding unless
99-
// it contains a cast expression, ternary, or `try`. (This may be more
100-
// inclusive than it needs to be.)
99+
// it contains a cast expression, ternary, `await`, or `try`. (This may
100+
// be more inclusive than it needs to be.)
101101
return elements.contains {
102-
$0.is(AsExprSyntax.self) || $0.is(IsExprSyntax.self) || $0.is(TernaryExprSyntax.self)
102+
$0.is(AsExprSyntax.self) || $0.is(IsExprSyntax.self)
103+
|| $0.is(TernaryExprSyntax.self) || $0.is(AwaitExprSyntax.self)
103104
|| $0.is(TryExprSyntax.self)
104105
}
105106

@@ -331,8 +332,8 @@ extension SequenceExprSyntax {
331332
/// This function takes into account certain corrections that must occur as
332333
/// part of folding, like repairing ternary and cast expressions (undoing the
333334
/// even/odd normalization that was performed at the beginning of the
334-
/// algorithm), as well as absorbing other operators and operands into `try`
335-
/// expressions.
335+
/// algorithm), as well as absorbing other operators and operands into
336+
/// `await/try` expressions.
336337
private func makeExpression(
337338
operator op: ExprSyntax,
338339
lhs: ExprSyntax,
@@ -341,31 +342,39 @@ extension SequenceExprSyntax {
341342
) -> ExprSyntax {
342343
var lhs = lhs
343344

344-
// If the left-hand side is a `try`, hoist it up. The compiler will parse an
345-
// expression like `try foo() + 1` syntactically as
346-
// `SequenceExpr(TryExpr(foo()), +, 1)`, then fold the rest of the
347-
// expression into the `try` as `TryExpr(BinaryExpr(foo(), +, 1))`. So, we
348-
// temporarily drop down to the subexpression for the purposes of this
349-
// function, then before returning below, we wrap the result back in the
350-
// `try`.
345+
// If the left-hand side is a `try` or `await`, hoist it up. The compiler
346+
// will parse an expression like `try|await foo() + 1` syntactically as
347+
// `SequenceExpr(TryExpr|AwaitExpr(foo()), +, 1)`, then fold the rest of
348+
// the expression into the `try|await` as
349+
// `TryExpr|AwaitExpr(BinaryExpr(foo(), +, 1))`. So, we temporarily drop
350+
// down to the subexpression for the purposes of this function, then before
351+
// returning below, we wrap the result back in the `try|await`.
351352
//
352-
// If the right-hand side is a `try`, it's an error unless the operator is
353-
// an assignment or ternary operator and there's nothing to the right that
354-
// didn't parse as part of the right operand. The compiler handles that case
355-
// so that it can emit an error, but for the purposes of the syntax tree, we
356-
// can leave it alone.
353+
// If the right-hand side is a `try` or `await`, it's an error unless the
354+
// operator is an assignment or ternary operator and there's nothing to the
355+
// right that didn't parse as part of the right operand. The compiler
356+
// handles that case so that it can emit an error, but for the purposes of
357+
// the syntax tree, we can leave it alone.
357358
let maybeTryExpr = lhs.as(TryExprSyntax.self)
358359
if let tryExpr = maybeTryExpr {
359360
lhs = tryExpr.expression
360361
}
362+
let maybeAwaitExpr = lhs.as(AwaitExprSyntax.self)
363+
if let awaitExpr = maybeAwaitExpr {
364+
lhs = awaitExpr.expression
365+
}
361366

362367
let makeResultExpression = { (expr: ExprSyntax) -> ExprSyntax in
363-
// Fold the result back into the `try` if it was present; otherwise, just
364-
// return the result itself.
368+
// Fold the result back into the `try` and/or `await` if either were
369+
// present; otherwise, just return the result itself.
370+
var result = expr
371+
if let awaitExpr = maybeAwaitExpr {
372+
result = ExprSyntax(awaitExpr.withExpression(result))
373+
}
365374
if let tryExpr = maybeTryExpr {
366-
return ExprSyntax(tryExpr.withExpression(expr))
375+
result = ExprSyntax(tryExpr.withExpression(result))
367376
}
368-
return expr
377+
return result
369378
}
370379

371380
switch Syntax(op).as(SyntaxEnum.self) {

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,24 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
519519

520520
override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind {
521521
after(node.labelColon, tokens: .space)
522-
after(node.forKeyword, tokens: .space)
522+
523+
// If we have a `(try) await` clause, allow breaking after the `for` so that the `(try) await`
524+
// can fall onto the next line if needed, and if both `try await` are present, keep them
525+
// together. Otherwise, keep `for` glued to the token after it so that we break somewhere later
526+
// on the line.
527+
if let awaitKeyword = node.awaitKeyword {
528+
after(node.forKeyword, tokens: .break)
529+
if let tryKeyword = node.tryKeyword {
530+
before(tryKeyword, tokens: .open)
531+
after(tryKeyword, tokens: .break)
532+
after(awaitKeyword, tokens: .close, .break)
533+
} else {
534+
after(awaitKeyword, tokens: .break)
535+
}
536+
} else {
537+
after(node.forKeyword, tokens: .space)
538+
}
539+
523540
after(node.caseKeyword, tokens: .space)
524541
before(node.inKeyword, tokens: .break)
525542
after(node.inKeyword, tokens: .space)
@@ -897,10 +914,11 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
897914

898915
if let calledMemberAccessExpr = node.calledExpression.as(MemberAccessExprSyntax.self) {
899916
if let base = calledMemberAccessExpr.base, base.is(IdentifierExprSyntax.self) {
900-
// When this function call is wrapped by a try-expr, the group applied when visiting the
901-
// try-expr is sufficient. Adding another gruop here in that case can result in
902-
// unnecessarily breaking after the try keyword.
903-
if !(base.firstToken?.previousToken?.parent?.is(TryExprSyntax.self) ?? false) {
917+
// When this function call is wrapped by a try-expr or await-expr, the group applied when
918+
// visiting that wrapping expression is sufficient. Adding another group here in that case
919+
// can result in unnecessarily breaking after the try/await keyword.
920+
if !(base.firstToken?.previousToken?.parent?.is(TryExprSyntax.self) ?? false
921+
|| base.firstToken?.previousToken?.parent?.is(AwaitExprSyntax.self) ?? false) {
904922
before(base.firstToken, tokens: .open)
905923
after(calledMemberAccessExpr.name.lastToken, tokens: .close)
906924
}
@@ -1087,7 +1105,13 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
10871105
}
10881106
}
10891107

1108+
before(node.asyncKeyword, tokens: .break)
10901109
before(node.throwsTok, tokens: .break)
1110+
if let asyncKeyword = node.asyncKeyword, let throwsTok = node.throwsTok {
1111+
before(asyncKeyword, tokens: .open)
1112+
after(throwsTok, tokens: .close)
1113+
}
1114+
10911115
before(node.output?.arrow, tokens: .break)
10921116
after(node.lastToken, tokens: .close)
10931117
before(node.inTok, tokens: .break(.same))
@@ -1461,32 +1485,52 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
14611485
tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)))
14621486

14631487
// Check for an anchor token inside of the expression to group with the try keyword.
1464-
if let anchorToken = findTryExprConnectingToken(inExpr: node.expression) {
1488+
if let anchorToken = findTryAwaitExprConnectingToken(inExpr: node.expression) {
14651489
before(node.tryKeyword, tokens: .open)
14661490
after(anchorToken, tokens: .close)
14671491
}
14681492

14691493
return .visitChildren
14701494
}
14711495

1496+
override func visit(_ node: AwaitExprSyntax) -> SyntaxVisitorContinueKind {
1497+
before(
1498+
node.expression.firstToken,
1499+
tokens: .break(.continue, newlines: .elective(ignoresDiscretionary: true)))
1500+
1501+
// Check for an anchor token inside of the expression to group with the await keyword.
1502+
if !(node.parent?.is(TryExprSyntax.self) ?? false),
1503+
let anchorToken = findTryAwaitExprConnectingToken(inExpr: node.expression)
1504+
{
1505+
before(node.awaitKeyword, tokens: .open)
1506+
after(anchorToken, tokens: .close)
1507+
}
1508+
1509+
return .visitChildren
1510+
}
1511+
14721512
/// Searches the AST from `expr` to find a token that should be grouped with an enclosing
1473-
/// try-expr. Returns that token, or nil when no such token is found.
1513+
/// try-expr or await-expr. Returns that token, or nil when no such token is found.
14741514
///
1475-
/// - Parameter expr: An expression that is wrapped by a try-expr.
1476-
/// - Returns: A token that should be grouped with the try-expr, or nil.
1477-
func findTryExprConnectingToken(inExpr expr: ExprSyntax) -> TokenSyntax? {
1515+
/// - Parameter expr: An expression that is wrapped by a try-expr or await-expr.
1516+
/// - Returns: A token that should be grouped with the try-expr or await-expr, or nil.
1517+
func findTryAwaitExprConnectingToken(inExpr expr: ExprSyntax) -> TokenSyntax? {
1518+
if let awaitExpr = expr.as(AwaitExprSyntax.self) {
1519+
// If we were called from the `try` of a `try await <expr>`, drill into the child expression.
1520+
return findTryAwaitExprConnectingToken(inExpr: awaitExpr.expression)
1521+
}
14781522
if let callingExpr = expr.asProtocol(CallingExprSyntaxProtocol.self) {
1479-
return findTryExprConnectingToken(inExpr: callingExpr.calledExpression)
1523+
return findTryAwaitExprConnectingToken(inExpr: callingExpr.calledExpression)
14801524
}
14811525
if let memberAccessExpr = expr.as(MemberAccessExprSyntax.self), let base = memberAccessExpr.base
14821526
{
1483-
// When there's a simple base (i.e. identifier), group the entire `try <base>.<name>`
1527+
// When there's a simple base (i.e. identifier), group the entire `try/await <base>.<name>`
14841528
// sequence. This check has to happen here so that the `MemberAccessExprSyntax.name` is
14851529
// available.
14861530
if base.is(IdentifierExprSyntax.self) {
14871531
return memberAccessExpr.name.lastToken
14881532
}
1489-
return findTryExprConnectingToken(inExpr: base)
1533+
return findTryAwaitExprConnectingToken(inExpr: base)
14901534
}
14911535
if expr.is(IdentifierExprSyntax.self) {
14921536
return expr.lastToken
@@ -1634,13 +1678,28 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
16341678
}
16351679

16361680
override func visit(_ node: DeclModifierSyntax) -> SyntaxVisitorContinueKind {
1637-
after(node.lastToken, tokens: .break)
1681+
// Due to the way we currently use spaces after let/var keywords in variable bindings, we need
1682+
// this special exception for `async let` statements to avoid breaking prematurely between the
1683+
// `async` and `let` keywords.
1684+
let breakOrSpace: Token
1685+
if node.name.tokenKind == .identifier("async") {
1686+
breakOrSpace = .space
1687+
} else {
1688+
breakOrSpace = .break
1689+
}
1690+
after(node.lastToken, tokens: breakOrSpace)
16381691
return .visitChildren
16391692
}
16401693

16411694
override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind {
16421695
before(node.asyncOrReasyncKeyword, tokens: .break)
16431696
before(node.throwsOrRethrowsKeyword, tokens: .break)
1697+
if let asyncOrReasyncKeyword = node.asyncOrReasyncKeyword,
1698+
let throwsOrRethrowsKeyword = node.throwsOrRethrowsKeyword
1699+
{
1700+
before(asyncOrReasyncKeyword, tokens: .open)
1701+
after(throwsOrRethrowsKeyword, tokens: .close)
1702+
}
16441703
before(node.output?.firstToken, tokens: .break)
16451704
return .visitChildren
16461705
}
@@ -3087,10 +3146,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
30873146
}
30883147

30893148
/// Returns whether the given expression consists of multiple subexpressions. Certain expressions
3090-
/// that are known to wrap an expressions, e.g. try expressions, are handled by checking the
3149+
/// that are known to wrap an expression, e.g. try expressions, are handled by checking the
30913150
/// expression that they contain.
30923151
private func isCompoundExpression(_ expr: ExprSyntax) -> Bool {
30933152
switch Syntax(expr).as(SyntaxEnum.self) {
3153+
case .awaitExpr(let awaitExpr):
3154+
return isCompoundExpression(awaitExpr.expression)
30943155
case .sequenceExpr(let sequenceExpr):
30953156
return sequenceExpr.elements.count > 1
30963157
case .ternaryExpr:
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import SwiftFormatConfiguration
2+
3+
final class AwaitExprTests: PrettyPrintTestCase {
4+
func testBasicAwaits() {
5+
let input =
6+
"""
7+
let a = await asynchronousFunction()
8+
let b = await longerAsynchronousFunction()
9+
let c = await evenLongerAndLongerAsynchronousFunction()
10+
"""
11+
12+
let expected =
13+
"""
14+
let a = await asynchronousFunction()
15+
let b =
16+
await longerAsynchronousFunction()
17+
let c =
18+
await
19+
evenLongerAndLongerAsynchronousFunction()
20+
21+
"""
22+
23+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 36)
24+
}
25+
26+
func testAwaitKeywordBreaking() {
27+
let input =
28+
"""
29+
let aVeryLongArgumentName = await foo.bar()
30+
let aVeryLongArgumentName = await
31+
foo.bar()
32+
let abc = await foo.baz().quxxe(a, b, c).bar()
33+
let abc = await foo
34+
.baz().quxxe(a, b, c).bar()
35+
let abc = await [1, 2, 3, 4, 5, 6, 7].baz().quxxe(a, b, c).bar()
36+
let abc = await [1, 2, 3, 4, 5, 6, 7]
37+
.baz().quxxe(a, b, c).bar()
38+
let abc = await foo.baz().quxxe(a, b, c).bar[0]
39+
let abc = await foo
40+
.baz().quxxe(a, b, c).bar[0]
41+
let abc = await
42+
foo
43+
.baz().quxxe(a, b, c).bar[0]
44+
"""
45+
46+
let expected =
47+
"""
48+
let aVeryLongArgumentName =
49+
await foo.bar()
50+
let aVeryLongArgumentName =
51+
await foo.bar()
52+
let abc = await foo.baz().quxxe(a, b, c)
53+
.bar()
54+
let abc =
55+
await foo
56+
.baz().quxxe(a, b, c).bar()
57+
let abc = await [1, 2, 3, 4, 5, 6, 7]
58+
.baz().quxxe(a, b, c).bar()
59+
let abc = await [1, 2, 3, 4, 5, 6, 7]
60+
.baz().quxxe(a, b, c).bar()
61+
let abc = await foo.baz().quxxe(a, b, c)
62+
.bar[0]
63+
let abc =
64+
await foo
65+
.baz().quxxe(a, b, c).bar[0]
66+
let abc =
67+
await foo
68+
.baz().quxxe(a, b, c).bar[0]
69+
70+
"""
71+
72+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 42)
73+
}
74+
75+
func testTryAwaitKeywordBreaking() {
76+
let input =
77+
"""
78+
let aVeryLongArgumentName = try await foo.bar()
79+
let aVeryLongArgumentName = try await
80+
foo.bar()
81+
let abc = try await foo.baz().quxxe(a, b, c).bar()
82+
let abc = try await foo
83+
.baz().quxxe(a, b, c).bar()
84+
let abc = try await [1, 2, 3, 4, 5, 6, 7].baz().quxxe(a, b, c).bar()
85+
let abc = try await [1, 2, 3, 4, 5, 6, 7]
86+
.baz().quxxe(a, b, c).bar()
87+
let abc = try await foo.baz().quxxe(a, b, c).bar[0]
88+
let abc = try await foo
89+
.baz().quxxe(a, b, c).bar[0]
90+
let abc = try await
91+
foo
92+
.baz().quxxe(a, b, c).bar[0]
93+
let abc = try await thisIsASuperblyExtremelyVeryLongFunctionName()
94+
"""
95+
96+
let expected =
97+
"""
98+
let aVeryLongArgumentName =
99+
try await foo.bar()
100+
let aVeryLongArgumentName =
101+
try await foo.bar()
102+
let abc = try await foo.baz().quxxe(a, b, c)
103+
.bar()
104+
let abc =
105+
try await foo
106+
.baz().quxxe(a, b, c).bar()
107+
let abc = try await [1, 2, 3, 4, 5, 6, 7]
108+
.baz().quxxe(a, b, c).bar()
109+
let abc = try await [1, 2, 3, 4, 5, 6, 7]
110+
.baz().quxxe(a, b, c).bar()
111+
let abc = try await foo.baz().quxxe(a, b, c)
112+
.bar[0]
113+
let abc =
114+
try await foo
115+
.baz().quxxe(a, b, c).bar[0]
116+
let abc =
117+
try await foo
118+
.baz().quxxe(a, b, c).bar[0]
119+
let abc =
120+
try await
121+
thisIsASuperblyExtremelyVeryLongFunctionName()
122+
123+
"""
124+
125+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 46)
126+
}
127+
}

0 commit comments

Comments
 (0)