Skip to content

Stop rendering a type from collapsing an unresolved constant - #643

Merged
lagergren merged 1 commit into
masterfrom
lagergren/569-4-terminaltype
Sep 25, 2026
Merged

lagergren merged 1 commit into
masterfrom
lagergren/569-4-terminaltype

Conversation

@lagergren

@lagergren lagergren commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

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 through
ensureResolvedConstant(), which resolves and then stores the result:

m_constId = constId = resolved;

Resolution itself is already 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 has no need to keep.

The whole change:

-   return ensureResolvedConstant().getValueString();
+   // not ensureResolvedConstant(): that stores the resolution back into m_constId
+   return m_constId.resolve().getValueString();

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() returns this, and UnresolvedTypeConstant and UnresolvedNameConstant both
delegate to the single ResolvableConstant.unwrap(), which returns either this or a non-null link
from the resolution chain. So calling resolve() directly returns what ensureResolvedConstant()
returns, minus the store. (The null checks in ensureResolvedConstant() and
Annotation.getAnnotationClass() are consequently dead.)

By measurement: every constant in all 24 compiled modules, rendered under master's javatools
and this branch's, then diffed:

24 modules, 130,140 constants
  6,515 TerminalTypeConstant rendered directly
byte-for-byte identical

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, a SignatureConstant or a MethodConstant.

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 FileStructure rather than a live container,
so class structures are not available. Worth noting because 743 of them are
ParameterizedTypeConstant — which is #639 showing its
other face: the isA()-based decision about whether a type can be spelled function R(P) doesn't
only 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, and TerminalTypeConstant.java contains no
StringBuilder at all. Flagging it because the previous PRs in this series did convert theirs.

Verification

On 8f80ad6ed:

  • :javatools:test 414 / 0 failures / 0 errors; :javatools_utils:test 120 / 0 / 0.
  • CI=true ./gradlew spotlessCheck clean.
  • The 130,140-constant diff above.

Comment thread javatools/src/main/java/org/xvm/asm/constants/TerminalTypeConstant.java Outdated
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
lagergren force-pushed the lagergren/569-4-terminaltype branch from 048b471 to 621a36e Compare September 25, 2026 19:56
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.

2 participants