diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38f7434d..50eb82d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: - '1.6' # LTS - '1.9' # parsing errors branch on 1.10, so test the last pre-1.10 version - '1' # current stable + - 'pre' # next release, if available os: - ubuntu-latest - macOS-latest diff --git a/Project.toml b/Project.toml index fac557cd..010d9dd1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Revise" uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" -version = "3.5.15" +version = "3.5.16" [deps] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" @@ -19,7 +19,7 @@ Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [compat] CodeTracking = "1.2" JuliaInterpreter = "0.9" -LoweredCodeUtils = "2.3" +LoweredCodeUtils = "3" OrderedCollections = "1" # Exclude Requires-1.1.0 - see https://github.com/JuliaPackaging/Requires.jl/issues/94 Requires = "~1.0, ^1.1.1" diff --git a/src/lowered.jl b/src/lowered.jl index 799c3989..97546915 100644 --- a/src/lowered.jl +++ b/src/lowered.jl @@ -85,10 +85,10 @@ Since the contents of such expression are difficult to analyze, it is generally safest to execute all such evals. """ function minimal_evaluation!(@nospecialize(predicate), methodinfo, mod::Module, src::Core.CodeInfo, mode::Symbol) - edges = CodeEdges(src) + edges = CodeEdges(mod, src) # LoweredCodeUtils.print_with_code(stdout, src, edges) isrequired = fill(false, length(src.code)) - namedconstassigned = Dict{Symbol,Bool}() + namedconstassigned = Dict{GlobalRef,Bool}() evalassign = false for (i, stmt) in enumerate(src.code) if !isrequired[i] @@ -99,18 +99,24 @@ function minimal_evaluation!(@nospecialize(predicate), methodinfo, mod::Module, end end if isexpr(stmt, :const) - name = stmt.args[1]::Symbol - namedconstassigned[name] = false - elseif isexpr(stmt, :(=)) + name = stmt.args[1] + if isa(name, Symbol) + name = GlobalRef(mod, name) + end + namedconstassigned[name::GlobalRef] = false + elseif LoweredCodeUtils.is_assignment_like(stmt) lhs = (stmt::Expr).args[1] if isa(lhs, Symbol) + lhs = GlobalRef(mod, lhs) + end + if isa(lhs, GlobalRef) if haskey(namedconstassigned, lhs) namedconstassigned[lhs] = true end end if mode === :evalassign evalassign = isrequired[i] = true - if isa(lhs, Symbol) + if isa(lhs, GlobalRef) isrequired[edges.byname[lhs].succs] .= true # mark any `const` statements or other "uses" in this block end end @@ -119,7 +125,7 @@ function minimal_evaluation!(@nospecialize(predicate), methodinfo, mod::Module, if mode === :sigs for (name, isassigned) in namedconstassigned isassigned || continue - if isdefined(mod, name) + if isdefined(name.mod, name.name) empty!(edges.byname[name].succs) # avoid redefining `consts` in `:sigs` mode (fixes #789) end end @@ -225,7 +231,7 @@ function methods_by_execution!(@nospecialize(recurse), methodinfo, docexprs, mod Core.eval(mod, ex) catch err (always_rethrow || isa(err, InterruptException)) && rethrow(err) - loc = location_string(whereis(frame)...) + loc = location_string(whereis(frame)) bt = trim_toplevel!(catch_backtrace()) throw(ReviseEvalException(loc, err, Any[(sf, 1) for sf in stacktrace(bt)])) end @@ -248,7 +254,7 @@ function methods_by_execution!(@nospecialize(recurse), methodinfo, docexprs, mod methods_by_execution!(recurse, methodinfo, docexprs, frame, isrequired; mode=mode, kwargs...) catch err (always_rethrow || isa(err, InterruptException)) && (disablebp && foreach(enable, active_bp_refs); rethrow(err)) - loc = location_string(whereis(frame)...) + loc = location_string(whereis(frame)) sfs = [] # crafted for interaction with Base.show_backtrace frame = JuliaInterpreter.leaf(frame) while frame !== nothing @@ -277,7 +283,7 @@ function methods_by_execution!(@nospecialize(recurse), methodinfo, docexprs, fra while true JuliaInterpreter.is_leaf(frame) || (@warn("not a leaf"); break) stmt = pc_expr(frame, pc) - if !isrequired[pc] && mode !== :eval && !(mode === :evalassign && isexpr(stmt, :(=))) + if !isrequired[pc] && mode !== :eval && !(mode === :evalassign && LoweredCodeUtils.is_assignment_like(stmt)) pc = next_or_nothing!(frame) pc === nothing && break continue @@ -309,10 +315,13 @@ function methods_by_execution!(@nospecialize(recurse), methodinfo, docexprs, fra # However, it might have been followed by a thunk that defined a # method (issue #435), so we still need to check for additions. if !isempty(signatures) - file, line = whereis(frame.framecode, pc) - lnn = LineNumberNode(Int(line), Symbol(file)) - for sig in signatures - add_signature!(methodinfo, sig, lnn) + loc = whereis(frame.framecode, pc) + if loc !== nothing + file, line = loc + lnn = LineNumberNode(Int(line), Symbol(file)) + for sig in signatures + add_signature!(methodinfo, sig, lnn) + end end end pc = next_or_nothing!(frame) @@ -400,7 +409,7 @@ function methods_by_execution!(@nospecialize(recurse), methodinfo, docexprs, fra end end end - elseif head === :(=) + elseif LoweredCodeUtils.is_assignment_like(stmt) # If we're here, either isrequired[pc] is true, or the mode forces us to eval assignments pc = step_expr!(recurse, frame, stmt, true) elseif head === :call diff --git a/src/parsing.jl b/src/parsing.jl index 313bdf1b..8a8175ea 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -58,7 +58,7 @@ function process_source!(mod_exprs_sigs::ModuleExprsSigs, ex, filename, mod::Mod catch err bt = trim_toplevel!(catch_backtrace()) lnn = firstline(ex) - loc = location_string(lnn.file, lnn.line) + loc = location_string((lnn.file, lnn.line)) throw(ReviseEvalException(loc, err, Any[(sf, 1) for sf in stacktrace(bt)])) end end diff --git a/src/utils.jl b/src/utils.jl index 6767ba10..4d65d81b 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -59,8 +59,9 @@ firstline(rex::RelocatableExpr) = firstline(rex.ex) newloc(methloc::LineNumberNode, ln, lno) = fixpath(ln) -location_string(file::AbstractString, line) = abspath(file)*':'*string(line) -location_string(file::Symbol, line) = location_string(string(file), line) +location_string((file, line)::Tuple{AbstractString, Any},) = abspath(file)*':'*string(line) +location_string((file, line)::Tuple{Symbol, Any},) = location_string((string(file), line)) +location_string(::Nothing) = "unknown location" function linediff(la::LineNumberNode, lb::LineNumberNode) (isa(la.file, Symbol) && isa(lb.file, Symbol) && (la.file::Symbol === lb.file::Symbol)) || return typemax(Int)