@@ -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,7 @@ 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 {
7469 // Safety Check: Ambiguous Closures
7570 // Closures and trailing closures inside conditions need parentheses to avoid ambiguity.
7671 // e.g. `if ({ true }) == ({ true }) {}` or `if (call { true }) == false {}`
@@ -96,28 +91,33 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
9691 return false
9792 }
9893
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
94+ // Allowlist: Check keyPathInParent to explicitly know that this expression
95+ // occurs in a place where the parentheses are redundant.
96+ if let keyPath = tuple. keyPathInParent {
97+ switch keyPath {
98+ case \InitializerClauseSyntax . value,
99+ \ConditionElementSyntax . condition,
100+ \ReturnStmtSyntax . expression,
101+ \ThrowStmtSyntax . expression,
102+ \SwitchExprSyntax . subject,
103+ \RepeatStmtSyntax . condition:
104+ return true
105+ default :
106+ break
107+ }
109108 }
110109
111110 // Fallback: Allow if the expression itself is "simple"
112111 guard isSimpleExpression ( expr) else {
113112 return false
114113 }
115114
116- // Safety Check: Postfix Precedence
117- // Expressions like `try`, `await`, `consume`, and `copy` bind looser than postfix expressions.
115+ // Safety Check: Postfix and Binary Precedence
116+ // Expressions like `try`, `await`, `consume`, and `copy` bind looser than postfix and infix expressions.
118117 // e.g., `(try? f()).description` is different from `try? f().description`.
119118 // The former accesses `.description` on the Optional result, the latter on the unwrapped value.
120- if isPostfixParent ( parent) {
119+ // Similarly, `(try? f()) + 1` is different from `try? f() + 1` (Int? + Int vs Int + Int).
120+ if parentHasTighterBindingThanEffect ( parent) {
121121 switch expr. as ( ExprSyntaxEnum . self) {
122122 case . tryExpr, . awaitExpr, . unsafeExpr, . consumeExpr, . copyExpr:
123123 return false
@@ -129,17 +129,32 @@ public struct RemoveRedundantParentheses: SyntaxRefactoringProvider {
129129 return true
130130 }
131131
132- /// Returns true if parent is a postfix expression where the tuple is the base.
133- private static func isPostfixParent( _ parent: Syntax ? ) -> Bool {
132+ /// Returns true if parent is an expression with higher precedence than effects (try/await/etc).
133+ /// This includes postfix expressions (member access, subscript, call, force unwrap, optional chaining),
134+ /// infix operators, type casting (as/is), and ternary expressions.
135+ private static func parentHasTighterBindingThanEffect( _ parent: Syntax ? ) -> Bool {
134136 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:
137+ // Postfix expressions: member access, subscript, function call, force unwrap, and postfix operators
138+ // These all bind tighter than effect expressions (try/await/etc).
139+ // For member access, since we're a TupleExprSyntax, we are always the base.
140+ case . memberAccessExpr, . subscriptCallExpr, . functionCallExpr, . forceUnwrapExpr, . postfixOperatorExpr:
142141 return true
142+
143+ case . optionalChainingExpr( let optionalChaining) :
144+ // Optional chaining (?.) binds tighter than effects
145+ return optionalChaining. expression != nil
146+
147+ // Infix operators and sequence expressions bind tighter than effects.
148+ // For sequence expressions (before SwiftOperators folding), the parent chain
149+ // is: TupleExpr -> ExprList -> SequenceExpr, e.g., `(try? f()) + 1`.
150+ case . infixOperatorExpr, . sequenceExpr, . exprList:
151+ return true
152+
153+ // Type casting operators (as, is) bind tighter than effects.
154+ // Ternary operator also binds tighter than effects.
155+ case . asExpr, . isExpr, . ternaryExpr:
156+ return true
157+
143158 default :
144159 return false
145160 }
0 commit comments