# This file is a part of Julia. License is MIT: https://julialang.org/license

struct Binding
    mod::Module
    var::Symbol

    function Binding(m::Module, v::Symbol)
        # Normalise the binding module for module symbols so that:
        #   Binding(Base, :Base) === Binding(Main, :Base)
        m = nameof(m) === v ? parentmodule(m) : m
        # Walk every explicit by-name import to recover the canonical `(mod, name)` pair,
        # threading `as`-renames through chained re-exports.
        # `partition_restriction` only walks one hop; `binding_module` walks all hops
        # but drops the name.
        world = Base.get_world_counter()
        while Base.invoke_in_world(world, isdefinedglobal, m, v)
            bpart = Base.lookup_binding_partition(world, GlobalRef(m, v))
            Base.is_some_explicit_imported(Base.binding_kind(bpart)) || break
            imported = Base.partition_restriction(bpart)::Core.Binding
            next_m, next_v = imported.globalref.mod, imported.globalref.name
            (next_m === m && next_v === v) && break
            m, v = next_m, next_v
        end
        new(Base.binding_module(m, v), v)
    end
end

bindingexpr(x) = Expr(:call, Binding, splitexpr(x)...)

defined(b::Binding) = invokelatest(isdefinedglobal, b.mod, b.var)
resolve(b::Binding) = invokelatest(getglobal, b.mod, b.var)

function splitexpr(x::Expr)
    isexpr(x, :.) ? (x.args[1], x.args[2]) : error("Could not find something to document in `$x`.")
end
splitexpr(s::Symbol) = :($Base.@__MODULE__), quot(s) # this somewhat complex form allows deferring resolving the Module for module docstring until after the module is created
splitexpr(r::GlobalRef) = r.mod, quot(r.name)
splitexpr(other)     = error("Could not find something to document in `$other`.")

function Base.show(io::IO, b::Binding)
    if b.mod === Base.active_module()
        print(io, b.var)
    else
        print(io, b.mod, '.')
        if Base.isoperator(b.var)
            # ensures symbols are quoted right, so e.g.  :(==), :(:), :+ or :-
            show(io, b.var)
        else
            # print ordinary identifiers without any quoting
            print(io, b.var)
        end
    end
end

aliasof(b::Binding)     = defined(b) ? (a = aliasof(resolve(b), b); defined(a) ? a : b) : b
aliasof(d::DataType, b) = Binding(parentmodule(d), nameof(d))
aliasof(λ::Function, b) = Binding(parentmodule(λ), nameof(λ))
aliasof(m::Module,   b) = Binding(m, nameof(m))
aliasof(other,       b) = b
