libero/wire_identity
Wire-format type identity for libero codegen.
Each user type that crosses the wire is identified by a 10-char hex hash derived from its source identity (module path + constructor name + field types). The hash is computed at codegen time and baked directly into generated encode/decode functions; the runtime carries no global lookup tables for type identity.
Two same-named types in different modules produce different hashes because the canonical signature includes the module path. There is no scenario where two genuinely-distinct user types collide on the wire short of a hash birthday collision, which the codegen-level uniqueness check (separate module) catches as a build error.
Types
The minimal projection of a constructor needed to compute its wire
identity: source module path, constructor name, and ordered field
types. Codegen converts from walker.DiscoveredVariant (which
carries additional fields like float-field indices) by dropping the
codegen-only fields, so this type stays free of incidental coupling
and tests don’t need to fabricate codegen-internal data.
pub type Constructor {
Constructor(
module_path: String,
name: String,
fields: List(field_type.FieldType),
)
}
Constructors
-
Constructor( module_path: String, name: String, fields: List(field_type.FieldType), )
Values
pub fn canonical_signature(
module_path module_path: String,
constructor_name constructor_name: String,
fields fields: List(field_type.FieldType),
) -> String
Render the canonical signature for a constructor: the deterministic
string of the form <module_path>|<constructor_name>|<field_tokens>
that feeds into wire_hash. Exposed so tests can pin the signature
shape independently of the hash function, and so the codegen can
surface the signature in collision error messages.
Field tokens come from field_type.to_canonical_token. Zero-field
constructors render with an empty trailing segment, e.g.
shared/types|Pending|.
pub fn check_field_type_safety(
field_type ft: field_type.FieldType,
label label: String,
) -> Result(Nil, gen_error.GenError)
Validate a single FieldType for wire safety. Same rules as check_wire_safety but for standalone fields (endpoint params/returns).
pub fn check_uniqueness(
constructors: List(Constructor),
) -> Result(Nil, gen_error.GenError)
Walk a list of constructors and detect identity hash collisions —
two distinct canonical signatures whose hashes collide. Returns
Ok(Nil) when every distinct signature has a distinct hash, or
Error(TypeIdentityHashCollision) on the first collision found.
Duplicate constructors (identical canonical signature) are not collisions — the caller may pass the same constructor twice (for example via cross-module type sharing) and that’s expected.
This is the sole runtime safety mechanism against hash collisions; the codegen calls this before emitting any wire-identity-dependent code. With 40-bit truncated SHA-256 the chance of a real collision in any realistic codebase is vanishingly small, but the cost of catching it is one dict lookup per constructor.
pub fn check_uniqueness_with(
constructors: List(Constructor),
hash_fn: fn(String) -> String,
) -> Result(Nil, gen_error.GenError)
Same as check_uniqueness but takes the hash function as a
parameter. Exists so tests can force a collision via a mock hash
(real SHA-256 collisions at 40 bits are computationally infeasible
to construct on demand). Production callers always pass wire_hash.
pub fn check_wire_safety(
constructors: List(Constructor),
) -> Result(Nil, gen_error.GenError)
Walk a list of constructors and verify each one’s fields are wire-safe.
Returns Ok(Nil) when every field can be encoded over the wire, or
the first violation encountered as a GenError.
Currently rejects:
Dict(K, V)whereKis anything other thanInt,String, orBool(other key types have ambiguous JS-side identity or wire contracts that don’t round-trip cleanly).TypeVar(_)— an unresolved generic parameter that survived to runtime. Without a concrete type, codegen cannot emit transformer logic for nested fields; matching JS’s runtime stance, we reject at codegen time so the failure is loud and early.
The codegen calls this before emission so unsafe types never reach the transformer emitter (where they would become silent footguns).
pub fn wire_hash(signature: String) -> String
Compute the 10-char lowercase hex wire hash for an arbitrary canonical signature. SHA-256 truncated to 40 bits, rendered as 10 lowercase hex characters. Pure function: same signature always produces the same hash.
40 bits of identity is enough for our universe of types. Birthday collisions become statistically likely around 1M distinct types in one consumer; the codegen uniqueness check covers the residual case.
pub fn wire_identity(
module_path module_path: String,
constructor_name constructor_name: String,
fields fields: List(field_type.FieldType),
) -> #(String, String)
Compute the wire identity for a constructor. Returns the canonical signature paired with its hash, so callers (codegen + uniqueness check) that need both can avoid recomputing the signature twice.