Skip to content
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

Support type redefinition (WIP) #25

Open
wants to merge 1 commit 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
86 changes: 80 additions & 6 deletions src/Revise.jl
Original file line number Diff line number Diff line change
Expand Up @@ -240,22 +240,96 @@ end
# error("definition for module $modsym not found in $s")
# end

const oldbinding_counter = Ref(0)

function eval_revised(revmd::ModDict)
to_delete = RelocatableExpr[]
for (mod, exprs) in revmd
empty!(to_delete)
# Any type (re)definitions must come first
for rex in exprs
ex = convert(Expr, rex)
try
eval(mod, ex)
catch err
warn("failure to evaluate changes in ", mod)
println(STDERR, ex)
if istypedef(rex)
typsym = rex.args[2]
typ = getfield(mod, typsym)
# Get the list of methods that we're going to have to
# re-bind to the new name
meths = invalid_methods(typ)
# Get the list of defining expressions now, while the old name is valid
modrexs = []
for m in meths
if haskey(method2rex, m)
push!(modrexs, method2rex[m])
else
warn("unable to re-bind:\n ", m, "\nmay need to restart")
end
end
# Move the old binding aside
oldbinding_counter[] = counter = oldbinding_counter[] + 1
newname = Symbol(typsym, "#RV#", counter)
Base.rename_binding(mod, typsym, newname)
# Create the new binding
tryeval(mod, rex)
push!(to_delete, rex)
# Re-evaluate those methods so they use the new binding
for (mod, rex) in modrexs
tryeval(mod, rex)
end
end
end
for rex in to_delete
delete!(exprs, rex)
end
# Now the rest
for rex in exprs
tryeval(mod, rex)
end
end
end

function tryeval(mod, ex::Expr)
try
ret = eval(mod, ex)
return true
catch err
warn("failure to evaluate changes in ", mod)
showerror(STDERR, err)
println(STDERR)
println(STDERR, ex)
end
false
end
tryeval(mod, rex::RelocatableExpr) = tryeval(mod, convert(Expr, rex))

istypedef(rex::RelocatableExpr) = rex.head == :type
istypedef(arg) = false

# methods that need to be re-evaluated from source following a type redefinition
function invalid_methods(typ)
mthswith = methodswith(typ)
constructors = methods(typ) # would like to know which are inner constructors and exclude them
callers = Set{Method}()
for c in constructors
isdefined(c, :specializations) || continue
tme = c.specializations
isa(tme, TypeMapEntry) || continue
mi = tme.func
for b in mi.backedges
push!(callers, b.def)
end
end
mths = [mthswith; constructors]
for c in callers
push!(mths, c)
end
mths
end

function reeval(mths::Vector{Method})
end

const file2modules = Dict{String,FileModules}()
const new_files = String[]
const method2rex = Dict{Method,Tuple{Module,RelocatableExpr}}()

function parse_pkg_files(modsym::Symbol)
paths = String[]
Expand Down
77 changes: 77 additions & 0 deletions test/type_redefinition.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Revise, Base.Test

to_remove = String[]
yry() = (sleep(0.1); revise(); sleep(0.1))

testdir = joinpath(tempdir(), randstring(10))
mkdir(testdir)
push!(to_remove, testdir)
push!(LOAD_PATH, testdir)
modname = :MyTypes
dn = joinpath(testdir, String(modname), "src")
mkpath(dn)
common = """
# Constructor methods
MyType(::String) = MyType(0)

# Functions that have the type in their signature
insignature(mt::MyType) = mt.x + 1

# Methods that call the constructor but don't declare it in their inputs
function callsconstructor(x)
mt = MyType(x)
storearg[] = mt # to preserve it for later analysis
return mt.x + 5
end
const storearg = Ref{Any}()

"""
open(joinpath(dn, String(modname)*".jl"), "w") do io
println(io, """
module $modname

export MyType, insignature, callsconstructor

struct MyType
x::Int
end

$common

end
"""
)
end
@eval using $modname
mt = MyType(3)
@test insignature(mt) == 4
@test callsconstructor(20) == 25
@test MyTypes.storearg[] === MyType(20)

# Now redefine the type
sleep(0.1) # to ensure the file-watching has kicked in
open(joinpath(dn, String(modname)*".jl"), "w") do io
println(io, """
module $modname

export MyType, insignature, callsconstructor

struct MyType
x::Int
y::Float64
end

# We need a new constructor method
MyType(x) = MyType(x, -1.8)

$common

end
"""
)
end
yry()
mt = MyType(3, 3.7)
@test insignature(mt) == 4
@test callsconstructor(20) == 25
@test MyTypes.storearg[].y === -1.8