fix: trim-resolvable UID construction - #1
Merged
Merged
Conversation
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.
Compiles test/encid_trim_workload.jl with juliac --trim=safe (JuliaC.jl, error
budget zero) and runs the resulting executable, so the trim-verifier fixes in
this PR can't silently regress. Same harness shape as StructUtils/HTTP.
The workload asserts runtime values across the trim-safe surface: base58
encode/decode incl. the leading-zero padding path, primitive uid construction
and rendering, and the value-level UID(U, ::String) fast path for every
single-word uid type. The generic args... machinery (runtime StringN{N}) and
decode-by-index (runtime type-parameter walk) are documented as outside the
trim-safe surface and stay covered by the regular suite.
Skips on Julia < 1.12 and on prerelease builds.
The multi-word primitive uid constructors (UID24/UID32/UID64) reinterpret NTuple values into wide primitive types, which Julia only supports from 1.10 — on 1.6-1.9 the bitcast rejects tuple arguments, so the basics testset has failed the 'min' CI job since the initial commit (main included). 1.10 is the current LTS; the full suite passes there (the trim-compile testset skips below 1.12 by design).
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three commits that make
UIDgeneration compile cleanly underjuliac --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 bareDataType— either way everyUID(...)call funneled into one instance whereuid_typeis a runtimeDataType: type-unstable, and unresolvable dynamic dispatch under--trim. Both entry points now take::Type{U} ... where Uand forward positionally to_uid_impl, so the body'suid_type == UIDnladder 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, soUID(::Type{U}, s::String)forU ∈ {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 taggedUID{String, U}rather thanUID{Tuple{StringN{N}}, U}— printing/generation identical; callers that decode by type should use the generic constructor.3. base58: byte fill instead of
"1"^nrepeat(::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
UID24with a 16-char string: its words vector[UInt128(t[1]), UInt64(t[2])]promotes toVector{UInt128}, and theUID24((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