Skip to content

Allow range formation operators without surrounding spaces to wrap safely#1170

Open
TTOzzi wants to merge 1 commit into
swiftlang:mainfrom
TTOzzi:fix-line-breaks-range-operators
Open

Allow range formation operators without surrounding spaces to wrap safely#1170
TTOzzi wants to merge 1 commit into
swiftlang:mainfrom
TTOzzi:fix-line-breaks-range-operators

Conversation

@TTOzzi

@TTOzzi TTOzzi commented Mar 21, 2026

Copy link
Copy Markdown
Member

Resolve #1141

Range operators in parenthesized infix expressions could wrap before the operator when formatted without surrounding spaces, changing parsing by breaking infix interpretation.
This prevents that by skipping leftmost-parenthesized wrapping recursion for operators that do not require surrounding whitespace.

@allevato

Copy link
Copy Markdown
Member

In the test example,

      let x =
        (aaa + bbb)..<(ccc
        + ddd)

I think I would prefer the line break to occur before the ccc instead, but that might be harder to achieve without changing grouping elsewhere and having cascading effects.

Here's an interesting idea that we could try: what if we let the break happen before the range formation operator, but we change the rule to be that if that break occurs (don't respect discretionary breaks here), then we also insert a space after the operator? That would produce much better breaking than the current approach that forces the token to stay glued together. Then the spaces-around-range-formation-operators option would be more of a "best effort", which I think is fine; better formatting should trump rigidly following the option in this case.

I'm not sure if the formatting token model actually supports that kind of conditional logic today, but I think it would be a cleaner way forward.

@TTOzzi TTOzzi force-pushed the fix-line-breaks-range-operators branch from c696f98 to 49b26cc Compare March 24, 2026 17:10
@TTOzzi TTOzzi changed the title Prevent line breaks before range operators in parenthesized infix expressions Allow range formation operators without surrounding spaces to wrap safely Mar 24, 2026
@TTOzzi TTOzzi force-pushed the fix-line-breaks-range-operators branch 3 times, most recently from cb97715 to 5603729 Compare March 24, 2026 17:34
@TTOzzi TTOzzi marked this pull request as draft March 24, 2026 17:37
@TTOzzi TTOzzi force-pushed the fix-line-breaks-range-operators branch from 5603729 to 177ce93 Compare March 24, 2026 17:44
@TTOzzi TTOzzi force-pushed the fix-line-breaks-range-operators branch from 177ce93 to d99be60 Compare March 24, 2026 17:47
@TTOzzi TTOzzi marked this pull request as ready for review March 24, 2026 17:52
@TTOzzi

TTOzzi commented Mar 24, 2026

Copy link
Copy Markdown
Member Author

In the test example,

      let x =
        (aaa + bbb)..<(ccc
        + ddd)

I think I would prefer the line break to occur before the ccc instead, but that might be harder to achieve without changing grouping elsewhere and having cascading effects.

Here's an interesting idea that we could try: what if we let the break happen before the range formation operator, but we change the rule to be that if that break occurs (don't respect discretionary breaks here), then we also insert a space after the operator? That would produce much better breaking than the current approach that forces the token to stay glued together. Then the spaces-around-range-formation-operators option would be more of a "best effort", which I think is fine; better formatting should trump rigidly following the option in this case.

I'm not sure if the formatting token model actually supports that kind of conditional logic today, but I think it would be a cleaner way forward.

Thanks for the suggestion 🙇

I implemented this by extending the spacing model so a space token can carry conditional behavior. That allows range formation operators to remain unspaced when they stay on one line, while still inserting a space after the operator if we wrap before it. I also introduced SpaceCondition so this conditional-space behavior has an explicit model that can be reused if similar cases come up later.

I also added test cases covering the new wrapping behavior and its interaction with existing line break preservation.

@allevato allevato left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is great! I like the way this turned out.

Can you take a look at the case that's failing in sourcekit-lsp? It looks like the continuation break is firing there when we should try to avoid it (because it's fine for the ..< to stick to the surrounding parentheses.

// fires, we preserve the infix parse by inserting a space after the operator.
before(
binOp.firstToken(viewMode: .sourceAccurate),
tokens: .break(.continue, size: 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you make this break ignoresDiscretionary: true so that we don't allow breaking unless it's otherwise required? We don't want to encourage people to break around these operators in situations like this if the margin is much farther out:

for i in 0
  ..< 5
{

Then add a test for a case like this to verify that we collapse the newline and remove the space on the other side.

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.

Line break incorrectly inserted before infix operator when trying to limit line length

2 participants