Skip to content

Make verify trim printing more readable (also fix the colours)#60350

Merged
DilumAluthge merged 14 commits intomasterfrom
gb/fancy-printing
Jan 20, 2026
Merged

Make verify trim printing more readable (also fix the colours)#60350
DilumAluthge merged 14 commits intomasterfrom
gb/fancy-printing

Conversation

@gbaraldi
Copy link
Member

@gbaraldi gbaraldi commented Dec 9, 2025

pr:
image

master:
image

Inspired by @jakobnissen's blog https://viralinstruction.com/posts/aoc2025/

Written with AI help

@topolarity topolarity self-requested a review December 9, 2025 23:41
Copy link
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a solid improvement to me. Thanks @gbaraldi !

if newstmt.head ∈ (:quote, :inert)
return newstmt
# Check if a union is small with concrete types (same logic as code_warntype's is_expected_union)
function is_expected_union(@nospecialize(u::Union))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of duplicating, we should move these into the Compiler printing utilities and have InteractiveUtils pull them in from there?

end

# Print with light_black if stable, normal otherwise
function print_styled(io::IO, @nospecialize(x), stable::Bool)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function print_styled(io::IO, @nospecialize(x), stable::Bool)
function print_nontype(io::IO, @nospecialize(x), stable::Bool)

Not a great name, but better than print_styled and printstyled floating around in the same function

# Convert Core.Argument to slot name symbol if available
function argument_name(codeinfo::CodeInfo, arg::Core.Argument)
slotnames = codeinfo.slotnames
if slotnames !== nothing && arg.n <= length(slotnames)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IR verifier seems to suggest that codeinfo.slotnames is always populated and the right length - Is that not true?

Copy link
Member Author

@gbaraldi gbaraldi Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you see that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most uses I see do check it.

if codeinfo.slotnames !== nothing
io = IOContext(io, :SOURCE_SLOTNAMES => sourceinfo_slotnames(codeinfo))
# Colors for matching parentheses at different nesting levels
const PAREN_COLORS = (:blue, :yellow, :magenta, :cyan, :green)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not a fan of multi-colored parentheses personally

They really overload the maximum number of visual items I can track at a time. Do you think they're very helpful?

Comment on lines +527 to +531
if warn
printstyled(io, "Verifier warning #", no, ": "; color=Base.warn_color(), bold=true)
else
printstyled(io, "Verifier error #", no, ": "; color=Base.error_color(), bold=true)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if warn
printstyled(io, "Verifier warning #", no, ": "; color=Base.warn_color(), bold=true)
else
printstyled(io, "Verifier error #", no, ": "; color=Base.error_color(), bold=true)
end
printstyled(io, "Problem #", no, ": "; color=Base.warn_color(), bold=true)

The index is shared but these are counted separately, so changing between "error" / "warning" seems a bit confusing to me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with just error here. I don't think the user deciding to ignore errors means we should print them as warnings

Copy link
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you're missing a repr when printing constant values:

Error #1: unresolved call from statement Base.print(
  Base.stdout::IO,
  x::String,

::String # this is "\n"::String
)::Nothing

ran into this with:

function @main(ARGS)
    println("hello!")
end

This can also end up with some pretty complex output at times:

Main.SimpleLib.unsafe_wrap(
  Main.SimpleLib.Array::Type{Array},
  Base.getproperty(
    Base.getproperty(
      Main.SimpleLib.tree::Any,
      ::Symbol
    )::Any,
    :data::Symbol
  )::Any,
  Base.getproperty(
    Base.getproperty(
      Main.SimpleLib.tree::Any,
      ::Symbol
    )::Any,
    :length::Symbol
  )::Any
)::Array{T, N} where {N, T}

I'm not sure unwrapping that many calls is needed? It seems that beyond 1 or 2 you are likely to want to dive into the IR instead.

Oh and the usage of "…" reminds me of Varargs, but that's not what it means. It would be good to make that clearer somehow.

function argument_name(codeinfo::CodeInfo, arg::Core.Argument)
slotnames = codeinfo.slotnames
if slotnames !== nothing && arg.n <= length(slotnames)
return slotnames[arg.n]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

odd indentation

Copy link
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking pretty good now!

My only remaining wishlist is to translate intrinsics back to user-familiar syntax, such as:
- getproperty(x::T, :field)::FieldT -> (x::T).field::FieldT
- Base.memoryrefget((…)::MemoryRef{T}, (…)::Symbol, (…)::Bool) -> (...)[]::T
- etc.

I'm sure there will be plenty of details to work out w.r.t. when we can do that faithfully without mis-leading the user and exactly how we want to translate it all, so probably best left to a separate PR anyway.

Very minor, but I also think it'd be nice to have an extra newline before the stacktrace, like:

Error #1: unresolved call from statement Main.SimpleLib.println(
  Main.SimpleLib.stderr::IO,
  "bad time"::String,
  Base.memoryrefget(()::MemoryRef{Main.SimpleLib.CTree{Float64}}, ()::Symbol, ()::Bool)::Main.SimpleLib.CTree{Float64}
)::Any

Stacktrace:
 [1] size(tree::Main.SimpleLib.CTree{Float64})
   @ Main.SimpleLib ~/repos/julia/test/trimming/libsimple.jl:9
 [2] size(tree::Main.SimpleLib.CTree{Float64})
   @ Main.SimpleLib ~/repos/julia/test/trimming/libsimple.jl:11

Other than that, this looks ready to go to me. Thanks again @gbaraldi !

# Use repr for strings/chars to show quotes
function print_nontype(io::IO, @nospecialize(x), stable::Bool)
stable ? printstyled(io, x; color=:light_black) : print(io, x)
txt = x isa Union{AbstractString, AbstractChar} ? Base.repr(x) : x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to always use repr?

@topolarity
Copy link
Member

Actually hold on.. This seems to still have pretty aggressive nesting somehow:

Base.prod(
  Base.getfield(
    Main.SimpleLib.unsafe_wrap(
      ()::Type{Array},
      Base.getproperty(
        Base.getproperty(
          Main.SimpleLib.tree::Any,
          ()::Symbol
        )::Any,
        ()::Symbol
      )::Any,
      Base.getproperty(
        Base.getproperty(
          Main.SimpleLib.tree::Any,
          ()::Symbol
        )::Any,
        ()::Symbol
      )::Any
    )::Array{T, N} where {N, T},
    ()::Symbol
  )::NTuple{N, Int64} where N
)::Int64

Is the limit not being applied?

This would almost be readable if we translated the intrinsics back to syntax, but as-is it's pretty overwhelming

@topolarity
Copy link
Member

Also the (…)::Symbol is hiding some pretty crucial information.. That's a really nice identifier to have.

Can we avoid abbreviating "small" constant values in that position?

@gbaraldi gbaraldi added the merge me PR is reviewed. Merge when all tests are passing label Jan 19, 2026
@gbaraldi
Copy link
Member Author

We may want to port this to 1.13. and maybe even 1.12 (it's technically a bug fix of #60412

@DilumAluthge DilumAluthge merged commit 2d9a3f8 into master Jan 20, 2026
9 checks passed
@DilumAluthge DilumAluthge deleted the gb/fancy-printing branch January 20, 2026 00:14
@DilumAluthge DilumAluthge removed the merge me PR is reviewed. Merge when all tests are passing label Jan 20, 2026
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.

Severe memory consumption (20 GB+) when printing certain --trim errors

3 participants