Skip to content

Public print typediff#14

Open
adienes wants to merge 2 commits into
masterfrom
public_print_typediff
Open

Public print typediff#14
adienes wants to merge 2 commits into
masterfrom
public_print_typediff

Conversation

@adienes

@adienes adienes commented Jun 7, 2026

Copy link
Copy Markdown
Owner

What

JuliaLang#61651 added machinery to highlight where two types differ — it's
what marks the mismatched arguments in MethodError "Closest candidates" lists.
That machinery is private to errorshow.jl. This PR exposes its entry point as
Base.Experimental.print_type_diff (next to the existing
register_error_hint / show_error_hints error-UX APIs), so custom exception
types can reuse the same highlighting in their own showerror methods.

print_type_diff(io::IO, a::Type, b::Type; highlight)

Prints a, highlighting the subtrees that differ from b. highlight(io, part)
controls how a differing subtree renders, defaulting to red under :color and
!Matched{part} otherwise:

julia> sprint(Base.Experimental.print_type_diff, @NamedTuple{a::Int, b::Char}, @NamedTuple{a::Int, b::String})
"@NamedTuple{a::Int64, b::!Matched{Char}}"

The internal recursion (show_type_diff) and MethodError's use of it are
unchanged except for two additions that make it reusable: a mark callback for
rendering a differing subtree, and an a === b fast path so callers can diff each
element of a signature without pre-checking equality.

Why

Custom errors that compare types currently re-implement this by hand. Motivating
case: Reactant.jl throws
MisMatchedThunkTypeError when a compiled function is called with arguments whose
types differ from what it was compiled for. The two type lists are large and
nearly identical, so the actual mismatch is invisible in the current message.

Because those lists are already Tuple{...} types, the entire hook reduces to two
calls:

function Base.showerror(io::IO, ::MisMatchedThunkTypeError{<:Thunk{F,…,ArgTypes},FoundTypes}) where {}
    print(io, "The Reactant-compiled function `", F, "` was called with argument types ",
              "that don't match the signature it was compiled for.")
    print(io, "\n  Called with:  "); Base.Experimental.print_type_diff(io, FoundTypes, ArgTypes)
    print(io, "\n  Compiled for: "); Base.Experimental.print_type_diff(io, ArgTypes, FoundTypes)
end

In a real Lux/Reactant training loop, where a layer's state gains one extra conv
nesting level per step, the full (~2 KB) argument tuple is printed with every
matching argument dimmed and only the differing subtree highlighted, so the
mismatch is the only thing that stands out. The matching arguments are abbreviated
as ... below for readability (nothing is actually elided — they print in full):

Called with:  Tuple{..., @NamedTuple{layer_1::@NamedTuple{conv::!Matched{@NamedTuple{conv::@NamedTuple{}}}}, layer_2::@NamedTuple{}}, ...}
Compiled for: Tuple{..., @NamedTuple{layer_1::@NamedTuple{conv::!Matched{@NamedTuple{}}}, layer_2::@NamedTuple{}}, ...}

(!Matched{...} is the no-color rendering; under :color that subtree is shown in error_color instead.)


This pull request was written with the assistance of generative AI.

🤖 Generated with Claude Code

@adienes
adienes force-pushed the public_print_typediff branch from 0833cf3 to 862fb1a Compare June 7, 2026 17:42
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.

1 participant