Skip to content

fix: trim-resolvable UID construction#1

Open
quinnj wants to merge 3 commits into
mainfrom
trim-verifier-fixes
Open

fix: trim-resolvable UID construction#1
quinnj wants to merge 3 commits into
mainfrom
trim-verifier-fixes

Conversation

@quinnj

@quinnj quinnj commented Jul 11, 2026

Copy link
Copy Markdown
Member

Three commits that make UID generation compile cleanly under juliac --trim (found while trim-compiling an HTTP server whose id generation funnels through Encid):

1. Specialize the constructors on the uid type

Julia doesn't specialize methods on unparameterized ::Type{<:Union{...}} arguments, and kwarg NamedTuples type a Type value as a bare DataType — either way every UID(...) call funneled into one instance where uid_type is a runtime DataType: type-unstable, and unresolvable dynamic dispatch under --trim. Both entry points now take ::Type{U} ... where U and forward positionally to _uid_impl, so the body's uid_type == UIDn ladder folds per instance.

2. Value-level String fast path (single-word uid types)

The generic path wraps strings as StringN{length(s)} — a runtime type parameter — which makes the whole encode machinery runtime dispatch. Generation only needs the length's value, so UID(::Type{U}, s::String) for U ∈ {UID2, UID4, UID8, UID16} does the same bit surgery value-level. Bit-identical output verified seed-for-seed against the generic path for all four types. Multi-word types fall through to the generic method unchanged. Note: the result is tagged UID{String, U} rather than UID{Tuple{StringN{N}}, U} — printing/generation identical; callers that decode by type should use the generic constructor.

3. base58: byte fill instead of "1"^n

repeat(::String, ::Int) is an unresolved invoke under --trim; String(fill(UInt8('1'), n)) produces the same padding (roundtrip verified, e.g. [0,0,5,1] → "11P6" → [0,0,5,1]).

Found in passing (not addressed here)

The generic path throws for UID24 with a 16-char string: its words vector [UInt128(t[1]), UInt64(t[2])] promotes to Vector{UInt128}, and the UID24((words[1], words[2])) construction then rejects (UInt128, UInt128). Pre-existing; worth its own fix.

Tests green; with this + the JSON/StructUtils/Parsers/HTTP trim PRs, a 30+ package server graph's id-generation path contributes zero verifier errors.

🤖 Generated with Claude Code

quinnj added 3 commits July 11, 2026 12:42
Julia doesn't specialize methods on unparameterized Type arguments
(::Type{<:Union{...}}), so every UID(...; uid_type=...) call funneled into
one instance where uid_type is a runtime DataType — type-unstable, and
unresolvable dynamic dispatch under juliac --trim. A where-param keeps
dispatch identical and makes each uid_type a compile-time constant (the
if-else ladder in the body folds per instance).
…evel String fast path)

- UID constructors take the uid type as a where-param and forward to
  _uid_impl positionally: julia doesn't specialize on unparameterized
  Type arguments, and kwarg NamedTuples type a Type value as a bare
  DataType — either way every call funneled into one instance with a
  runtime DataType, unresolvable under juliac --trim.
- Single-word String fast path: the generic path wraps strings as
  StringN{length(s)} — a runtime type parameter — making the encode
  machinery runtime dispatch. The value-level path produces bit-identical
  results (verified seed-for-seed against the generic path for
  UID2/4/8/16) and tags the result UID{String, U}. Multi-word types fall
  through to the generic method.

Found in passing: the generic path throws for UID24 with a 16-char
string (its words vector promotes to Vector{UInt128}, then the UID24
constructor rejects (UInt128, UInt128)) — pre-existing, not addressed
here.
repeat(String, Int) is an unresolved invoke under juliac --trim; an
explicit byte fill produces the same string.
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