Skip to content

Conversation

@PhantomInTheWire
Copy link
Member

Relevant issue: swiftlang/sourcekit-lsp#2451

  • Introduces RemoveRedundantParentheses refactoring to strip unnecessary parentheses from expressions.
  • Handles nested parentheses (e.g., ((x)) -> x) and simple literals while accurately preserving source trivia.
  • Implements safety checks to retain parentheses in specific contexts, such as closures in condition elements.
  • Adds test coverage for complex cases including try?, await, and optional chaining to ensure precedence is maintained.

The scope of possible edge cases here is quite large, I might be missing some but I have tried to cover as many as I could come up with.

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing this. This is a great start, the question is how far you want to take this? Should this build on top of SwiftOperators to eg. simplify 1 + (2 * 3) to 1 + 2 * 3? And could we also remove unnecessary parentheses in let x = (1 + 2)?

Comment on lines 52 to 60
private static func getSingleUnlabeledElement(from tuple: TupleExprSyntax) -> LabeledExprSyntax? {
guard tuple.elements.count == 1,
let element = tuple.elements.first,
element.label == nil
else {
return nil
}
return element
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should already have this as tuple.elemetns.singleUnlabledExpression or tuple.isParentheses in SwiftIfConfig. Maybe it makes sense to pull this up to Convenience.swift in the SwiftSyntax module, for now at the package access level.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made it public for now since bazel build breaks due to some reason when i make it package level.(I'll probably have to read up on how bazel and build config works to figure out why it breaks.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, seems like we don’t have any package access level declarations right now. Let’s use @_spi(RawSyntax) public for now. I don’t want to add this as part of the public API just yet in an effort to keep the API surface small.

@keith Do we need to set the package name in the Bazel rules so we can use the package access modifier? If so, could you add it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we just have to set package_name for w/e targets to w/e string and that will be passed through to -package-name. don't block on bazel to merge this in the preferred way and I can fix that up async

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Thank you @keith!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the CMake build doesn’t support the package access level yet either, let’s use @_spi(RawSyntax) public for now.

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just gave this a little more of an in-depth review and found some more edge cases we need to handle. Given that this references swiftlang/sourcekit-lsp#2451 as the issue and that contains let x = (a + b), we should also at least handle that case, even if we don’t go into full operator-parsing just yet.

@PhantomInTheWire PhantomInTheWire force-pushed the feat/redun-parantheses branch 2 times, most recently from 696112d to 700f876 Compare January 15, 2026 21:35
@PhantomInTheWire
Copy link
Member Author

I've added more through tests and a assertParenRemoval helper since the old method would have required a lot of ast parsing and was quite verbose

@PhantomInTheWire
Copy link
Member Author

rebased to main and make singleUnlabeledExpression package level

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments, yet again. This really is more tricky than it looks like on the surface, even without operator handling.

@PhantomInTheWire PhantomInTheWire force-pushed the feat/redun-parantheses branch 2 times, most recently from f7aa51d to c8ef85d Compare January 19, 2026 01:13
@PhantomInTheWire
Copy link
Member Author

yep, it does seem like there are a lot of edge cases that we can potentially run into with this pr, also a heads up I added support for removing redundant parentheses in control flow and return/throw statements(it was non trivial and felt weird not having something so basic)

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delayed review. I looked through this again and found some more cases that we weren’t handling correctly.

@PhantomInTheWire
Copy link
Member Author

getting a second look at this after some time, this is trickier than it sounds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants