Avoid removing certain disambiguating parens from conditions (fixes #298) #503
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Essentially, after formatting the correct code in the snippet below, you end up with invalid Swift code (because of the parsing ambiguity introduced).
My fix adds another early exit condition (to the
NoParensAroundConditions
rule) that checks if the enclosed expression is a call to a closure expression. I have added a test for this new behaviour, and in doing so I found that there wasn't a test for the (already implemented) ambiguity check; so I added that to the test too.Tl;dr
Merging this PR would fix the exact issue raised in #298. However, I have found that the existing ambiguity check (pre-PR) for trailing closures in conditions is purely cosmetic (and incomplete) in current Swift. Thus, I think the check for disambiguating parens should be reworked to work like so:
If a condition contains any trailing closures or immediately called closures that aren't otherwise enclosed within
()
,[]
, or{}
(visually), the condition requires disambiguating parens around the whole thing. It is pretty conservative, but it's guaranteed to always be correct both in terms of compilation and visual disambiguation (as long as there aren't more patterns that could be ambiguous such as if/switch expressions).I'd love to hear your opinions on this solution.
Caveats and complications
Although this PR fixes the issue in the most simple case, I have still managed to find some more obscure snippets that have a similar issue (which aren't fixed by this PR). A more complex recursive check would have to be implemented to fix these ambiguities, I discuss this more in the next section.
My instinct is that if the left-most expression is an immediately called closure, parentheses are required, and otherwise they aren't (in terms of compilation). If you add a
!
operator before an ambiguous called closure, the compiler successfully parses the code with or without parentheses.Visual disambiguation parentheses (not required for correct parsing)
Interestingly, the existing check (pre-PR) for cases such as
if (f(1) { false }) { print("hi") }
is purely cosmetic in current Swift (it successfully compiles with or without parentheses around the condition). This begs the question, should parentheses also be left in in any of the following cases where they aren't strictly required for compilation?I believe that to truly fix this issue, we should implement a new (and probably recursive) heuristic for identifying when parens are required; which will require more clearly deciding what we count as ambiguous to humans (not just the compiler). However, these concerns are purely cosmetic and probably a separate issue, it'd be good to merge this fix before doing that because this fix is actually a functional issue not just a matter of preference. See the tldr for my proposed solution.