Skip to content

Introduce TaggedOneOf - #297

Draft
Drvi wants to merge 5 commits into
masterfrom
td-tagged-oneof
Draft

Introduce TaggedOneOf#297
Drvi wants to merge 5 commits into
masterfrom
td-tagged-oneof

Conversation

@Drvi

@Drvi Drvi commented Mar 10, 2026

Copy link
Copy Markdown
Member

WIP

The idea is to generate a custom struct for each oneof field which would contain a tag and logic to access the active union member in a type-stable way with PB.active_value(x) do val; ...; end

Example:

syntax = "proto3";
message A { oneof field_name { string b = 3; uint64 c = 4; bytes d = 5; int32 e = 6; int32 g = 7; } }
struct var"A.FieldName" <: PB.TaggedOneOf
    tag::UInt8
    bit::Union{Nothing,UInt64,Int32}
    ptr::Union{Nothing,String,Vector{UInt8}}
end
var"A.FieldName"(::Val{:b}, x) = var"A.FieldName"(UInt8(1), nothing, convert(String, x))
var"A.FieldName"(::Val{:c}, x) = var"A.FieldName"(UInt8(2), convert(UInt64, x), nothing)
var"A.FieldName"(::Val{:d}, x) = var"A.FieldName"(UInt8(3), nothing, convert(Vector{UInt8}, x))
var"A.FieldName"(::Val{:e}, x) = var"A.FieldName"(UInt8(4), convert(Int32, x), nothing)
var"A.FieldName"(::Val{:g}, x) = var"A.FieldName"(UInt8(5), convert(Int32, x), nothing)
PB.unsafe_getproperty(x::var"A.FieldName", ::Val{:b}) = Base.getfield(x, :ptr)::String
PB.unsafe_getproperty(x::var"A.FieldName", ::Val{:c}) = Base.getfield(x, :bit)::UInt64
PB.unsafe_getproperty(x::var"A.FieldName", ::Val{:d}) = Base.getfield(x, :ptr)::Vector{UInt8}
PB.unsafe_getproperty(x::var"A.FieldName", ::Val{:e}) = Base.getfield(x, :bit)::Int32
PB.unsafe_getproperty(x::var"A.FieldName", ::Val{:g}) = Base.getfield(x, :bit)::Int32
PB.field_to_tag(::Type{var"A.FieldName"}) = (;b = UInt8(1), c = UInt8(2), d = UInt8(3), e = UInt8(4), g = UInt8(5))
function PB.active_name(f::F, x::var"A.FieldName") where {F}
    tag = getfield(x, :tag)
    if     tag == UInt8(1); return f(Val(:b))
    elseif tag == UInt8(2); return f(Val(:c))
    elseif tag == UInt8(3); return f(Val(:d))
    elseif tag == UInt8(4); return f(Val(:e))
    elseif tag == UInt8(5); return f(Val(:g))
    else   PB.throw_bad_tag(var"A.FieldName", tag)
    end
end
function PB.active_value(f::F, x::var"A.FieldName") where {F}
    tag = getfield(x, :tag)
    if     tag == UInt8(1); return f(Base.getfield(x, :ptr)::String)
    elseif tag == UInt8(2); return f(Base.getfield(x, :bit)::UInt64)
    elseif tag == UInt8(3); return f(Base.getfield(x, :ptr)::Vector{UInt8})
    elseif tag == UInt8(4); return f(Base.getfield(x, :bit)::Int32)
    elseif tag == UInt8(5); return f(Base.getfield(x, :bit)::Int32)
    else   PB.throw_bad_tag(var"A.FieldName", tag)
    end
end

struct A
    field_name::var"A.FieldName"
end
# ...

A big remaining todo is handling dependency cycles.

@Drvi
Drvi marked this pull request as draft March 10, 2026 16:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant