Skip to content

Commit 5510297

Browse files
authored
Merge pull request #539 from allevato/async-throws
Fix `async throws` function types when they appear in an expression context.
2 parents d1c7701 + dabb463 commit 5510297

File tree

3 files changed

+214
-40
lines changed

3 files changed

+214
-40
lines changed

Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,21 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
412412
return .visitChildren
413413
}
414414

415+
override func visit(_ node: AccessorEffectSpecifiersSyntax) -> SyntaxVisitorContinueKind {
416+
arrangeEffectSpecifiers(node)
417+
return .visitChildren
418+
}
419+
420+
override func visit(_ node: FunctionEffectSpecifiersSyntax) -> SyntaxVisitorContinueKind {
421+
arrangeEffectSpecifiers(node)
422+
return .visitChildren
423+
}
424+
425+
override func visit(_ node: TypeEffectSpecifiersSyntax) -> SyntaxVisitorContinueKind {
426+
arrangeEffectSpecifiers(node)
427+
return .visitChildren
428+
}
429+
415430
/// Applies formatting tokens to the tokens in the given function or function-like declaration
416431
/// node (e.g., initializers, deinitiailizers, and subscripts).
417432
private func arrangeFunctionLikeDecl<Node: BracedSyntax, BodyContents: SyntaxCollection>(
@@ -434,6 +449,17 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
434449
after(node.lastToken(viewMode: .sourceAccurate), tokens: .close)
435450
}
436451

452+
/// Arranges the `async` and `throws` effect specifiers of a function or accessor declaration.
453+
private func arrangeEffectSpecifiers<Node: EffectSpecifiersSyntax>(_ node: Node) {
454+
before(node.asyncSpecifier, tokens: .break)
455+
before(node.throwsSpecifier, tokens: .break)
456+
// Keep them together if both `async` and `throws` are present.
457+
if let asyncSpecifier = node.asyncSpecifier, let throwsSpecifier = node.throwsSpecifier {
458+
before(asyncSpecifier, tokens: .open)
459+
after(throwsSpecifier, tokens: .close)
460+
}
461+
}
462+
437463
// MARK: - Property and subscript accessor block nodes
438464

439465
override func visit(_ node: AccessorListSyntax) -> SyntaxVisitorContinueKind {
@@ -449,22 +475,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
449475

450476
override func visit(_ node: AccessorDeclSyntax) -> SyntaxVisitorContinueKind {
451477
arrangeAttributeList(node.attributes)
452-
453-
if let asyncKeyword = node.effectSpecifiers?.asyncSpecifier {
454-
if node.effectSpecifiers?.throwsSpecifier != nil {
455-
before(asyncKeyword, tokens: .break, .open)
456-
} else {
457-
before(asyncKeyword, tokens: .break)
458-
}
459-
}
460-
461-
if let throwsKeyword = node.effectSpecifiers?.throwsSpecifier {
462-
before(node.effectSpecifiers?.throwsSpecifier, tokens: .break)
463-
if node.effectSpecifiers?.asyncSpecifier != nil {
464-
after(throwsKeyword, tokens: .close)
465-
}
466-
}
467-
468478
arrangeBracesAndContents(of: node.body, contentsKeyPath: \.statements)
469479
return .visitChildren
470480
}
@@ -1160,13 +1170,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
11601170
}
11611171
}
11621172

1163-
before(node.effectSpecifiers?.asyncSpecifier, tokens: .break)
1164-
before(node.effectSpecifiers?.throwsSpecifier, tokens: .break)
1165-
if let asyncKeyword = node.effectSpecifiers?.asyncSpecifier, let throwsTok = node.effectSpecifiers?.throwsSpecifier {
1166-
before(asyncKeyword, tokens: .open)
1167-
after(throwsTok, tokens: .close)
1168-
}
1169-
11701173
before(node.output?.arrow, tokens: .break)
11711174
after(node.lastToken(viewMode: .sourceAccurate), tokens: .close)
11721175
before(node.inTok, tokens: .break(.same))
@@ -1607,8 +1610,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
16071610
override func visit(_ node: FunctionTypeSyntax) -> SyntaxVisitorContinueKind {
16081611
after(node.leftParen, tokens: .break(.open, size: 0), .open)
16091612
before(node.rightParen, tokens: .break(.close, size: 0), .close)
1610-
before(node.effectSpecifiers?.asyncSpecifier, tokens: .break)
1611-
before(node.effectSpecifiers?.throwsSpecifier, tokens: .break)
16121613
return .visitChildren
16131614
}
16141615

