@@ -26,7 +26,7 @@ export @test, @test_throws, @test_broken, @test_skip,
2626
2727export @testset
2828export @inferred
29- export detect_ambiguities, detect_unbound_args
29+ export detect_ambiguities, detect_unbound_args, detect_closure_boxes, detect_closure_boxes_all_modules
3030export GenericString, GenericSet, GenericDict, GenericArray, GenericOrder
3131export TestSetException
3232export TestLogger, LogRecord
@@ -2615,7 +2615,7 @@ function detect_ambiguities(mods::Module...;
26152615 allowed_undefineds = nothing )
26162616 @nospecialize
26172617 ambs = Set {Tuple{Method,Method}} ()
2618- mods = collect (mods) :: Vector{ Module}
2618+ mods = Module[mods ... ]
26192619 function sortdefs (m1:: Method , m2:: Method )
26202620 ord12 = cmp (m1. file, m2. file)
26212621 if ord12 == 0
@@ -2645,6 +2645,86 @@ function detect_ambiguities(mods::Module...;
26452645 return collect (ambs)
26462646end
26472647
2648+ """
2649+ detect_closure_boxes(mod1, mod2...)
2650+
2651+ Return a sorted `Vector{Pair{Method, Vector{Symbol}}}` of methods defined in the
2652+ specified modules (or their submodules) that allocate `Core.Box` in their lowered
2653+ code, paired with the boxed variable names. Variable names are `:unknown` when a
2654+ slot name cannot be resolved.
2655+
2656+ See also [`detect_closure_boxes_all_modules`](@ref) to check all loaded modules.
2657+ """
2658+ function detect_closure_boxes (mods:: Module... )
2659+ @nospecialize
2660+ boxes = Dict {Method, Vector{Symbol}} ()
2661+ mods = Module[mods... ]
2662+ isempty (mods) && return Pair{Method, Vector{Symbol}}[]
2663+
2664+ function is_box_call (@nospecialize expr)
2665+ if ! (expr isa Expr)
2666+ return false
2667+ end
2668+ if expr. head === :call || expr. head === :new
2669+ callee = expr. args[1 ]
2670+ return callee === Core. Box || (callee isa GlobalRef && callee. mod === Core && callee. name === :Box )
2671+ end
2672+ return false
2673+ end
2674+
2675+ function slot_name (ci, slot):: Symbol
2676+ if slot isa Core. SlotNumber
2677+ idx = Int (slot. id)
2678+ if 1 <= idx <= length (ci. slotnames)
2679+ return ci. slotnames[idx]
2680+ end
2681+ end
2682+ return Symbol (string (slot))
2683+ end
2684+
2685+ function matches_module (mod:: Module )
2686+ return is_in_mods (mod, true , mods)
2687+ end
2688+
2689+ function scan_method! (m:: Method )
2690+ matches_module (parentmodule (m)) || return
2691+ ci = try
2692+ Base. uncompressed_ast (m)
2693+ catch
2694+ return
2695+ end
2696+ for stmt in ci. code
2697+ if stmt isa Expr && stmt. head === :(= )
2698+ lhs = stmt. args[1 ]
2699+ rhs = stmt. args[2 ]
2700+ if is_box_call (rhs)
2701+ push! (get! (Vector{Symbol}, boxes, m), slot_name (ci, lhs))
2702+ end
2703+ elseif is_box_call (stmt)
2704+ push! (get! (Vector{Symbol}, boxes, m), :unknown )
2705+ end
2706+ end
2707+ end
2708+
2709+ Base. visit (Core. methodtable) do m
2710+ scan_method! (m)
2711+ end
2712+
2713+ result = collect (boxes)
2714+ sort! (result, by = entry -> (entry. first. file, entry. first. line, entry. first. name))
2715+ return result
2716+ end
2717+
2718+ """
2719+ ()
2720+
2721+ Return a sorted `Vector{Pair{Method, Vector{Symbol}}}` of all methods in currently
2722+ loaded modules that allocate `Core.Box` in their lowered code.
2723+
2724+ See also [`detect_closure_boxes`](@ref) to check specific modules.
2725+ """
2726+ detect_closure_boxes_all_modules () = detect_closure_boxes (Base. loaded_modules_array ()... )
2727+
26482728"""
26492729 detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
26502730
@@ -2671,7 +2751,7 @@ function detect_unbound_args(mods...;
26712751 allowed_undefineds= nothing )
26722752 @nospecialize mods
26732753 ambs = Set {Method} ()
2674- mods = collect (mods) :: Vector{ Module}
2754+ mods = Module[mods ... ]
26752755 function examine (mt:: Core.MethodTable )
26762756 for m in Base. MethodList (mt)
26772757 is_in_mods (parentmodule (m), recursive, mods) || continue
0 commit comments