Skip to content

Commit

Permalink
move REPL.REPLCompletions.UndefVarError_hint to REPL module (#52990)
Browse files Browse the repository at this point in the history
Since `UndefVarError_hint` is an interactive feature and isn't directly
related to completion, it seems more appropriate to place it within
`REPL` module rather than its `REPLCompletions` submodule.
  • Loading branch information
aviatesk authored Jan 22, 2024
1 parent 9c78420 commit 188cc93
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 74 deletions.
76 changes: 73 additions & 3 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,83 @@ REPL.run_repl(repl)
"""
module REPL

Base.Experimental.@optlevel 1
Base.Experimental.@max_methods 1

function UndefVarError_hint(io::IO, ex::UndefVarError)
var = ex.var
if var === :or
print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.")
elseif var === :and
print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.")
elseif var === :help
println(io)
# Show friendly help message when user types help or help() and help is undefined
show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
elseif var === :quit
print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
end
if isdefined(ex, :scope)
scope = ex.scope
if scope isa Module
bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding
if isdefined(bnd, :owner)
owner = bnd.owner
if owner === bnd
print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.")
end
else
owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), scope, var)
if C_NULL == owner
# No global of this name exists in this module.
# This is the common case, so do not print that information.
print(io, "\nSuggestion: check for spelling errors or missing imports.")
owner = bnd
else
owner = unsafe_pointer_to_objref(owner)::Core.Binding
end
end
if owner !== bnd
# this could use jl_binding_dbgmodule for the exported location in the message too
print(io, "\nSuggestion: this global was defined as `$(owner.globalref)` but not assigned a value.")
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
print(io, "\nSuggestion: check for an assignment to a local variable that shadows a global of the same name.")
end
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)
end
warned ||
_UndefVarError_warnfor(io, Core, var) ||
_UndefVarError_warnfor(io, Main, var)
end
return nothing
end

function _UndefVarError_warnfor(io::IO, m::Module, var::Symbol)
Base.isbindingresolved(m, var) || return false
(Base.isexported(m, var) || Base.ispublic(m, var)) || return false
print(io, "\nHint: a global variable of this name also exists in $m.")
return true
end

function __init__()
Base.REPL_MODULE_REF[] = REPL
Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
return nothing
end

Base.Experimental.@optlevel 1
Base.Experimental.@max_methods 1

using Base.Meta, Sockets
import InteractiveUtils

Expand Down
71 changes: 1 addition & 70 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1473,78 +1473,9 @@ function shell_completions(string, pos)
return Completion[], 0:-1, false
end

function UndefVarError_hint(io::IO, ex::UndefVarError)
var = ex.var
if var === :or
print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.")
elseif var === :and
print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.")
elseif var === :help
println(io)
# Show friendly help message when user types help or help() and help is undefined
show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
elseif var === :quit
print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
end
if isdefined(ex, :scope)
scope = ex.scope
if scope isa Module
bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding
if isdefined(bnd, :owner)
owner = bnd.owner
if owner === bnd
print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.")
end
else
owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), scope, var)
if C_NULL == owner
# No global of this name exists in this module.
# This is the common case, so do not print that information.
print(io, "\nSuggestion: check for spelling errors or missing imports.")
owner = bnd
else
owner = unsafe_pointer_to_objref(owner)::Core.Binding
end
end
if owner !== bnd
# this could use jl_binding_dbgmodule for the exported location in the message too
print(io, "\nSuggestion: this global was defined as `$(owner.globalref)` but not assigned a value.")
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
print(io, "\nSuggestion: check for an assignment to a local variable that shadows a global of the same name.")
end
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)
end
warned ||
_UndefVarError_warnfor(io, Core, var) ||
_UndefVarError_warnfor(io, Main, var)
end
nothing
end

function _UndefVarError_warnfor(io::IO, m::Module, var::Symbol)
Base.isbindingresolved(m, var) || return false
(Base.isexported(m, var) || Base.ispublic(m, var)) || return false
print(io, "\nHint: a global variable of this name also exists in $m.")
return true
end

function __init__()
Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
COMPLETION_WORLD[] = Base.get_world_counter()
nothing
return nothing
end

end # module
2 changes: 1 addition & 1 deletion stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1677,7 +1677,7 @@ end

try # test the functionality of `UndefVarError_hint` against `Base.remove_linenums!`
@assert isempty(Base.Experimental._hint_handlers)
Base.Experimental.register_error_hint(REPL.REPLCompletions.UndefVarError_hint, UndefVarError)
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!)
Expand Down

0 comments on commit 188cc93

Please sign in to comment.