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

export @var

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
        new(Base.binding_module(m, v), v)
    end
end

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

defined(b::Binding) = invokelatest(isdefined, b.mod, b.var)
resolve(b::Binding) = invokelatest(getfield, b.mod, b.var)

function splitexpr(x::Expr)
    isexpr(x, :macrocall) ? splitexpr(x.args[1]) :
    isexpr(x, :.)         ? (x.args[1], x.args[2]) :
    error("Invalid @var syntax `$x`.")
end
splitexpr(s::Symbol) = Expr(:macrocall, getfield(Base, Symbol("@__MODULE__")), nothing), quot(s)
splitexpr(r::GlobalRef) = r.mod, quot(r.name)
splitexpr(other)     = error("Invalid @var syntax `$other`.")

macro var(x)
    esc(bindingexpr(x))
end

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(d.name.module, d.name.name)
aliasof(λ::Function, b) = (m = typeof(λ).name; Binding(m.module, m.singletonname))
aliasof(m::Module,   b) = Binding(m, nameof(m))
aliasof(other,       b) = b
