Stop BinaryAST.toString() mutating process-global state just to nag about itself - #640
Merged
Merged
Conversation
…bout itself BinaryAST.toString() returned the node type name, but first called reportUnimplemented(), which added the message to a process-global unsynchronized HashSet and, on first sight of each message, wrote it to System.err. So merely looking at a node - in a debugger, in a log line, in a string concatenation - mutated shared process state and produced output on a stream nobody asked it to write to. The raw `new HashSet()` also grew without bound for the life of the process and was written from any thread that happened to render a node. The returned text is unchanged: both before and after, the method returns nodeType().name(). The nag was pure side effect, and the TODO it encoded is tracked in the type system rather than shouted once per JVM. reportUnimplemented() and ALREADY_DISPLAYED had no other callers; both are removed with their imports.
ggleyzer
reviewed
Sep 25, 2026
ggleyzer
approved these changes
Sep 25, 2026
ggleyzer
left a comment
Collaborator
There was a problem hiding this comment.
Frankly, I don't understand why you even look at it. Spending ten seconds on that is ten seconds too many
Contributor
Author
I simply don't understand how you can't get productivity destroyed by this - aren't you stepping in the debugger and looking at objects at any part of compilation or runtime? This is the thing that forces me to use println debugging instead of just standing with the debugger at a program point, expanding various objects with mouse hover to see what is going on? |
Collaborator
|
I don't know if you noticed, but Cam and I have been looking at objects in the debugger for the last ten years :) |
This was referenced Sep 25, 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.
NOTE: I know that the BAST currently is not supported and that its future is uncertain, but for consistency, and since this is a very simple fix part of the "stop having mutating toStrings because it is impossible to debug stuff" pattern fix. Might as well get it in there since it is simple and done.
One file, +4 −14. First of eight small changes carved out of #569, each one landing independently.
What it does
BinaryAST.toString()returned the node type name, but first calledreportUnimplemented():So merely looking at a node — in a debugger, a log line, a string concatenation — mutated shared
process state and wrote to
System.err. Three separate problems in five lines:ALREADY_DISPLAYEDis process-global, raw (new HashSet(), notnew HashSet<>()), unsynchronized, and written by any thread that happens to render a node. Italso grows without bound for the life of the JVM.
process, so the observable behaviour of
toString()is a function of history.This removes the nag.
reportUnimplemented()andALREADY_DISPLAYEDhad no other callers, so bothgo with their imports.
Why it is safe
The returned text is unchanged by construction, not merely by measurement. Both before and
after, the method body ends
return nodeType().name();. The only removal is the side effect. Thereis no path by which a caller's string can differ.
I also verified it empirically alongside the other seven slices: rendering all 83,977 constants of
container zero produces a byte-identical digest to master.
Why this matters beyond tidiness
It's the cheapest instance of a pattern worth removing across the display methods:
toString()thatdoes something other than produce a string. The expensive instance is
#639, where rendering a type interns 130 constants into
the pool via
isA(). This one is the same class of bug with none of the difficulty, which is whyit's first.
Verification
On
76e5507a6::javatools:test414 tests / 0 failures / 0 errors;:javatools_utils:test120 / 0 / 0.CI=true ./gradlew spotlessCheckclean.