@@ -1833,14 +1834,6 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
18331834
}
18341835

18351836
override func visit(_ node: FunctionSignatureSyntax) -> SyntaxVisitorContinueKind {
1836-
before(node.effectSpecifiers?.asyncSpecifier, tokens: .break)
1837-
before(node.effectSpecifiers?.throwsSpecifier, tokens: .break)
1838-
if let asyncOrReasyncKeyword = node.effectSpecifiers?.asyncSpecifier,
1839-
let throwsOrRethrowsKeyword = node.effectSpecifiers?.throwsSpecifier
1840-
{
1841-
before(asyncOrReasyncKeyword, tokens: .open)
1842-
after(throwsOrRethrowsKeyword, tokens: .close)
1843-
}
18441837
before(node.output?.firstToken(viewMode: .sourceAccurate), tokens: .break)
18451838
return .visitChildren
18461839
}
@@ -1873,6 +1866,14 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
18731866
}
18741867

18751868
let binOp = node.operatorOperand
1869+
if binOp.is(ArrowExprSyntax.self) {
1870+
// `ArrowExprSyntax` nodes occur when a function type is written in an expression context;
1871+
// for example, `let x = [(Int) throws -> Void]()`. We want to treat those consistently like
1872+
// we do other function return clauses and not treat them as regular binary operators, so
1873+
// handle that behavior there instead.
1874+
return .visitChildren
1875+
}
1876+
18761877
let rhs = node.rightOperand
18771878
maybeGroupAroundSubexpression(rhs, combiningOperator: binOp)
18781879

@@ -1986,9 +1987,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
19861987
}
19871988

19881989
override func visit(_ node: ArrowExprSyntax) -> SyntaxVisitorContinueKind {
1989-
// The break before the `throws` keyword is inserted at the `InfixOperatorExpr` level so that it
1990-
// is placed in the correct relative position to the group surrounding the "operator".
1991-
after(node.effectSpecifiers?.throwsSpecifier, tokens: .break)
1990+
before(node.arrowToken, tokens: .break)
1991+
after(node.arrowToken, tokens: .space)
19921992
return .visitChildren
19931993
}
19941994

Tests/SwiftFormatPrettyPrintTests/ArrayDeclTests.swift

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,70 @@ final class ArrayDeclTests: PrettyPrintTestCase {
7070
let input =
7171
"""
7272
let A = [(Int, Double) -> Bool]()
73+
let A = [(Int, Double) async -> Bool]()
7374
let A = [(Int, Double) throws -> Bool]()
75+
let A = [(Int, Double) async throws -> Bool]()
7476
"""
7577

76-
let expected =
78+
let expected46 =
7779
"""
7880
let A = [(Int, Double) -> Bool]()
81+
let A = [(Int, Double) async -> Bool]()
7982
let A = [(Int, Double) throws -> Bool]()
83+
let A = [(Int, Double) async throws -> Bool]()
8084
8185
"""
86+
assertPrettyPrintEqual(input: input, expected: expected46, linelength: 46)
8287

83-
assertPrettyPrintEqual(input: input, expected: expected, linelength: 45)
88+
let expected43 =
89+
"""
90+
let A = [(Int, Double) -> Bool]()
91+
let A = [(Int, Double) async -> Bool]()
92+
let A = [(Int, Double) throws -> Bool]()
93+
let A = [
94+
(Int, Double) async throws -> Bool
95+
]()
96+
97+
"""
98+
assertPrettyPrintEqual(input: input, expected: expected43, linelength: 43)
99+
100+
let expected35 =
101+
"""
102+
let A = [(Int, Double) -> Bool]()
103+
let A = [
104+
(Int, Double) async -> Bool
105+
]()
106+
let A = [
107+
(Int, Double) throws -> Bool
108+
]()
109+
let A = [
110+
(Int, Double) async throws
111+
-> Bool
112+
]()
113+
114+
"""
115+
assertPrettyPrintEqual(input: input, expected: expected35, linelength: 35)
116+
117+
let expected27 =
118+
"""
119+
let A = [
120+
(Int, Double) -> Bool
121+
]()
122+
let A = [
123+
(Int, Double) async
124+
-> Bool
125+
]()
126+
let A = [
127+
(Int, Double) throws
128+
-> Bool
129+
]()
130+
let A = [
131+
(Int, Double)
132+
async throws -> Bool
133+
]()
134+
135+
"""
136+
assertPrettyPrintEqual(input: input, expected: expected27, linelength: 27)
84137
}
85138

