Skip to content

Commit 4cc0b40

Browse files
committed
move the contrib script to Test
1 parent 0dc8a09 commit 4cc0b40

File tree

4 files changed

+118
-197
lines changed

4 files changed

+118
-197
lines changed

contrib/scan-closure-boxes.jl

Lines changed: 0 additions & 194 deletions
This file was deleted.

stdlib/Test/docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ Test.GenericOrder
420420
Test.GenericSet
421421
Test.GenericString
422422
Test.detect_ambiguities
423+
Test.detect_closure_boxes
423424
Test.detect_unbound_args
424425
```
425426

stdlib/Test/src/Test.jl

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export @test, @test_throws, @test_broken, @test_skip,
2626

2727
export @testset
2828
export @inferred
29-
export detect_ambiguities, detect_unbound_args
29+
export detect_ambiguities, detect_unbound_args, detect_closure_boxes
3030
export GenericString, GenericSet, GenericDict, GenericArray, GenericOrder
3131
export TestSetException
3232
export TestLogger, LogRecord
@@ -2527,7 +2527,7 @@ function detect_ambiguities(mods::Module...;
25272527
allowed_undefineds = nothing)
25282528
@nospecialize
25292529
ambs = Set{Tuple{Method,Method}}()
2530-
mods = collect(mods)::Vector{Module}
2530+
mods = Module[mods...]
25312531
function sortdefs(m1::Method, m2::Method)
25322532
ord12 = cmp(m1.file, m2.file)
25332533
if ord12 == 0
@@ -2557,6 +2557,90 @@ function detect_ambiguities(mods::Module...;
25572557
return collect(ambs)
25582558
end
25592559

2560+
"""
2561+
detect_closure_boxes(mod1, mod2...; recursive=false)
2562+
2563+
Return a vector of `(Method, varname)` pairs for methods defined in the specified
2564+
modules that allocate `Core.Box` in their lowered code. If `recursive=false`,
2565+
each module is treated as a root module (for example, `Base` matches
2566+
`Base.Compiler`), but submodules of non-root modules are excluded. Use
2567+
`recursive=true` to include submodules of the specified modules. The returned
2568+
`varname` is a `Symbol`, or `:unknown` when a slot name cannot be resolved.
2569+
"""
2570+
function detect_closure_boxes(mods::Module...; recursive::Bool = false)
2571+
@nospecialize
2572+
boxes = Tuple{Method,Symbol}[]
2573+
mods = Module[mods...]
2574+
isempty(mods) && return boxes
2575+
2576+
function is_box_call(expr)
2577+
if !(expr isa Expr)
2578+
return false
2579+
end
2580+
if expr.head === :call || expr.head === :new
2581+
callee = expr.args[1]
2582+
return callee === Core.Box || (callee isa GlobalRef && callee.mod === Core && callee.name === :Box)
2583+
end
2584+
return false
2585+
end
2586+
2587+
function slot_name(ci, slot)::Symbol
2588+
if slot isa Core.SlotNumber
2589+
idx = Int(slot.id)
2590+
if 1 <= idx <= length(ci.slotnames)
2591+
return ci.slotnames[idx]
2592+
end
2593+
end
2594+
return Symbol(string(slot))
2595+
end
2596+
2597+
function root_module(mod::Module)
2598+
while true
2599+
parent = parentmodule(mod)
2600+
if parent === mod || parent === Main || parent === Core
2601+
return mod
2602+
end
2603+
mod = parent
2604+
end
2605+
end
2606+
2607+
function matches_module(mod::Module)
2608+
mod in mods && return true
2609+
recursive && return is_in_mods(mod, true, mods)
2610+
return root_module(mod) in mods
2611+
end
2612+
2613+
function scan_method!(m::Method)
2614+
matches_module(parentmodule(m)) || return
2615+
ci = try
2616+
Base.uncompressed_ast(m)
2617+
catch
2618+
return
2619+
end
2620+
for stmt in ci.code
2621+
if stmt isa Expr && stmt.head === :(=)
2622+
lhs = stmt.args[1]
2623+
rhs = stmt.args[2]
2624+
if is_box_call(rhs)
2625+
push!(boxes, (m, slot_name(ci, lhs)))
2626+
end
2627+
elseif is_box_call(stmt)
2628+
push!(boxes, (m, :unknown))
2629+
end
2630+
end
2631+
end
2632+
2633+
Base.visit(Core.methodtable) do m
2634+
scan_method!(m)
2635+
end
2636+
2637+
sort!(boxes, by = entry -> begin
2638+
m, var = entry
2639+
(m.file, m.line, var, m.name, m.sig)
2640+
end)
2641+
return boxes
2642+
end
2643+
25602644
"""
25612645
detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
25622646
@@ -2583,7 +2667,7 @@ function detect_unbound_args(mods...;
25832667
allowed_undefineds=nothing)
25842668
@nospecialize mods
25852669
ambs = Set{Method}()
2586-
mods = collect(mods)::Vector{Module}
2670+
mods = Module[mods...]
25872671
function examine(mt::Core.MethodTable)
25882672
for m in Base.MethodList(mt)
25892673
is_in_mods(parentmodule(m), recursive, mods) || continue

stdlib/Test/test/runtests.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,36 @@ import Logging: Debug, Info, Warn, with_logger
2929
@test 'a' .. 'a'
3030
@test !('a' .. 'b')
3131
end
32+
33+
34+
module ClosureBoxTest
35+
function boxed()
36+
x = 1
37+
inner() = (x += 1)
38+
inner()
39+
end
40+
41+
module Sub
42+
function boxed_sub()
43+
x = 0
44+
inner() = (x += 1)
45+
inner()
46+
end
47+
end
48+
end
49+
50+
@testset "detect_closure_boxes" begin
51+
boxes = Test.detect_closure_boxes(ClosureBoxTest)
52+
@test any(entry -> entry[1].name === :boxed, boxes)
53+
@test any(entry -> entry[1].name === :boxed_sub, boxes)
54+
55+
sub_boxes = Test.detect_closure_boxes(ClosureBoxTest.Sub)
56+
@test any(entry -> entry[1].name === :boxed_sub, sub_boxes)
57+
@test all(entry -> parentmodule(entry[1]) === ClosureBoxTest.Sub, sub_boxes)
58+
59+
@test isempty(Test.detect_closure_boxes())
60+
end
61+
3262
@testset "@test with skip/broken kwargs" begin
3363
# Make sure the local variables can be used in conditions
3464
a = 1

0 commit comments

Comments
 (0)