Skip to content

methoddef!: use mt => sig format when filling in signatures #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- run: julia --project -e 'using Pkg; Pkg.add([PackageSpec(; url="https://github.com/serenity4/JuliaInterpreter.jl", rev="codetracking-v2"), PackageSpec(; name = "CodeTracking", rev="master")])'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To be removed before merge.

- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/Documenter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 CodeTracking#master"'
- run: cd docs && julia --project -e 'using Pkg; pkg"add https://github.com/serenity4/JuliaInterpreter.jl#codetracking-v2 CodeTracking#master"'
Comment on lines +14 to +15
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To be removed before merge.

- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-docdeploy@latest
env:
Expand Down
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ version = "3.3.0"
authors = ["Tim Holy <[email protected]>"]

[deps]
CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a"

[compat]
CodeTracking = "2"
JuliaInterpreter = "0.10"
julia = "1.10"

Expand Down
4 changes: 3 additions & 1 deletion src/LoweredCodeUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ module LoweredCodeUtils
# This somewhat unusual structure is in place to support
# the VS Code extension integration.

using CodeTracking: MethodInfoKey

using JuliaInterpreter
using JuliaInterpreter: SSAValue, SlotNumber, Frame, Interpreter, RecursiveInterpreter
using JuliaInterpreter: codelocation, is_global_ref, is_global_ref_egal, is_quotenode_egal, is_return,
lookup, lookup_return, linetable, moduleof, next_until!, nstatements, pc_expr,
step_expr!, whichtt
step_expr!, whichtt, extract_method_table

include("packagedef.jl")

Expand Down
24 changes: 11 additions & 13 deletions src/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,27 +242,25 @@
add_inner!(cl, icl, i)
continue
elseif isexpr(stmt, :method)
if length(stmt.args) === 3 && (arg3 = stmt.args[3]; arg3 isa CodeInfo)
icl = CodeLinks(cl.thismod, arg3)
add_inner!(cl, icl, i)
end
name = stmt.args[1]
if isa(name, GlobalRef) || isa(name, Symbol)
if length(stmt.args) === 1
# A function with no methods was defined. Associate its new binding to it.
name = stmt.args[1]
if isa(name, Symbol)
name = GlobalRef(cl.thismod, name)
end
assign = get(cl.nameassigns, name, nothing)
if assign === nothing
cl.nameassigns[name] = assign = Int[]
if !isa(name, GlobalRef)
@show stmt
error("name ", typeof(name), " not recognized")

Check warning on line 253 in src/codeedges.jl

View check run for this annotation

Codecov / codecov/patch

src/codeedges.jl#L252-L253

Added lines #L252 - L253 were not covered by tests
end
assign = get!(Vector{Int}, cl.nameassigns, name)
push!(assign, i)
targetstore = get!(Links, cl.namepreds, name)
target = P(name, targetstore)
add_links!(target, stmt, cl)
elseif name in (nothing, false)
else
@show stmt
error("name ", typeof(name), " not recognized")
elseif length(stmt.args) === 3 && (arg3 = stmt.args[3]; arg3 isa CodeInfo) # method definition
# A method was defined for an existing function.
icl = CodeLinks(cl.thismod, arg3)
add_inner!(cl, icl, i)
end
rhs = stmt
target = P(SSAValue(i), cl.ssapreds[i])
Expand Down
2 changes: 1 addition & 1 deletion src/packagedef.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Base.Experimental.@optlevel 1

using Core: SimpleVector
using Core: SimpleVector, MethodTable
using Core.IR: CodeInfo, GotoIfNot, GotoNode, IR, MethodInstance, ReturnNode
@static if isdefined(Core.IR, :EnterNode)
using Core.IR: EnterNode
Expand Down
61 changes: 32 additions & 29 deletions src/signatures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@
end

