Stop rendering a type from collapsing an unresolved constant - #643
Merged
Merged
Conversation
ggleyzer
requested changes
Sep 25, 2026
TerminalTypeConstant.getValueString() identified the underlying constant with
ensureResolvedConstant(), which resolves and then stores the result:
m_constId = constId = resolved;
Resolution itself is a pure read - Constant.resolve() delegates to ResolvableConstant.unwrap(),
which walks the resolution chain and returns its end without writing anything. So rendering a type,
which happens implicitly from a log line or a debugger's Variables view, was collapsing an
unresolved constant mid-compilation purely to cache an answer the display path does not need.
Calling resolve() directly is sufficient and cannot return null. There are three implementations:
Constant.resolve() returns this, and both UnresolvedTypeConstant and UnresolvedNameConstant delegate
to ResolvableConstant.unwrap(), which returns either this or a non-null link from the resolution
chain. The null checks in ensureResolvedConstant() and Annotation.getAnnotationClass() are therefore
dead, and are not worth propagating to a new call site.
Verified by rendering every constant in all 24 compiled modules under master's javatools and this
branch's - 130,140 constants, 6,515 of them TerminalTypeConstants, and the rest covering the nested
case where a terminal type is rendered inside a parameterized or signature constant - byte for byte
identical. 1,704 of those renderings throw in both versions, because the harness renders from an
unlinked FileStructure rather than a live container; the failing set is identical line for line.
lagergren
force-pushed
the
lagergren/569-4-terminaltype
branch
from
September 25, 2026 19:56
048b471 to
621a36e
Compare
ggleyzer
approved these changes
Sep 25, 2026
This was referenced Sep 26, 2026
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.
One file, +7 −1. Continues the small display-purity changes carved out of #569 (#640, #641, #642).
The fix
TerminalTypeConstant.getValueString()identified the underlying constant throughensureResolvedConstant(), which resolves and then stores the result:Resolution itself is already a pure read —
Constant.resolve()delegates toResolvableConstant.unwrap(), which walks the resolution chain and returns its end without writinganything. So rendering a type — which happens implicitly, from a log line or a debugger's Variables
view — was collapsing an unresolved constant mid-compilation purely to cache an answer the display
path has no need to keep.
The whole change:
The write-back stays where the non-display callers can still have it.
Why it is safe
By construction:
resolve()cannot return null. There are exactly three implementations —Constant.resolve()returnsthis, andUnresolvedTypeConstantandUnresolvedNameConstantbothdelegate to the single
ResolvableConstant.unwrap(), which returns eitherthisor a non-null linkfrom the resolution chain. So calling
resolve()directly returns whatensureResolvedConstant()returns, minus the store. (The null checks in
ensureResolvedConstant()andAnnotation.getAnnotationClass()are consequently dead.)By measurement: every constant in all 24 compiled modules, rendered under master's
javatoolsand this branch's, then diffed:
The other ~123k lines matter as much as the 6,515: they cover the nested case, where a terminal type
is rendered inside a
ParameterizedTypeConstant, aSignatureConstantor aMethodConstant.1,704 of those renderings throw in both versions, and the failing set is identical line for line.
That is an artifact of the harness rendering from a bare
FileStructurerather than a live container,so class structures are not available. Worth noting because 743 of them are
ParameterizedTypeConstant— which is #639 showing itsother face: the
isA()-based decision about whether a type can be spelledfunction R(P)doesn'tonly mutate the pool, it also throws when the type system isn't fully linked.
On StringBuilder
Nothing to do here — this method is a single
return, andTerminalTypeConstant.javacontains noStringBuilderat all. Flagging it because the previous PRs in this series did convert theirs.Verification
On
8f80ad6ed::javatools:test414 / 0 failures / 0 errors;:javatools_utils:test120 / 0 / 0.CI=true ./gradlew spotlessCheckclean.