86139
func testNoTrailingCommasInTypes() {

Tests/SwiftFormatPrettyPrintTests/FunctionTypeTests.swift

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,127 @@ final class FunctionTypeTests: PrettyPrintTestCase {
6060
assertPrettyPrintEqual(input: input, expected: expected, linelength: 60)
6161
}
6262

63+
func testFunctionTypeAsync() {
64+
let input =
65+
"""
66+
func f(g: (_ somevalue: Int) async -> String?) {
67+
let a = 123
68+
let b = "abc"
69+
}
70+
func f(g: (currentLevel: Int) async -> String?) {
71+
let a = 123
72+
let b = "abc"
73+
}
74+
func f(g: (currentLevel: inout Int) async -> String?) {
75+
let a = 123
76+
let b = "abc"
77+
}
78+
func f(g: (variable1: Int, variable2: Double, variable3: Bool) async -> Double) {
79+
let a = 123
80+
let b = "abc"
81+
}
82+
func f(g: (variable1: Int, variable2: Double, variable3: Bool, variable4: String) async -> Double) {
83+
let a = 123
84+
let b = "abc"
85+
}
86+
"""
87+
88+
let expected =
89+
"""
90+
func f(g: (_ somevalue: Int) async -> String?) {
91+
let a = 123
92+
let b = "abc"
93+
}
94+
func f(g: (currentLevel: Int) async -> String?) {
95+
let a = 123
96+
let b = "abc"
97+
}
98+
func f(g: (currentLevel: inout Int) async -> String?) {
99+
let a = 123
100+
let b = "abc"
101+
}
102+
func f(
103+
g: (variable1: Int, variable2: Double, variable3: Bool) async ->
104+
Double
105+
) {
106+
let a = 123
107+
let b = "abc"
108+
}
109+
func f(
110+
g: (
111+
variable1: Int, variable2: Double, variable3: Bool,
112+
variable4: String
113+
) async -> Double
114+
) {
115+
let a = 123
116+
let b = "abc"
117+
}
118+
119+
"""
120+
121+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 66)
122+
}
123+
124+
func testFunctionTypeAsyncThrows() {
125+
let input =
126+
"""
127+
func f(g: (_ somevalue: Int) async throws -> String?) {
128+
let a = 123
129+
let b = "abc"
130+
}
131+
func f(g: (currentLevel: Int) async throws -> String?) {
132+
let a = 123
133+
let b = "abc"
134+
}
135+
func f(g: (currentLevel: inout Int) async throws -> String?) {
136+
let a = 123
137+
let b = "abc"
138+
}
139+
func f(g: (variable1: Int, variable2: Double, variable3: Bool) async throws -> Double) {
140+
let a = 123
141+
let b = "abc"
142+
}
143+
func f(g: (variable1: Int, variable2: Double, variable3: Bool, variable4: String) async throws -> Double) {
144+
let a = 123
145+
let b = "abc"
146+
}
147+
"""
148+
149+
let expected =
150+
"""
151+
func f(g: (_ somevalue: Int) async throws -> String?) {
152+
let a = 123
153+
let b = "abc"
154+
}
155+
func f(g: (currentLevel: Int) async throws -> String?) {
156+
let a = 123
157+
let b = "abc"
158+
}
159+
func f(g: (currentLevel: inout Int) async throws -> String?) {
160+
let a = 123
161+
let b = "abc"
162+
}
163+
func f(
164+
g: (variable1: Int, variable2: Double, variable3: Bool) async throws ->
165+
Double
166+
) {
167+
let a = 123
168+
let b = "abc"
169+
}
170+
func f(
171+
g: (
172+
variable1: Int, variable2: Double, variable3: Bool, variable4: String
173+
) async throws -> Double
174+
) {
175+
let a = 123
176+
let b = "abc"
177+
}
178+
179+
"""
180+
181+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 73)
182+
}
183+
63184
func testFunctionTypeThrows() {
64185
let input =
65186
"""
@@ -84,7 +205,7 @@ final class FunctionTypeTests: PrettyPrintTestCase {
84205
let b = "abc"
85206
}
86207
"""
87-
208+
88209
let expected =
89210
"""
90211
func f(g: (_ somevalue: Int) throws -> String?) {
@@ -117,7 +238,7 @@ final class FunctionTypeTests: PrettyPrintTestCase {
117238
}
118239
119240
"""
120-
241+
121242
assertPrettyPrintEqual(input: input, expected: expected, linelength: 67)
122243
}
123244

0 commit comments

Comments
 (0)