/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
base/Enums.jl
254 строки
8 KB
Alex Arslan
Allow instance docstrings within `@enum` (#61955)
31 май 2026, 18:48
Не верифицирован
31 май 2026, 18:48
3fdda75
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license module Enums import Core.Intrinsics.bitcast export Enum, @enum function namemap end """ Enum{T<:Integer} The abstract supertype of all enumerated types defined with [`@enum`](@ref). """ abstract type Enum{T<:Integer} end basetype(::Type{<:Enum{T}}) where {T<:Integer} = T (::Type{T})(x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(bitcast(T2, x))::T Base.cconvert(::Type{T}, x::Enum{T2}) where {T<:Integer,T2<:Integer} = T(x)::T Base.write(io::IO, x::Enum{T}) where {T<:Integer} = write(io, T(x)) Base.read(io::IO, ::Type{T}) where {T<:Enum} = T(read(io, basetype(T))) """ _enum_hash(x::Enum, h::UInt) Compute hash for an enum value `x`. This internal method will be specialized for every enum type created through [`@enum`](@ref). """ _enum_hash(x::Enum, h::UInt) = invoke(hash, Tuple{Any, UInt}, x, h) Base.hash(x::Enum, h::UInt) = _enum_hash(x, h) Base.isless(x::T, y::T) where {T<:Enum} = isless(basetype(T)(x), basetype(T)(y)) Base.Symbol(x::Enum) = namemap(typeof(x))[Integer(x)]::Symbol function _symbol(x::Enum) names = namemap(typeof(x)) x = Integer(x) get(() -> Symbol("<invalid #$x>"), names, x)::Symbol end Base.print(io::IO, x::Enum) = print(io, _symbol(x)) function Base.show(io::IO, x::Enum) sym = _symbol(x) if !(get(io, :compact, false)::Bool) from = get(io, :module, Main) def = parentmodule(typeof(x)) if from === nothing || !Base.isvisible(sym, def, from) show(io, def) print(io, ".") end end print(io, sym) end function Base.show(io::IO, ::MIME"text/plain", x::Enum) print(io, x, "::") show(IOContext(io, :compact => true), typeof(x)) print(io, " = ") show(io, Integer(x)) end function Base.show(io::IO, m::MIME"text/plain", t::Type{<:Enum}) if isconcretetype(t) print(io, "Enum ") Base.show_datatype(io, t) print(io, ":") for x in instances(t) print(io, "\n", Symbol(x), " = ") show(io, Integer(x)) end else invoke(show, Tuple{IO, MIME"text/plain", Type}, io, m, t) end end # generate code to test whether expr is in the given set of values function membershiptest(expr, values) lo, hi = extrema(values) if length(values) == hi - lo + 1 :($lo <= $expr <= $hi) elseif length(values) < 20 foldl((x1,x2)->:($x1 || ($expr == $x2)), values[2:end]; init=:($expr == $(values[1]))) else :($expr in $(Set(values))) end end # give Enum types scalar behavior in broadcasting Base.broadcastable(x::Enum) = Ref(x) @noinline enum_argument_error(typename, x) = throw(ArgumentError(LazyString("invalid value for Enum ", typename, ": ", x))) """ @enum EnumName[::BaseType] value1[=x] value2[=y] Create an `Enum{BaseType}` subtype with name `EnumName` and enum member values of `value1` and `value2` with optional assigned values of `x` and `y`, respectively. `EnumName` can be used just like other types and enum member values as regular values, such as # Examples ```jldoctest fruitenum julia> @enum Fruit apple=1 orange=2 kiwi=3 julia> f(x::Fruit) = "I'm a Fruit with value: \$(Int(x))" f (generic function with 1 method) julia> f(apple) "I'm a Fruit with value: 1" julia> Fruit(1) apple::Fruit = 1 ``` Values can also be specified inside a `begin` block, e.g. ```julia @enum EnumName begin value1 value2 end ``` `BaseType`, which defaults to [`Int32`](@ref), must be a primitive subtype of `Integer`. Member values can be converted between the enum type and `BaseType`. `read` and `write` perform these conversions automatically. In case the enum is created with a non-default `BaseType`, `Integer(value1)` will return the integer `value1` with the type `BaseType`. To list all the instances of an enum use `instances`, e.g. ```jldoctest fruitenum julia> instances(Fruit) (apple, orange, kiwi) ``` It is possible to construct a symbol from an enum instance: ```jldoctest fruitenum julia> Symbol(apple) :apple ``` """ macro enum(T::Union{Symbol,Expr}, syms...) if isempty(syms) throw(ArgumentError(LazyString("no arguments given for Enum ", T))) end basetype = Int32 typename = T if isa(T, Expr) && T.head === :(::) && length(T.args) == 2 && isa(T.args[1], Symbol) typename = T.args[1] basetype = Core.eval(__module__, T.args[2]) if !isa(basetype, DataType) || !(basetype <: Integer) || !isbitstype(basetype) throw(ArgumentError( LazyString("invalid base type for Enum ", typename, ", ", T, "=::", basetype, "; base type must be an integer primitive type"))) end elseif !isa(T, Symbol) throw(ArgumentError(LazyString("invalid type expression for enum ", T))) end values = Vector{basetype}() seen = Set{Symbol}() namemap = Dict{basetype,Symbol}() lo = hi = i = zero(basetype) hasexpr = false docs = Dict{Symbol,Expr}() if length(syms) == 1 && syms[1] isa Expr && syms[1].head === :block syms = syms[1].args end for s in syms s isa LineNumberNode && continue if isa(s, Expr) && s.head === :macrocall && s.args[1] == GlobalRef(Core, Symbol("@doc")) doc = s s = s.args[4] else doc = nothing end if isa(s, Symbol) if i == typemin(basetype) && !isempty(values) throw(ArgumentError(LazyString("overflow in value \"", s, "\" of Enum ", typename))) end elseif isa(s, Expr) && (s.head === :(=) || s.head === :kw) && length(s.args) == 2 && isa(s.args[1], Symbol) i = Core.eval(__module__, s.args[2]) # allow exprs, e.g. uint128"1" if !isa(i, Integer) throw(ArgumentError(LazyString("invalid value for Enum ", typename, ", ", s, "; values must be integers"))) end i = convert(basetype, i) s = s.args[1] hasexpr = true else throw(ArgumentError(LazyString("invalid argument for Enum ", typename, ": ", s))) end s = s::Symbol if !Base.isidentifier(s) throw(ArgumentError(LazyString("invalid name for Enum ", typename, "; \"", s, "\" is not a valid identifier"))) end if hasexpr && haskey(namemap, i) throw(ArgumentError(LazyString("both ", s, " and ", namemap[i], " have value ", i, " in Enum ", typename, "; values must be unique"))) end namemap[i] = s push!(values, i) if s in seen throw(ArgumentError(LazyString("name \"", s, "\" in Enum ", typename, " is not unique"))) end push!(seen, s) if length(values) == 1 lo = hi = i else hi = max(hi, i) end if doc !== nothing docs[s] = doc end i += oneunit(i) end blk = quote # enum definition Base.@__doc__(primitive type $(esc(typename)) <: Enum{$(basetype)} $(sizeof(basetype) * 8) end) function $(esc(typename))(x::Integer) $(membershiptest(:x, values)) || enum_argument_error($(Expr(:quote, typename)), x) return bitcast($(esc(typename)), convert($(basetype), x)) end Enums.namemap(::Type{$(esc(typename))}) = $(esc(namemap)) Base.typemin(x::Type{$(esc(typename))}) = $(esc(typename))($lo) Base.typemax(x::Type{$(esc(typename))}) = $(esc(typename))($hi) let type_hash = hash($(esc(typename))) # Use internal `_enum_hash` to allow users to specialize # `Base.hash` for their own enum types without overwriting the # method we would define here. This avoids a warning for # precompilation. Enums._enum_hash(x::$(esc(typename)), h::UInt) = hash(type_hash, hash(Integer(x), h)) end let insts = (Any[ $(esc(typename))(v) for v in $values ]...,) Base.instances(::Type{$(esc(typename))}) = insts end end if isa(typename, Symbol) for (i, sym) in namemap ex = :(const $(esc(sym)) = $(esc(typename))($i)) if haskey(docs, sym) docs[sym].args[4] = ex ex = docs[sym] end push!(blk.args, ex) end end push!(blk.args, :nothing) blk.head = :toplevel return Base.replace_linenums!(blk, __source__) end end # module