Repository: JuliaLang/julia
File: stdlib/Test/src/Test.jl (Julia)

Code before:
    detect_closure_boxes(mod1, mod2...; recursive=false)

Return a vector of `(Method, varname)` pairs for methods defined in the specified
modules that allocate `Core.Box` in their lowered code. If `recursive=false`,
each module is treated as a root module (for example, `Base` matches
`Base.Compiler`), but submodules of non-root modules are excluded. Use
`recursive=true` to include submodules of the specified modules. The returned
`varname` is a `Symbol`, or `:unknown` when a slot name cannot be resolved.
"""
function detect_closure_boxes(mods::Module...; recursive::Bool = false)
    @nospecialize
    boxes = Tuple{Method,Symbol}[]
    mods = Module[mods...]
    isempty(mods) && return boxes

    function is_box_call(expr)
        if !(expr isa Expr)
            return false
        end
        if expr.head === :call || expr.head === :new
            callee = expr.args[1]
            return callee === Core.Box || (callee isa GlobalRef && callee.mod === Core && callee.name === :Box)
        end
        return false
    end

    function slot_name(ci, slot)::Symbol
        if slot isa Core.SlotNumber
            idx = Int(slot.id)
            if 1 <= idx <= length(ci.slotnames)
                return ci.slotnames[idx]
            end
        end
        return Symbol(string(slot))
    end

    function root_module(mod::Module)
        while true
            parent = parentmodule(mod)
            if parent === mod || parent === Main || parent === Core
                return mod
            end
            mod = parent
        end
    end

    function matches_module(mod::Module)
        mod in mods && return true
        recursive && return is_in_mods(mod, true, mods)
        return root_module(mod) in mods
    end

Review comment (suggestion):
```
julia> Test.detect_unbound_args()
ERROR: TypeError: in typeassert, expected Vector{Module}, got a value of type Vector{Union{}}
```

Code after:
    detect_closure_boxes(mod1, mod2...)

Return a sorted `Vector{Pair{Method, Vector{Symbol}}}` of methods defined in the
specified modules (or their submodules) that allocate `Core.Box` in their lowered
code, paired with the boxed variable names. Variable names are `:unknown` when a
slot name cannot be resolved.

See also [`detect_closure_boxes_all_modules`](@ref) to check all loaded modules.
"""
function detect_closure_boxes(mods::Module...)
    @nospecialize
    boxes = Dict{Method, Vector{Symbol}}()
    mods = Module[mods...]
    isempty(mods) && return Pair{Method, Vector{Symbol}}[]

    function is_box_call(@nospecialize expr)
        if !(expr isa Expr)
            return false
        end
        if expr.head === :call || expr.head === :new
            callee = expr.args[1]
            return callee === Core.Box || (callee isa GlobalRef && callee.mod === Core && callee.name === :Box)
        end
        return false
    end

    function slot_name(ci, slot)::Symbol
        if slot isa Core.SlotNumber
            idx = Int(slot.id)
            if 1 <= idx <= length(ci.slotnames)
                return ci.slotnames[idx]
            end
        end
        return Symbol(string(slot))
    end

    function matches_module(mod::Module)
        return is_in_mods(mod, true, mods)
    end

    function scan_method!(m::Method)
        matches_module(parentmodule(m)) || return
        ci = try
            Base.uncompressed_ast(m)
        catch
            return
        end
        for stmt in ci.code
            if stmt isa Expr && stmt.head === :(=)
                lhs = stmt.args[1]
                rhs = stmt.args[2]