Expand description
#855: the wire-contract IR — the shape a Bynk type takes crossing a context boundary, derived once from the AST + type table so both the emitter’s codec generation and the editor’s “wire contract” peek render the same derivation instead of two hand-synchronised ones.
The seam: bynk-check says what the boundary is. bynk-emit says
how that reads as TypeScript.
Crossing this module boundary is legal for BaseType, PredKind,
TypeRef, Expr, TypeDecl (all bynk-syntax) — the vocabulary a
boundary type is built from. It is not legal for any TS-token string,
pred_condition_and_message (bynk-emit/src/emitter.rs, stays put),
lower_field_default_wire (bynk-emit/src/emitter/serialisation.rs,
stays put — a default’s wire literal is a rendering, not a boundary
fact), or the Qual type-name→TS-namespace-prefix map (bynk-emit’s
serialisation.rs; see the Provenance doc for why it cannot move
here). bynk-emit renders this IR into TypeScript; it does not re-derive
it.
Derived from AST + type table, not checker::Ty. Ty::Named { name, kind, args } carries no refinement predicates, so a Ty-based
derivation would still need this same type-table lookup for PredKinds —
Ty would buy only generic substitution, which the moved walks below
already implement directly over TypeRef. contract.rs (this crate)
also derives straight from the AST + type table and must not depend on
checker output being available; this module keeps the same shape of
dependency.
This IR is not contract.rs’s canonical form, and the two must
never be unified — they disagree on purpose, on every axis that matters:
contract.rs (hash) | wire.rs (this module) | |
|---|---|---|
| predicates | sorted, deduped | declaration order, not deduped |
| record fields | sorted by name | declaration order (emitted key order) |
| sum variants | sorted by name | declaration order (switch arm order) |
| opaque predicate | elided — unobservable to the consumer | present — the owner still re-validates |
contract.rs:248 documents the sorting as a precondition for hash
correctness — hashing predicates in source order “would make two contexts
that agree perfectly fail closed against each other.” The hash is a
type identity (order-insensitive by design); this module’s shapes are
an emission order (order-sensitive by necessity, since a JS switch
and an inlined if chain both have a literal source order a reader can
see). Merging them would create the exact spurious-409 failure the hash
exists to prevent. What the two genuinely share — and what a cross-check
test elsewhere asserts — is boundary-type reachability, not shape.
Structs§
- Wire
Field - One field of a boundary record, in declaration order (the emitted JSON key order).
- Wire
Model - The set of
TypeRefs a boundary walk resolved and their re-derived structural shapes — the single source of truth both the emitter’s codec generation and the wire-contract peek render from. - Wire
Scalar - A refined- or opaque-base-type boundary scalar.
- WireSum
- A boundary sum type. The wire and in-memory discriminants are carried
side by side because the codec’s whole job is translating between them —
memory_discriminantis the softest part of this seam (a host representation choice, not a wire fact); moving it back intobynk-emitalone is a one-field change if a reviewer objects. - Wire
Type - A named boundary type’s declared shape, refinement-independent of who is
asking.
owner-relative facts (Revalidation) live one level down, inWireScalar— the shape itself does not vary with provenance, only how hard the receiver re-checks it. - Wire
Variant - One variant of a
WireSum, in declaration order (the emittedswitcharm order).
Enums§
- Base
Guard - An extra structural guard a base-typed wire value must pass beyond its
typeof: anInt/Instantmust be whole, aFloatmust be finite. - Expected
- What a structural-mismatch error reports as
expected— the vocabulary a renderer needs to explain why a wire value was rejected, independent of the TS spelling of the check that rejected it. - Json
Kind - The JSON value shape a
BaseTypeoccupies on the wire. Replacesbynk-emit’sts_base_for_serialisationclassification — the TS-token spelling of each kind ("number","string", …) stays inbynk-emit, since a TS token is exactly what this seam excludes. - Provenance
- Where a boundary type’s declaration lives relative to the module doing the crossing.
- Revalidation
- #661 Decisions C/D plus the owner path, stated once: how a boundary scalar’s refinement is re-checked on the way in, given who declared it and where the check is happening.
- Unchecked
Reason - Why a
WireRefcarries no generated codec — the runtime-owned error family, which has noTypeDeclto derive a shape from. - Wire
Body - Wire
Inst - A boundary occurrence of a generic instantiation needing its own
specialised codec —
Result<A, B>,Option<A>,List<A>,Map<K, V>, or a generic user record/sum applied to concrete arguments (Paginated[User],ApiResult[User]). - WireRef
- The resolved shape of one
TypeRefoccurrence — a field’s type, a sum variant payload’s type, a generic instantiation’s argument. Mirrors the exhaustive dispatchbynk-emit’semit_field_deserialiseperforms today, one level removed from any TS string.
Functions§
- boundary_
model - Build the full
WireModelfor a boundary: every non-generic named type intype_namesresolved throughtypes, plus the generic instantiations the caller already collected (collect_generic_instantiations/collect_codec_closure).provenanceis supplied by the caller —bynk-emitknows the build target and the consumed set; this crate must not (see theProvenancedoc). - codec_
suffix - The codec-name suffix a
TypeRefresolves to —Int,Order,Result_Int_String,Paginated_User. Renamed frominner_ts_namein the move frombynk-emit; used both to key aWireInstand, unqualified, as the bare codec function suffix a same-module call reaches. - collect_
boundary_ types - Compute the set of type names (transitively reachable) that need serialise/deserialise helpers for this context: any type used in the argument or return position of a service handler exposed by this context, walked through record fields, sum payloads, and the generic type parameters of Result/Option/Effect.
- collect_
codec_ closure - v0.22b: the codec closure for a set of
Json.encode/Json.decode[T]target type-refs — the named types needing per-type helpers (transitively through record fields and sum payloads) plus the generic instantiations needing specialised helpers. The same closure logic as the boundary collectors, rooted at expressions instead of service signatures. - collect_
generic_ instantiations - Collect the set of
Result<A, B>/Option<A>instantiations used in boundary positions so the emitter can synthesise the specialised helpers. v0.18: an instantiation may also appear in the fields of a boundary record or sum payload (e.g. the bynk surface’sRequest.contentType: Option[String]) — the per-type serialisers delegate to the specialised generic helpers, so walk those too. - inst_
codec_ suffix - v0.174 (#592): the monomorphised codec suffix for a generic-record
instantiation —
Paginated[User]→Paginated_User,Pair[User, String]→Pair_User_String. #593: shared with generic sums. Renamed fromapp_ts_namein the move frombynk-emit. - json_
kind_ of - The JSON kind a base type wires as. Same mapping as the
ts_base_for_serialisationit replaces:Int/Float/Duration/Instant→Number(all four erase to a TSnumber),String/Bytes→String(aByteswires as a base64 string, ADR 0142 D5),Bool→Boolean. - record_
inst_ fields - v0.174 (#592): the concrete
(field-name, field-type)list for a generic record instantiationName[args…]— the declared fields with every type parameter substituted by the matching argument. ReturnsNoneifnameis not a declared generic record or the arity does not match (both guaranteed impossible by the checker, so this is purely defensive). - sum_
inst_ variants - #593: the concrete
(variant-name, [(field-name, field-type)])list for a generic sum instantiationName[args…]— the declared variants with every type parameter substituted by the matching argument. The sum analogue ofrecord_inst_fields;None(defensively) ifnameis not a declared generic sum or the arity does not match. - wire_
ref - Resolve one
TypeRefoccurrence (a field’s type, a variant payload field’s type, a generic argument) into itsWireRefshape — the same dispatchemit_field_deserialiseperforms today, one level removed from any TS string. - wire_
type - Build a
WireTypefrom aTypeDecl, using the same base/predicate/ opaque logic currently inline inbynk-emit’semit_refinedandemit_bytes_named_codec— without any TS string emission. ReturnsNonefor a generic declaration (decl.type_paramsnon-empty): a generic record/sum has no single bareWireTypeof its own, only per-instantiation shapes (WireInst), mirroring the skip inemit_helpers_for_owner_qualified.