"""
sigt, lastpc = signature([interp::Interpreter=RecursiveInterpreter()], frame::Frame, pc::Int)
(mt, sigt), lastpc = signature([interp::Interpreter=RecursiveInterpreter()], frame::Frame, pc::Int)

Compute the signature-type `sigt` of a method whose definition in `frame` starts at `pc`.
Compute the method table `mt` and signature-type `sigt` of a method whose definition in `frame` starts at `pc`.
Generally, `pc` should point to the `Expr(:method, methname)` statement, in which case
`lastpc` is the final statement number in `frame` that is part of the signature
(i.e, the line above the 3-argument `:method` expression).
Alternatively, `pc` can point to the 3-argument `:method` expression,
as long as all the relevant SSAValues have been assigned.
In this case, `lastpc == pc`.

If no 3-argument `:method` expression is found, `sigt` will be `nothing`.
If no 3-argument `:method` expression is found, `nothing` will be returned in place of `(mt, sigt)`.
"""
function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::Int)
mod = moduleof(frame)
Expand All @@ -52,9 +52,10 @@
stmt = pc_expr(frame, pc)
end
isa(stmt, Expr) || return nothing, pc
mt = extract_method_table(frame, stmt)
sigsv = lookup(interp, frame, stmt.args[2])::SimpleVector
sigt = signature(sigsv)
return sigt, lastpc
return MethodInfoKey(mt, sigt), lastpc
end
signature(interp::Interpreter, frame::Frame, pc::Int) = signature(interp, frame, pc_expr(frame, pc), pc)
signature(frame::Frame, pc::Int) = signature(RecursiveInterpreter(), frame, pc)
Expand Down Expand Up @@ -187,7 +188,9 @@
end
msrc = stmt.args[3]
if msrc isa CodeInfo
key = key::Union{GlobalRef,Bool,Nothing}
# XXX: Properly support interpolated `Core.MethodTable`. This will require using
# `stmt.args[2]` instead of `stmt.args[1]` to identify the parent function.
isa(key, Union{GlobalRef,Bool,Nothing}) || continue
for (j, mstmt) in enumerate(msrc.code)
isa(mstmt, Expr) || continue
jj = j
Expand Down Expand Up @@ -444,9 +447,9 @@
pctop -= 1
stmt = pc_expr(frame, pctop)
end # end fix
sigtparent, lastpcparent = signature(interp, frame, pctop)
(mt, sigtparent), lastpcparent = signature(interp, frame, pctop)
sigtparent === nothing && return name, pc, lastpcparent
methparent = whichtt(sigtparent)
methparent = whichtt(sigtparent, mt)
methparent === nothing && return name, pc, lastpcparent # caller isn't defined, no correction is needed
if isgen
cname = GlobalRef(moduleof(frame), nameof(methparent.generator.gen))
Expand Down Expand Up @@ -515,8 +518,8 @@
"""
ret = methoddef!([interp::Interpreter=RecursiveInterpreter()], signatures, frame; define=true)

Compute the signature of a method definition. `frame.pc` should point to a
`:method` expression. Upon exit, the new signature will be added to `signatures`.
Compute the method table/signature pair of a method definition. `frame.pc` should point to a
`:method` expression. Upon exit, the new method table/signature pair will be added to `signatures`.

There are several possible return values:

Expand All @@ -535,27 +538,27 @@
By default the method will be defined (evaluated). You can prevent this by setting `define=false`.
This is recommended if you are simply extracting signatures from code that has already been evaluated.
"""
function methoddef!(interp::Interpreter, signatures, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true)
function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true)
framecode, pcin = frame.framecode, pc
if ismethod3(stmt)
pc3 = pc
arg1 = stmt.args[1]
sigt, pc = signature(interp, frame, stmt, pc)
meth = whichtt(sigt)
(mt, sigt), pc = signature(interp, frame, stmt, pc)
meth = whichtt(sigt, mt)
if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig)
pc = define ? step_expr!(interp, frame, stmt, true) : next_or_nothing!(interp, frame)
elseif define
pc = step_expr!(interp, frame, stmt, true)
meth = whichtt(sigt)
meth = whichtt(sigt, mt)
end
if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig)
push!(signatures, meth.sig)
push!(signatures, mt => meth.sig)
else
if arg1 === false || arg1 === nothing
if arg1 === false || arg1 === nothing || isa(mt, MethodTable)
# If it's anonymous and not defined, define it
pc = step_expr!(interp, frame, stmt, true)
meth = whichtt(sigt)
isa(meth, Method) && push!(signatures, meth.sig)
meth = whichtt(sigt, mt)
isa(meth, Method) && push!(signatures, mt => meth.sig)
return pc, pc3
else
# guard against busted lookup, e.g., https://github.com/JuliaLang/julia/issues/31112
Expand Down Expand Up @@ -596,7 +599,7 @@
end
found || return nothing
while true # methods containing inner methods may need multiple trips through this loop
sigt, pc = signature(interp, frame, stmt, pc)
(mt, sigt), pc = signature(interp, frame, stmt, pc)
stmt = pc_expr(frame, pc)
while !isexpr(stmt, :method, 3)
pc = next_or_nothing(interp, frame, pc) # this should not check define, we've probably already done this once
Expand All @@ -611,15 +614,15 @@
# signature of the active method. So let's get the active signature.
frame.pc = pc
pc = define ? step_expr!(interp, frame, stmt, true) : next_or_nothing!(interp, frame)
meth = whichtt(sigt)
isa(meth, Method) && push!(signatures, meth.sig) # inner methods are not visible
meth = whichtt(sigt, mt)
isa(meth, Method) && push!(signatures, mt => meth.sig) # inner methods are not visible
name === name3 && return pc, pc3 # if this was an inner method we should keep going
stmt = pc_expr(frame, pc) # there *should* be more statements in this frame
end
end
methoddef!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=true) =
methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) =
methoddef!(interp, signatures, frame, pc_expr(frame, pc), pc; define)
function methoddef!(interp::Interpreter, signatures, frame::Frame; define::Bool=true)
function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true)
pc = frame.pc
stmt = pc_expr(frame, pc)
if !ismethod(stmt)
Expand All @@ -628,27 +631,27 @@
pc === nothing && error("pc at end of frame without finding a method")
methoddef!(interp, signatures, frame, pc; define)
end
methoddef!(signatures, frame::Frame, pc::Int; define::Bool=true) =
methoddef!(signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) =
methoddef!(RecursiveInterpreter(), signatures, frame, pc_expr(frame, pc), pc; define)
methoddef!(signatures, frame::Frame; define::Bool=true) =
methoddef!(signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true) =
methoddef!(RecursiveInterpreter(), signatures, frame; define)

