@@ -27,13 +27,8 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
2727 syntax: TupleExprSyntax ,
2828 in context: Void
2929 ) -> ExprSyntax {
30- // If the tuple has any unexpected nodes, it's not a simple parenthetical expression
31- // we should be refactoring (e.g., parsing recovery might have put some parts in unexpected nodes).
32- guard syntax. unexpectedBeforeLeftParen == nil ,
33- syntax. unexpectedBetweenLeftParenAndElements == nil ,
34- syntax. unexpectedBetweenElementsAndRightParen == nil ,
35- syntax. unexpectedAfterRightParen == nil
36- else {
30+ // If the syntax tree has errors, we should not attempt to refactor it.
31+ guard !syntax. hasError else {
3732 return ExprSyntax ( syntax)
3833 }
3934
@@ -49,7 +44,7 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
4944 }
5045
5146 // Case 2: Parentheses around simple expressions
52- if canRemoveParentheses ( around: innerExpr, in: syntax. parent) {
47+ if canRemoveParentheses ( tuple : syntax , around: innerExpr, in: syntax. parent) {
5348 return preserveTrivia ( from: syntax, to: innerExpr)
5449 }
5550
@@ -70,7 +65,8 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
7065 . with ( \. trailingTrivia, trailingTrivia)
7166 }
7267
73- private static func canRemoveParentheses( around expr: ExprSyntax , in parent: Syntax ? ) -> Bool {
68+ private static func canRemoveParentheses( tuple: TupleExprSyntax , around expr: ExprSyntax , in parent: Syntax ? ) -> Bool
69+ {
7470 // Safety Check: Ambiguous Closures
7571 // Closures and trailing closures inside conditions need parentheses to avoid ambiguity.
7672 // e.g. `if ({ true }) == ({ true }) {}` or `if (call { true }) == false {}`
@@ -96,28 +92,33 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
9692 return false
9793 }
9894
99- // Allowlist: Parents where parentheses are always redundant (unless blocked above)
100- if let parent = parent,
101- parent. is ( InitializerClauseSyntax . self)
102- || parent. is ( ConditionElementSyntax . self)
103- || parent. is ( ReturnStmtSyntax . self)
104- || parent. is ( ThrowStmtSyntax . self)
105- || parent. is ( SwitchExprSyntax . self)
106- || parent. is ( RepeatStmtSyntax . self)
107- {
108- return true
95+ // Allowlist: Check keyPathInParent to explicitly know that this expression
96+ // occurs in a place where the parentheses are redundant.
97+ if let keyPath = tuple. keyPathInParent {
98+ switch keyPath {
99+ case \InitializerClauseSyntax . value,
100+ \ConditionElementSyntax . condition,
101+ \ReturnStmtSyntax . expression,
102+ \ThrowStmtSyntax . expression,
103+ \SwitchExprSyntax . subject,
104+ \RepeatStmtSyntax . condition:
105+ return true
106+ default :
107+ break
108+ }
109109 }
110110
111111 // Fallback: Allow if the expression itself is "simple"
112112 guard isSimpleExpression ( expr) else {
113113 return false
114114 }
115115
116- // Safety Check: Postfix Precedence
117- // Expressions like `try`, `await`, `consume`, and `copy` bind looser than postfix expressions.
116+ // Safety Check: Postfix and Binary Precedence
117+ // Expressions like `try`, `await`, `consume`, and `copy` bind looser than postfix and infix expressions.
118118 // e.g., `(try? f()).description` is different from `try? f().description`.
119119 // The former accesses `.description` on the Optional result, the latter on the unwrapped value.
120- if isPostfixParent ( parent) {
120+ // Similarly, `(try? f()) + 1` is different from `try? f() + 1` (Int? + Int vs Int + Int).
121+ if parentHasTighterBindingThanEffect ( parent) {
121122 switch expr. as ( ExprSyntaxEnum . self) {
122123 case . tryExpr, . awaitExpr, . unsafeExpr, . consumeExpr, . copyExpr:
123124 return false
@@ -129,17 +130,32 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
129130 return true
130131 }
131132
132- /// Returns true if parent is a postfix expression where the tuple is the base.
133- private static func isPostfixParent( _ parent: Syntax ? ) -> Bool {
133+ /// Returns true if parent is an expression with higher precedence than effects (try/await/etc).
134+ /// This includes postfix expressions (member access, subscript, call, force unwrap, optional chaining),
135+ /// infix operators, type casting (as/is), and ternary expressions.
136+ private static func parentHasTighterBindingThanEffect( _ parent: Syntax ? ) -> Bool {
134137 switch parent? . as ( SyntaxEnum . self) {
135- case . memberAccessExpr( let memberAccess) :
136- return memberAccess. base != nil
137- case . subscriptCallExpr( let subscriptCall) :
138- return subscriptCall. calledExpression. is ( TupleExprSyntax . self)
139- case . functionCallExpr( let functionCall) :
140- return functionCall. calledExpression. is ( TupleExprSyntax . self)
141- case . postfixOperatorExpr:
138+ // Postfix expressions: member access, subscript, function call, force unwrap, and postfix operators
139+ // These all bind tighter than effect expressions (try/await/etc).
140+ // For member access, since we're a TupleExprSyntax, we are always the base.
141+ case . memberAccessExpr, . subscriptCallExpr, . functionCallExpr, . forceUnwrapExpr, . postfixOperatorExpr:
142142 return true
143+
144+ case . optionalChainingExpr( let optionalChaining) :
145+ // Optional chaining (?.) binds tighter than effects
146+ return optionalChaining. expression != nil
147+
148+ // Infix operators and sequence expressions bind tighter than effects.
149+ // For sequence expressions (before SwiftOperators folding), the parent chain
150+ // is: TupleExpr -> ExprList -> SequenceExpr, e.g., `(try? f()) + 1`.
151+ case . infixOperatorExpr, . sequenceExpr, . exprList:
152+ return true
153+
154+ // Type casting operators (as, is) bind tighter than effects.
155+ // Ternary operator also binds tighter than effects.
156+ case . asExpr, . isExpr, . ternaryExpr:
157+ return true
158+
143159 default :
144160 return false
145161 }
0 commit comments