Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
Base.run_main_repl(true, true, false, true, false))
isdefined(Base, :errormonitor) && Base.errormonitor(t)
while (!isdefined(Base, :active_repl_backend) || isnothing(Base.active_repl_backend)) sleep(0.1) end
pushfirst!(Base.active_repl_backend.ast_transforms, Revise.revise_first)
pushfirst!(Base.active_repl_backend.ast_transforms, Revise.revise_first_scan_last)
include(joinpath("test", "runtests.jl"))
if Base.VERSION.major == 1 && Base.VERSION.minor >= 9
REPL.eval_user_input(:(exit()), Base.active_repl_backend, Main)
Expand Down
58 changes: 43 additions & 15 deletions src/packagedef.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1483,12 +1483,9 @@ function maybe_set_prompt_color(color)
return nothing
end

# `revise_first` gets called by the REPL prior to executing the next command (by having been pushed
# `revise_first_scan_last` gets called by the REPL prior to executing the next command (by having been pushed
# onto the `ast_transform` list).
# This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime.
# This allows `revise_first` to be compiled without compiling `revise` itself, and greatly
# reduces the overhead of using Revise.
function revise_first(ex)
function revise_first_scan_last(ex)
# Special-case `exit()` (issue #562)
if isa(ex, Expr)
exu = unwrap(ex)
Expand Down Expand Up @@ -1517,8 +1514,28 @@ function revise_first(ex)
end
end
end
# Check for queued revisions, and if so call `revise` first before executing the expression
return Expr(:toplevel, :($isempty($revision_queue) || $(Base.invokelatest)($revise)), ex)
return revise_first_scan_last_expr(ex)
end

function revise_first_scan_last_expr(ex)
# Modify the user-supplied expression `ex` to:
# 1. Check for queued revisions, and if any call `revise` first
# 2. Execute the user's expression and store the result in a temporary variable
# 3. Spawn a task to update the type cache after the expression has been executed
# 4. Return the result of the user's expression
result = gensym("result")
return Expr(:toplevel,
# This uses invokelatest not for reasons of world age but to ensure that the call is made at runtime.
# This allows `revise_first_scan_last` to be compiled without compiling `revise` itself, and greatly
# reduces the overhead of using Revise.
:($isempty($revision_queue) || $(Base.invokelatest)($revise)),
quote
let $result = $ex
Base.Threads.@spawn :default $(repopulate_typecache)()
$result
end
end
)
end

steal_repl_backend(_...) = @warn """
Expand Down Expand Up @@ -1633,10 +1650,10 @@ function __init__()

mode = get(ENV, "JULIA_REVISE", "auto")
if mode == "auto"
pushfirst!(REPL.repl_ast_transforms, revise_first)
pushfirst!(REPL.repl_ast_transforms, revise_first_scan_last)
# #664: once a REPL is started, it no longer interacts with REPL.repl_ast_transforms
if active_repl_backend_available()
push!(Base.active_repl_backend.ast_transforms, revise_first)
push!(Base.active_repl_backend.ast_transforms, revise_first_scan_last)
else
# wait for active_repl_backend to exist
# #719: do this async in case Revise is being loaded from startup.jl
Expand All @@ -1647,7 +1664,7 @@ function __init__()
iter += 1
end
if active_repl_backend_available()
push!(Base.active_repl_backend.ast_transforms, revise_first)
push!(Base.active_repl_backend.ast_transforms, revise_first_scan_last)
end
end
isdefined(Base, :errormonitor) && Base.errormonitor(t)
Expand All @@ -1665,11 +1682,22 @@ function __init__()
# This feature needs to be disabled on Apple Silicon for Julia v1.12 and earlier
# due to the Julia runtime side issue (https://github.com/JuliaLang/julia/issues/60721)
@static if !(VERSION < v"1.13-" && Sys.isapple())
if __bpart__[] && (isnothing(distributed_module) || distributed_module.myid() == 1)
Threads.@spawn :default foreach_subtype(Any) do @nospecialize type
# Populating this cache can be time consuming (eg, 30s on an
# i7-7700HQ) so do this incrementally and yield() to the scheduler
# regularly so this thread gets a chance to exit if the user quits early
if (isnothing(distributed_module) || distributed_module.myid() == 1)
Threads.@spawn :default repopulate_typecache()
end
end
return nothing
end

const _repopulating = ReentrantLock()
function repopulate_typecache()
if __bpart__[]
@lock _repopulating begin
foreach_subtype(Any) do @nospecialize type
# Populating this cache can may take a few seconds on large
# codebases, so do this incrementally and yield() to the
# scheduler regularly so this thread gets a chance to exit if
# the user quits early
yield()
fieldtypes_cached(type)
end
Expand Down
2 changes: 1 addition & 1 deletion src/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function _precompile_()
@warnpcfail precompile(Tuple{typeof(watch_package_callback), PkgId})

@warnpcfail precompile(Tuple{typeof(revise)})
@warnpcfail precompile(Tuple{typeof(revise_first), Expr})
@warnpcfail precompile(Tuple{typeof(revise_first_scan_last), Expr})
@warnpcfail precompile(Tuple{typeof(includet), String})
@warnpcfail precompile(Tuple{typeof(track), Module, String})
# setindex! doesn't fully precompile, but it's still beneficial to do it
Expand Down
19 changes: 16 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,18 @@ end

do_test("REPL input") && @testset "REPL input" begin
# issue #573
retex = Revise.revise_first(nothing)
retex = Revise.revise_first_scan_last(nothing)
@test retex.head === :toplevel
@test length(retex.args) == 2 && retex.args[end] === nothing
@test length(retex.args) == 2
retarg = retex.args[end]
if Meta.isexpr(retarg, :block)
retarg = retarg.args[end]
end
@test isexpr(retarg, :let)
ex1 = retarg.args[1]
@test Meta.isexpr(ex1, :(=))
@test ex1.args[2] === nothing
@test retarg.args[2].args[end] == ex1.args[1]
end

do_test("Signature extraction") && @testset "Signature extraction" begin
Expand Down Expand Up @@ -2561,7 +2570,11 @@ end
end
end

Revise.__bpart__[] && do_test("visit") && @testset "visit" include("test_visit.jl")
Revise.__bpart__[] && do_test("visit") && @testset "visit" begin
@lock Revise._repopulating begin
include("test_visit.jl")
end
end

if Revise.__bpart__[] && do_test("struct revision (simple)") # can we revise types and constants?
@testset "struct revision (simple)" begin
Expand Down
2 changes: 1 addition & 1 deletion test/start_late.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ while !isdefined(Base, :active_repl_backend) || isnothing(Base.active_repl_backe
end

using Revise
@test Revise.revise_first ∈ Base.active_repl_backend.ast_transforms
@test Revise.revise_first_scan_last ∈ Base.active_repl_backend.ast_transforms

exit()
Loading