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

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

# macro wrappers for various reflection functions

using Base: insert!, replace_ref_begin_end!,
    infer_return_type, infer_exception_type, infer_effects, code_ircode, isexpr

# defined in Base so it's possible to time all imports, including InteractiveUtils and its deps
# via. `Base.@time_imports` etc.
import Base: @time_imports, @trace_compile, @trace_dispatch

typesof_expr(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:(Tuple{$(Any[esc(reescape(get_typeof, a)) for a in extract_args(args)]...)}), where_params)
typesof_expr_unescaped(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:(Tuple{$(Any[reescape(get_typeof, a) for a in extract_args(args)]...)}), where_params)

function extract_args(args)
    stop = 0
    for (i, arg) in enumerate(args)
        arg = Meta.unescape(arg)
        encountered_vararg = stop !== 0
        isexpr(arg, :(::)) && encountered_vararg && error("Explicit type annotations cannot come after a `::Vararg{...} or `::T...` annotation")
        is_vararg = isexpr(arg, :...) && isexpr(arg.args[1], :(::)) ||
                    # technically, a user may define their own `Vararg` binding,
                    # but handling this right would require deferring the check until runtime,
                    # where we can't differentiate between a type coming from a value or a type annotation
                    isexpr(arg, :(::)) && is_vararg_symbol(arg.args[end])
        !is_vararg && continue
        encountered_vararg && error("`::Vararg{...}` or `::T...` annotations must occur only once")
        stop = i
    end
    stop === 0 && return args
    return args[1:stop]
end

function is_vararg_symbol(@nospecialize ex)
    ex = ignore_qualifiers(ex)
    name = isexpr(ex, :curly) ? ex.args[1] : ex
    return in(name, (:Vararg, Vararg))
end
ignore_qualifiers(@nospecialize ex) = isexpr(ex, :(.), 2) ? ex.args[2] : ex

function extract_where_parameters(ex::Expr)
    isexpr(ex, :where) || return ex, nothing
    ex.args[1], ex.args[2:end]
end

function rewrap_where(ex::Expr, where_params::Union{Nothing, Vector{Any}})
    isnothing(where_params) && return ex
    Expr(:where, ex, esc.(where_params)...)
end

Review comment (suggestion):
Can you explain more about this ambiguity?

`typeof` should never yield a (naked) `Vararg{...}` afaik

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

# macro wrappers for various reflection functions

using Base: insert!, replace_ref_begin_end!,
    infer_return_type, infer_exception_type, infer_effects, code_ircode, isexpr

# defined in Base so it's possible to time all imports, including InteractiveUtils and its deps
# via. `Base.@time_imports` etc.
import Base: @time_imports, @trace_compile, @trace_dispatch

typesof_expr(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:($make_tuple_type(Any[$(Any[esc(reescape(get_typeof, a)) for a in args]...)])), where_params)
typesof_expr_unescaped(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:($make_tuple_type(Any[$(Any[reescape(get_typeof, a) for a in args]...)])), where_params)

function make_tuple_type(types::Vector{Any})
    vararg = -1
    for i in eachindex(types)
        i == 1 && continue # ignore function type
        type = types[i]
        if isa(type, Core.TypeofVararg)
            vararg !== -1 && throw(ArgumentError("More than one `Core.Vararg` type present in argument tuple ($type detected after $(types[vararg])); if provided, it must be unique"))
            vararg = i
            if isdefined(type, :N)
                n = length(types) - vararg + 1
                n > type.N && throw(ArgumentError("Expected at most $(type.N) types after `$type`, found $n instead"))
            end
        elseif vararg !== -1
            ref = types[vararg]
            if isdefined(ref, :T) && !skip_type_check(ref.T) && !skip_type_check(type)
                !(type <: ref.T) && throw(ArgumentError("Inconsistent type `$type` detected after `$ref`; `$type <: $(ref.T)` must hold"))
            end
        end
    end
    vararg === -1 && return Tuple{types...}
    return Tuple{@view(types[1:vararg])...}
end

skip_type_check(@nospecialize(T)) = Core.has_free_typevars(T)

function extract_where_parameters(ex::Expr)
    isexpr(ex, :where) || return ex, nothing
    ex.args[1], ex.args[2:end]
end

function rewrap_where(ex::Expr, where_params::Union{Nothing, Vector{Any}})
    isnothing(where_params) && return ex
    Expr(:where, ex, esc.(where_params)...)
end