Skip to content

Commit 700f876

Browse files
Fix closure preservation in conditions
1 parent bf482b3 commit 700f876

File tree

2 files changed

+20
-6
lines changed

2 files changed

+20
-6
lines changed

Sources/SwiftRefactor/RemoveRedundantParentheses.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,13 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
6161
}
6262

6363
private static func canRemoveParentheses(around expr: ExprSyntax, in parent: Syntax?) -> Bool {
64-
// Parentheses in initializer clauses (e.g., `let x = (a + b)`) are always redundant.
64+
// Parentheses in initializer clauses (e.g., `let x = (a + b)`) are redundant,
65+
// unless they enclose a closure in a condition context (to avoid parsing ambiguity).
6566
if parent?.is(InitializerClauseSyntax.self) == true {
67+
let isInCondition = parent?.ancestorOrSelf(mapping: { $0.as(ConditionElementSyntax.self) }) != nil
68+
if isInCondition && (expr.is(ClosureExprSyntax.self) || hasTrailingClosure(expr)) {
69+
return false
70+
}
6671
return true
6772
}
6873

@@ -75,7 +80,7 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
7580
// The parser may not always produce a TypeExprSyntax, so we conservatively check for these accesses.
7681
if let memberAccess = parent?.as(MemberAccessExprSyntax.self) {
7782
let memberName = memberAccess.declName.baseName.text
78-
if memberName == "self" || memberName == "Type" {
83+
if memberName == "self" || memberName == "Type" || memberName == "Protocol" {
7984
return false
8085
}
8186
}
@@ -87,10 +92,7 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
8792
parent?.ancestorOrSelf(mapping: { $0.as(ConditionElementSyntax.self) }) != nil
8893
|| parent?.is(RepeatStmtSyntax.self) == true
8994
if isInCondition {
90-
if expr.is(ClosureExprSyntax.self) {
91-
return false
92-
}
93-
if hasTrailingClosure(expr) {
95+
if expr.is(ClosureExprSyntax.self) || hasTrailingClosure(expr) {
9496
return false
9597
}
9698
}

Tests/SwiftRefactorTest/RemoveRedundantParenthesesTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ final class RemoveRedundantParenthesesTest: XCTestCase {
7676
// `let x = (a + b)` removes parens because InitializerClauseSyntax context
7777
try assertParenRemoval("let x = (a + b)", expected: "let x = a + b")
7878
try assertParenRemoval("let x = ((1))", expected: "let x = 1")
79+
80+
// `if let` and `guard let` initializers also remove parentheses
81+
try assertParenRemoval("if let x = (a + b) {}", expected: "if let x = a + b {}")
82+
try assertParenRemoval("if var x = (a + b) {}", expected: "if var x = a + b {}")
83+
try assertParenRemoval("guard let x = (a + b) else {}", expected: "guard let x = a + b else {}")
84+
85+
// `try f()` is not a "simple expression", but in an initializer clause the parentheses are still redundant.
86+
try assertParenRemoval("let x = (try f())", expected: "let x = try f()")
7987
}
8088

8189
func testPreservesParenthesesInConditions() throws {
@@ -91,6 +99,10 @@ final class RemoveRedundantParenthesesTest: XCTestCase {
9199
try assertParenRemoval("if (#macro { true }) == false {}", expected: "if (#macro { true }) == false {}")
92100
// Subscripts with trailing closures
93101
try assertParenRemoval("if (array[0] { true }) == false {}", expected: "if (array[0] { true }) == false {}")
102+
103+
// Complex trailing closures in conditions
104+
try assertParenRemoval("if (call { true }) == false {}", expected: "if (call { true }) == false {}")
105+
try assertParenRemoval("if let x: () -> Bool = ({ true }) {}", expected: "if let x: () -> Bool = ({ true }) {}")
94106
}
95107

96108
func testPreservesParenthesesForMetatypes() throws {

0 commit comments

Comments
 (0)