Measure the whole type of a cast before inlining a chain onto it - #78
Merged
Merged
Conversation
In sun.rmi.transport.tcp.TCPTransport the cast (PrivilegedAction<String>) came out cut between "<" and "String", with the lambda it applies to on a third line, while the call around it stayed on one line (#60): static final Log tcpLog = Log.getLog( "sun.rmi.transport.tcp", "tcp", LogStream.parseLevel(AccessController.doPrivileged((PrivilegedAction< String>) () -> System.getProperty("sun.rmi.transport.tcp.logLevel")))); The arguments of getLog are inlined when a prefix of the last one fits, and CountWidthUntilBreakVisitor measures that prefix up to the first break inside the last level. visitTypeCast declares its level ACCEPT_INLINE_CHAIN, which means that first break is the one before the operand. The first break was the one visitParameterizedType emits after "<", so the prefix ended at "(PrivilegedAction<", fit, and the type had to take the break because "String>)" did not fit behind it. A wildcard bound, an intersection type and an annotated type offer breaks of their own in the same place and split the same way. The cast's level now has PartialInlineability.IF_FIRST_LEVEL_FITS, what visitDotWithPrefix uses for the same purpose: the visitor counts the whole first inner level, which is the rest of the type up to the break before the operand, when it fits. A chain is inlined onto a cast only if the type fits behind it. Here it does not, the chain is not inlined, and the arguments break one per line, as they do when the type is not parameterized. The type stays breakable for a cast that does not fit on a line at all, so the golden C keeps its output. The golden holds the statement above, the same cast one call shallower, which fit and does not change, and a method reference, two type arguments, a wildcard bound, a qualified name, an intersection type and an annotated type in the cast. Of the 15,747 files of the JDK 21 sources, that one statement in TCPTransport is the only one that formats differently, and its new output is stable.
The input was the wanted output with the arguments joined onto one line, so the golden checked little besides the chain. Each statement now arrives in a different shape: the first one as the broken output the fix removes, the others on one line, or split at the "::", inside the type arguments, after "&" or before the lambda, with stray spaces around "<", ",", "::", "->" and "[ ]". The expected output does not change, and main still formats the input differently.
abashev
added a commit
to openjavaformat/docs
that referenced
this pull request
Sep 25, 2026
openjavaformat/open-java-format#78 stops splitting a cast to a parameterized type inside its type when an inlined call chain has no room for the type; the call breaks its arguments one per line instead. It is a bug fix that changes the output of one shape, which the 2.x line allows, and Migrate lists every such fix. The two fixes of 2.98.0.3 become a list, the cast fix joins it marked as not yet released, and the corpus check says what it changes: one statement of the JDK 21 sources, in sun.rmi.transport.tcp.TCPTransport.
abashev
added a commit
to openjavaformat/docs
that referenced
this pull request
Sep 25, 2026
2.98.0.4 is out on GitHub, Maven Central and the Gradle Plugin Portal, so the pages now install it. It carries the three output changes that Migrate listed as not yet in a release: the cast fix (openjavaformat/open-java-format#78), the switch expression on the declaration's line (openjavaformat/open-java-format#76) and the /*** banner that no longer counts as javadoc (openjavaformat/open-java-format#74). Each now says it applies since 2.98.0.4, and the corpus paragraph no longer calls them unreleased. The GitHub Action page keeps 2.98.0.3 as the default of the action's version input, because the action itself still defaults to it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes #60.
The bug
In
sun.rmi.transport.tcp.TCPTransportthe cast(PrivilegedAction<String>)came out cut between<andString, with the lambda it applies to on a third line, while the call around it stayed on one line:Why
The arguments of
getLogare inlined when a prefix of the last one fits, andCountWidthUntilBreakVisitormeasures that prefix up to the first break inside the last level.visitTypeCastdeclares its levelACCEPT_INLINE_CHAIN, which means that first break is the one before the operand. The first break was the onevisitParameterizedTypeemits after<, so the prefix ended at(PrivilegedAction<, fit, and the type then had to take that break becauseString>)did not fit behind it. A wildcard bound (? extends List<Integer>), an intersection type (Runnable & Serializable) and an annotated type (@Nullable String) offer breaks of their own in the same place and split the same way.The fix
The cast's level gets
PartialInlineability.IF_FIRST_LEVEL_FITS, whatvisitDotWithPrefixuses for the same purpose: the visitor counts the whole first inner level, which is the rest of the type up to the break before the operand, when it fits. A chain is inlined onto a cast only if the type fits behind it. Here it does not, the chain is not inlined, and the arguments break one per line, as they do today when the type is not parameterized:The type stays breakable for a cast that does not fit on a line at all: laying the type out without breaks, the first option in #60, gave the same result here but turned the 231-character cast of the golden
Cinto a 254-column line, so it was dropped.Checked
ojf-issue-60-cast-type-in-inline-chainholds the statement above, the same cast one call shallower (which fit and does not change), and a method reference, two type arguments, a wildcard bound, a qualified name, an intersection type and an annotated type in the cast. It fails on main and passes with the change, and its output is stable on a second run../gradlew :open-java-format:teston JDK 21: 1545 tests, all green, the goldensCandB23749160among them.TCPTransportis the only one that formats differently, as A cast breaks inside its type when the call around it stays on one line #60 predicted; the 203 lambdas and 17 method references cast to a generic type elsewhere in the JDK are unchanged.Version
A bug fix that changes the output of one shape, which the README allows in 2.x. The Migrate page on the website lists such fixes and should get a line for this one with the release that carries it.