Skip to content

Do not inline a lambda body whose closing paren carries a comment - #77

Merged
abashev merged 1 commit into
mainfrom
comment-before-lambda-close-parens
Sep 25, 2026
Merged

abashev merged 1 commit into
mainfrom
comment-before-lambda-close-parens

Conversation

@abashev

@abashev abashev commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #62, which mirrors palantir#1792 (the reporter also points at palantir#1152).

The bug

A // comment on its own line before the )) that close a lambda's parenthesized body and the call around it was joined onto the code line before it, and the )) ended up inside the comment:

                        || items[i].getName().equals("gamma")// || (items[i].getName().equals("delta") && items.length >
                                                             // i// && items[i + 1].getName().equals("epsilon"))))

The output no longer parsed. The command line noticed only because its import pass parses the result again, and reported error: ')' expected at a position in the output; a caller of formatSource got the text as it was.

Why

An expression lambda's body is laid out by handle_breakOnlyIfInnerLevelsThenFitOnOneLine. When the body does not fit after -> but its first line does, tryInlinePrefixOntoCurrentLine lays the body's level out "on one line" through tryToLayOutLevelOnOneLine, which marks every break of that level as not taken and only lets the inner levels break. The comments before the closing ) belong to that same level, with forced breaks around them, and a forced break laid out flat is exactly what put the comment on the code line. The docs before the last inner level were checked for that (a forced break makes their width infinite); the docs after it were not checked at all. google-java-format has no such inlining, which is why it lays the input out correctly.

The fix

tryInlinePrefixOntoCurrentLine now refuses a level whose trailing docs have infinite width, the same check tryBreakInnerLevel already makes for its suffix. Such a body is not inlined and breaks normally: it starts on the line after ->, and the comments keep their own lines at the body's indent, which is what the formatter already did when the body's first line did not fit either:

                .filter(i ->
                        (items[i].getName().equals("alpha")
                                || items[i].getName().equals("beta")
                                || items[i].getName().equals("gamma")
                        // || (items[i].getName().equals("delta") && items.length > i
                        // && items[i + 1].getName().equals("epsilon"))
                        ))
                .findFirst()
                .orElse(-1);

A block comment in the same place went through the same path: it came out glued to the code without a space, bbb(i)/* block */));, and moved on a second run. It gets its own line now as well.

Checked

  • The golden ojf-issue-62-comment-before-lambda-close-parens holds the reporter's input, the same shape as a plain call, and the block comment. It fails on main (--skip-* flags show the raw output) and passes with the change, and its output is stable on a second run.
  • Related shapes format exactly as on main: a trailing // comment inside the body, a comment before the single ) of a call, a parenthesized argument that is not a lambda, a short body that already moved after ->, and a statement lambda with a comment before }.
  • ./gradlew :open-java-format:test on JDK 21: 1541 tests, all green.
  • The 15,747 files of the JDK 21 sources format exactly as before; none of them has a comment before )), and the five with a comment before a single ) do not change.

Version

Only input that failed today, or came out glued and unstable, formats differently, so this is a bug fix in the 2.x sense.

A "//" comment on its own line before the "))" that close a lambda's
parenthesized body and the call around it was joined onto the code
line before it, and the "))" ended up inside the comment, so the output
no longer parsed (#62, palantir#1792). The
command line noticed only because its import pass parses the result
again and reported a position in the output; a caller of formatSource
got the text as it was.

An expression lambda's body is laid out by
handle_breakOnlyIfInnerLevelsThenFitOnOneLine: when the body does not
fit after "->" but its first line does, tryInlinePrefixOntoCurrentLine
lays the body's level out "on one line" through
tryToLayOutLevelOnOneLine, which marks every break of that level as
not taken and only lets the inner levels break. The comments before
the closing ")" belong to that same level, with forced breaks around
them, and a forced break laid out flat is what put the comment on the
code line. The width of the docs before the last inner level was
checked, and a forced break there fails that check; the docs after it
were not checked at all.

The check is now made for the trailing docs too, the way
tryBreakInnerLevel already refuses a suffix of infinite width: a body
with a comment before its closing token is not inlined and breaks
normally, so the body starts on the line after "->" and the comments
keep their own lines at the body's indent, as they already did when
the body's first line did not fit. A block comment in the same place
was written without a space in front of it and moved on a second run;
it now gets its own line as well.

The golden holds the reporter's input, the same shape as a plain call,
and the block comment. The 15,747 files of the JDK 21 sources format
exactly as before: none of them has a comment before "))".
@abashev
abashev merged commit 8a39a6a into main Sep 25, 2026
15 checks passed
@abashev
abashev deleted the comment-before-lambda-close-parens branch September 25, 2026 08:55
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.

A line comment before the )) closing a lambda body is joined onto the code line and swallows the parentheses (upstream #1792)

1 participant