function methoddefs!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=true)
function methoddefs!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true)

Check warning on line 639 in src/signatures.jl

View check run for this annotation

Codecov / codecov/patch

src/signatures.jl#L639

Added line #L639 was not covered by tests
ret = methoddef!(interp, signatures, frame, pc; define)
pc = ret === nothing ? ret : ret[1]
return _methoddefs!(interp, signatures, frame, pc; define)
end
function methoddefs!(interp::Interpreter, signatures, frame::Frame; define::Bool=true)
function methoddefs!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true)
ret = methoddef!(interp, signatures, frame; define)
pc = ret === nothing ? ret : ret[1]
return _methoddefs!(interp, signatures, frame, pc; define)
end
methoddefs!(signatures, frame::Frame, pc::Int; define::Bool=true) =
methoddefs!(signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=true) =

Check warning on line 649 in src/signatures.jl

View check run for this annotation

Codecov / codecov/patch

src/signatures.jl#L649

Added line #L649 was not covered by tests
methoddefs!(RecursiveInterpreter(), signatures, frame, pc; define)
methoddefs!(signatures, frame::Frame; define::Bool=true) =
methoddefs!(signatures::Vector{MethodInfoKey}, frame::Frame; define::Bool=true) =
methoddefs!(RecursiveInterpreter(), signatures, frame; define)

function _methoddefs!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=define)
function _methoddefs!(interp::Interpreter, signatures::Vector{MethodInfoKey}, frame::Frame, pc::Int; define::Bool=define)
while pc !== nothing
stmt = pc_expr(frame, pc)
if !ismethod(stmt)
Expand Down
2 changes: 1 addition & 1 deletion test/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ module ModSelective end
edges = CodeEdges(ModEval, src)
lr = lines_required(GlobalRef(ModEval, :revise538), src, edges)
selective_eval_fromstart!(Frame(ModEval, src), lr, #=istoplevel=#true)
@test isdefined(ModEval, :revise538) && length(methods(ModEval.revise538, (Float32,))) == 1
@test isdefined(ModEval, :revise538) && isempty(methods(ModEval.revise538)) # function is defined, method is not

# https://github.com/timholy/Revise.jl/issues/599
thk = Meta.lower(Main, quote
Expand Down
Loading
Loading