Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1154,22 +1154,42 @@ function UndefVarError_hint(io::IO, ex::UndefVarError)
if isdefined(ex, :scope)
scope = ex.scope
if scope isa Module
bpart = Base.lookup_binding_partition(ex.world, GlobalRef(scope, var))
kind = Base.binding_kind(bpart)
if kind === Base.PARTITION_KIND_GLOBAL || kind === Base.PARTITION_KIND_UNDEF_CONST || kind == Base.PARTITION_KIND_DECLARED
bpart = lookup_binding_partition(ex.world, GlobalRef(scope, var))
kind = binding_kind(bpart)

# Get the current world's binding partition for comparison
curworld = tls_world_age()
cur_bpart = lookup_binding_partition(curworld, GlobalRef(scope, var))
Copy link
Member

@vtjnash vtjnash May 30, 2025

Choose a reason for hiding this comment

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

The printing world doesn't really have anything in common with the error world, especially in the REPL. Can Claud add the field to UndefVarError?

Copy link
Member Author

Choose a reason for hiding this comment

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

UndefVarError already has the field.

Copy link
Member Author

Choose a reason for hiding this comment

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

(See 5 lines prior)

Copy link
Member Author

Choose a reason for hiding this comment

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

The current world is just for the extra warning if the current kind is different from the old kind.

Copy link
Member

@vtjnash vtjnash May 30, 2025

Choose a reason for hiding this comment

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

Ah, great. I didn't realize we'd already done that. I assumed that Claude was just hallucinating it based on MethodError. I think we want get_world_counter() here though, not tls_world_age().

julia/base/errorshow.jl

Lines 347 to 349 in 44c3813

elseif hasmethod(f, arg_types) && !hasmethod(f, arg_types, world=ex.world)
curworld = get_world_counter()
print(io, "\nThe applicable method may be too new: running in world age $(ex.world), while current world is $(curworld).")

Copy link
Member Author

Choose a reason for hiding this comment

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

Claude thought so too, but I told it not to. I want to change the MethodError also, because otherwise there can be a TOCTOU issue with the hasmethod and the printing using different worlds. I think that's just confusing.

Copy link
Member

Choose a reason for hiding this comment

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

I thought hasmethod is a tls_world query these days to prevent the TOCTOU issue (looks like that is used incorrectly above). The methods listing query though is a latest world query, so it should try to be consistent with 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.

Will make a separate PR to discuss that, but in any case, I think this should be tls_world_age

cur_kind = binding_kind(cur_bpart)

# Track if we printed the "too new" message
printed_too_new = false

# Check if the binding exists in the current world but was undefined in the error's world
if kind === PARTITION_KIND_GUARD
if isdefinedglobal(scope, var)
print(io, "\nThe binding may be too new: running in world age $(ex.world), while current world is $(curworld).")
printed_too_new = true
else
print(io, "\nSuggestion: check for spelling errors or missing imports.")
end
elseif kind === PARTITION_KIND_GLOBAL || kind === PARTITION_KIND_UNDEF_CONST || kind == PARTITION_KIND_DECLARED
print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.")
elseif kind === Base.PARTITION_KIND_FAILED
elseif kind === PARTITION_KIND_FAILED
print(io, "\nHint: It looks like two or more modules export different ",
"bindings with this name, resulting in ambiguity. Try explicitly ",
"importing it from a particular module, or qualifying the name ",
"with the module it should come from.")
elseif kind === Base.PARTITION_KIND_GUARD
print(io, "\nSuggestion: check for spelling errors or missing imports.")
elseif Base.is_some_explicit_imported(kind)
print(io, "\nSuggestion: this global was defined as `$(Base.partition_restriction(bpart).globalref)` but not assigned a value.")
elseif kind === Base.PARTITION_KIND_BACKDATED_CONST
elseif is_some_explicit_imported(kind)
print(io, "\nSuggestion: this global was defined as `$(partition_restriction(bpart).globalref)` but not assigned a value.")
elseif kind === PARTITION_KIND_BACKDATED_CONST
print(io, "\nSuggestion: define the const at top-level before running function that uses it (stricter Julia v1.12+ rule).")
end

# Check if binding kind changed between the error's world and current world
if !printed_too_new && kind !== cur_kind
print(io, "\nNote: the binding state changed since the error occurred (was: $(kind), now: $(cur_kind)).")
end
elseif scope === :static_parameter
print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.")
elseif scope === :local
Expand Down
18 changes: 18 additions & 0 deletions test/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,24 @@ end
@test_throws expected_message X.x
end

# Module for UndefVarError world age testing
module TestWorldAgeUndef end

@testset "UndefVarError world age hint" begin
ex = try
TestWorldAgeUndef.newvar
catch e
e
end
@test ex isa UndefVarError

Core.eval(TestWorldAgeUndef, :(newvar = 42))

err_str = sprint(Base.showerror, ex)
@test occursin("The binding may be too new: running in world age", err_str)
@test occursin("while current world is", err_str)
end

# test showing MethodError with type argument
struct NoMethodsDefinedHere; end
let buf = IOBuffer()
Expand Down