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
71 changes: 50 additions & 21 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,36 +55,65 @@ function UndefVarError_hint(io::IO, ex::UndefVarError)
else
scope = undef
end
if scope !== Base && !_UndefVarError_warnfor(io, Base, var)
warned = false
for m in Base.loaded_modules_order
m === Core && continue
m === Base && continue
m === Main && continue
m === scope && continue
warned |= _UndefVarError_warnfor(io, m, var)
if scope !== Base
warned = _UndefVarError_warnfor(io, [Base], var)

if !warned
modules_to_check = (m for m in Base.loaded_modules_order
if m !== Core && m !== Base && m !== Main && m !== scope)
warned |= _UndefVarError_warnfor(io, modules_to_check, var)
end
warned ||
_UndefVarError_warnfor(io, Core, var) ||
_UndefVarError_warnfor(io, Main, var)

warned || _UndefVarError_warnfor(io, [Core, Main], var)
end
return nothing
end

function _UndefVarError_warnfor(io::IO, m::Module, var::Symbol)
(Base.isexported(m, var) || Base.ispublic(m, var)) || return false
function _UndefVarError_warnfor(io::IO, modules, var::Symbol)
active_mod = Base.active_module()
print(io, "\nHint: ")
if isdefined(active_mod, Symbol(m))
print(io, "a global variable of this name also exists in $m.")
else
if Symbol(m) == var
print(io, "$m is loaded but not imported in the active module $active_mod.")

warned = false
# collect modules which export or make public the variable by
# the module in which the variable is defined
to_warn_about = Dict{Module, Vector{Module}}()
for m in modules
# only include in info if binding has a value and is exported or public
if !Base.isdefined(m, var) || (!Base.isexported(m, var) && !Base.ispublic(m, var))
continue
end
warned = true

# handle case where the undefined variable is the name of a loaded module
if Symbol(m) == var && !isdefined(active_mod, var)
print(io, "\nHint: $m is loaded but not imported in the active module $active_mod.")
continue
end

binding_m = Base.binding_module(m, var)
if !haskey(to_warn_about, binding_m)
to_warn_about[binding_m] = [m]
else
print(io, "a global variable of this name may be made accessible by importing $m in the current active module $active_mod")
push!(to_warn_about[binding_m], m)
end
end

for (binding_m, modules) in pairs(to_warn_about)
print(io, "\nHint: a global variable of this name also exists in ", binding_m, ".")
for m in modules
m == binding_m && continue
how_available = if Base.isexported(m, var)
"exported by"
elseif Base.ispublic(m, var)
"declared public in"
end
print(io, "\n - Also $how_available $m")
if !isdefined(active_mod, nameof(m)) || (getproperty(active_mod, nameof(m)) !== m)
print(io, " (loaded but not imported in $active_mod)")
end
print(io, ".")
end
end
return true
return warned
end

function __init__()
Expand Down
43 changes: 36 additions & 7 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1833,23 +1833,52 @@ fake_repl() do stdin_write, stdout_read, repl
@test contains(txt, "Some type information was truncated. Use `show(err)` to see complete types.")
end

try # test the functionality of `UndefVarError_hint` against `Base.remove_linenums!`
try # test the functionality of `UndefVarError_hint`
@assert isempty(Base.Experimental._hint_handlers)
Base.Experimental.register_error_hint(REPL.UndefVarError_hint, UndefVarError)

# check the requirement to trigger the hint via `UndefVarError_hint`
@test !isdefined(Main, :remove_linenums!) && Base.ispublic(Base, :remove_linenums!)

fake_repl() do stdin_write, stdout_read, repl
backend = REPL.REPLBackend()
repltask = @async REPL.run_repl(repl; backend)
write(stdin_write,
"remove_linenums!\n\"ZZZZZ\"\n")
write(stdin_write, """
module A53000
export f
f() = 0.0
end

module C_outer_53000
import ..A53000: f
public f

module C_inner_53000
import ..C_outer_53000: f
export f
end
end

module D_53000
public f
f() = 1.0
end

C_inner_53000 = "I'm a decoy with the same name as C_inner_53000!"

append!(Base.loaded_modules_order, [A53000, C_outer_53000, C_outer_53000.C_inner_53000, D_53000])
f
"""
)
write(stdin_write, "\nZZZZZ\n")
txt = readuntil(stdout_read, "ZZZZZ")
write(stdin_write, '\x04')
wait(repltask)
@test occursin("Hint: a global variable of this name also exists in Base.", txt)
@test occursin("Hint: a global variable of this name also exists in Main.A53000.", txt)
@test occursin("Hint: a global variable of this name also exists in Main.D_53000.", txt)
@test occursin("- Also declared public in Main.C_outer_53000.", txt)
@test occursin("- Also exported by Main.C_outer_53000.C_inner_53000 (loaded but not imported in Main).", txt)
end
catch e
# fail test if error
@test false
finally
empty!(Base.Experimental._hint_handlers)
end
Expand Down