From c58376235213e8362a4ffbaacf08d981656ae2e2 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Wed, 2 Sep 2026 08:31:58 +0000 Subject: [PATCH] =?UTF-8?q?Make=20BastDoc=20owned:=20drop=20Bast*=20lifeti?= =?UTF-8?q?mes=20(ADR-012=20=C2=A72a,=20plan=20phase=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every Bast* type drops <'a>: &'a str -> String, &'a Value -> Value (deferred decision 1: plain String/Value — the tree is built once; Arc name-sharing needs a bench justification that doesn't exist). BastDoc::new(&Value, &str) still takes references in and clones into owned storage; the doc gains Clone. resolve_ref/resolve_typeref/ resolve_typeref_as_def return owned types. - Engine ownership flip: AlkTypeEngine holds the owned BastDoc (replacing bast_doc: Value + root_name: String; root_name() delegates to the doc), killing its three per-call BastDoc::new re-parses (aligned validate_bytes, read_field, write_field — the review #004 M1 pattern removed by construction; phase 5 retires the lookup_leaf_field walk itself). New public accessor root_name() (additive). Engine Send + Sync with the owned doc, asserted in the existing thread-share test. - Bonus cleanup: materialize_typeref_packed's dead _field param dropped (phase 2 left it dangling). Under ownership, keeping it would force a deep Value clone per array element / record value / union variant via dummy_field_for. The param, dummy_field_for, and ty_source are gone; no behavior change (the arg was already ignored). BastField::synthetic keeps an owned-signature #[allow(dead_code)] definition (no remaining callers today). - Consumers adapted: OffsetMap::compute(&BastDoc), materialize_aligned(&BastDoc, ...) (no lifetime), BuildCtx/ ComputeCtx hold &'d BastDoc, tunion/discriminator name borrows, lib.rs module doc. LayoutBuilder's doc_value re-parse cache is unchanged pending phase 4. Verification: 465 tests pass with zero test-logic changes (bast.rs suites exercise every parser path through the public API); clippy -D warnings clean; cargo doc zero warnings; wasm32 release build green. --- docs/plans/030-compiled-forms.md | 30 +++- src/bast.rs | 300 ++++++++++++++++--------------- src/bast_validation.rs | 6 +- src/engine.rs | 39 ++-- src/layout_builder.rs | 14 +- src/lib.rs | 5 +- src/materialize.rs | 94 +++------- src/offset_map.rs | 26 +-- src/read_plan.rs | 40 ++--- src/tunion.rs | 16 +- src/validation_plan.rs | 22 +-- tests/tunion_dispatch.rs | 2 +- 12 files changed, 299 insertions(+), 295 deletions(-) diff --git a/docs/plans/030-compiled-forms.md b/docs/plans/030-compiled-forms.md index 7098eee..dafc5f3 100644 --- a/docs/plans/030-compiled-forms.md +++ b/docs/plans/030-compiled-forms.md @@ -379,7 +379,35 @@ message). --- -## Phase 3 — Owned `BastDoc` (ADR-012 §2a) +## Phase 3 — Owned `BastDoc` (ADR-012 §2a) — **DONE (2026-09-02)** + +> **Status: implemented.** Every `Bast*` type dropped its `<'a>`: +> `&'a str` → `String`, `&'a Value` → `Value` (deferred decision 1 +> resolved: plain `String`/`Value` — the tree is built once, name +> sharing via `Arc` needs a bench justification that doesn't +> exist). `BastDoc::new(&Value, &str)` still takes references in and +> clones into owned storage; `BastDoc` gained `Clone`. `resolve_*` +> return owned `BastDef`/`BastType`. Consumers adapted: +> `OffsetMap::compute(&BastDoc)`, `materialize_aligned(&BastDoc, ...)` +> (no lifetime), `BuildCtx`/`ComputeCtx` hold `&'d BastDoc`. +> **Engine ownership flip:** `AlkTypeEngine` now holds the owned +> `BastDoc` (replacing `bast_doc: Value` + `root_name: String` — +> `root_name()` delegates to the doc), killing its three per-call +> `BastDoc::new` re-parses (`validate_bytes` aligned path, +> `read_field`, `write_field` — review #004 M1 sites by construction; +> phase 5 removes the `lookup_leaf_field` walk itself). New public +> accessor `AlkTypeEngine::root_name()` (additive). **Bonus cleanup:** +> `materialize_typeref_packed`'s dead `_field` param dropped (phase 2 +> left it dangling); under ownership it would have forced a deep +> `Value` clone per array element/record value/union variant via +> `dummy_field_for` — the param, `dummy_field_for`, and `ty_source` +> are gone (no behavior change; the `_field` arg was already +> ignored). `BastField::synthetic` retains an owned-signature +> `#[allow(dead_code)]` definition (no remaining callers; kept for the +> phase-4/5-aligned materializer helpers if they need it). Existing +> `BastDoc` consumers (`LayoutBuilder`'s `doc_value` re-parse cache) +> are unchanged pending phase 4. Engine stays `Send + Sync` with the +> owned doc — the engine's thread-share test now asserts it directly. **Goal:** Make `BastDoc` own its data (drop the `<'a>` lifetime). `&'a str` → `String`, `&'a Value` → `Value` (or `Arc`/`Arc` diff --git a/src/bast.rs b/src/bast.rs index 73c8702..d50edfe 100644 --- a/src/bast.rs +++ b/src/bast.rs @@ -11,11 +11,13 @@ //! ## Design //! //! The parser produces a **typed tree** ([`BastDoc`]/[`BastDef`]/[`BastType`]/…) -//! that borrows from the source [`serde_json::Value`]. Three consumers -//! (layout, materialize, validate) walk the same tree, so a typed view -//! pays for itself: each walks matched arms over [`BastType`] instead of -//! re-parsing raw JSON at every node. Borrowing (not cloning) keeps the -//! parse allocation-free beyond the small typed nodes themselves. +//! that owns its data (ADR-012 §2a). Three consumers (layout, materialize, +//! validate) walk the same tree, so a typed view pays for itself: each +//! walks matched arms over [`BastType`] instead of re-parsing raw JSON at +//! every node. Ownership (not borrowing) lets [`BastDoc`] be stored on +//! its consumers (`AlkTypeEngine`, `LayoutBuilder`) without lifetime +//! entanglement — the clone happens once at [`BastDoc::new`], not per +//! field. //! //! `$ref` resolution is a single hash lookup against `$defs` — BAST refs //! are always full JSON Pointers restricted to `#/$defs/`. Union @@ -45,16 +47,18 @@ const REF_PREFIX: &str = "#/$defs/"; /// /// Built once via [`BastDoc::new`] from the raw BAST `Value`; the /// downstream `compile()` call (step 4) will construct this and hand it -/// to the layout engines. The tree borrows from `root` — keep the -/// source `Value` alive for the lifetime of the doc. -#[derive(Debug)] -pub struct BastDoc<'a> { - root: &'a Value, - root_name: &'a str, - root_def: BastDef<'a>, +/// to the layout engines. The tree is **owned** (ADR-012 §2a): `new` +/// clones the root `Value` and the root name into the doc, so the doc +/// can be stored on consumers without borrowing from the caller's +/// input. +#[derive(Debug, Clone)] +pub struct BastDoc { + root: Value, + root_name: String, + root_def: BastDef, } -impl<'a> BastDoc<'a> { +impl BastDoc { /// Parse a BAST document, selecting `root_name` as the top-level type. /// /// Validates the document structurally as it parses: the `$defs` @@ -64,26 +68,27 @@ impl<'a> BastDoc<'a> { /// root definition and the definitions it (transitively) references /// are parsed eagerly; orphan `$defs` entries are not checked. /// - /// The raw `Value` is also retained so lazy `$ref` resolution - /// ([`BastDoc::resolve_typeref`]) can reach any `$defs` entry at access time. - pub fn new(root: &'a Value, root_name: &'a str) -> Result { + /// The raw `Value` is cloned into the doc so lazy `$ref` resolution + /// ([`BastDoc::resolve_typeref`]) can reach any `$defs` entry at + /// access time. + pub fn new(root: &Value, root_name: &str) -> Result { let raw_root_def = Self::lookup_def_raw(root, root_name)?; let root_def = BastDef::parse(raw_root_def, root_name, "")?; let doc = Self { - root, - root_name, + root: root.clone(), + root_name: root_name.to_string(), root_def, }; Ok(doc) } /// The root type name this doc was compiled with. - pub fn root_name(&self) -> &'a str { - self.root_name + pub fn root_name(&self) -> &str { + &self.root_name } /// The parsed root type definition. - pub fn root_def(&self) -> &BastDef<'a> { + pub fn root_def(&self) -> &BastDef { &self.root_def } @@ -92,8 +97,8 @@ impl<'a> BastDoc<'a> { /// The single hash lookup that replaces v0.1.0's ref-normalization /// machinery. BAST refs are always `#/$defs/`; /// no external refs, no fragment-only pointers, no bare names. - pub fn lookup_def(&self, name: &str) -> Result<&'a Value, AlkTypeError> { - Self::lookup_def_raw(self.root, name) + pub fn lookup_def(&self, name: &str) -> Result<&Value, AlkTypeError> { + Self::lookup_def_raw(&self.root, name) } /// Resolve a [`BastType::Ref`] to the [`BastDef`] it names. @@ -102,7 +107,7 @@ impl<'a> BastDoc<'a> { /// for union variant dispatch and `$ref` fields. The parser does not /// inline refs at construction time — variant refs stay as /// [`BastRef`]s and resolve on demand. - pub fn resolve_ref(&self, r: &BastRef<'a>) -> Result, AlkTypeError> { + pub fn resolve_ref(&self, r: &BastRef) -> Result { let raw = self.lookup_def(r.name())?; BastDef::parse(raw, r.name(), "") } @@ -114,7 +119,7 @@ impl<'a> BastDoc<'a> { /// The composite-walkers (struct fields, array elements, record /// values, union mappings) call this to deref one level. Deeper /// `$ref` chains are resolved by recursing through the consumer. - pub fn resolve_typeref(&self, ty: &BastType<'a>) -> Result, AlkTypeError> { + pub fn resolve_typeref(&self, ty: &BastType) -> Result { match ty { BastType::Ref(r) => { let def = self.resolve_ref(r)?; @@ -134,25 +139,25 @@ impl<'a> BastDoc<'a> { /// uniformly. pub fn resolve_typeref_as_def( &self, - ty: &BastType<'a>, + ty: &BastType, owning_path: &str, - ) -> Result, AlkTypeError> { + ) -> Result { match ty { BastType::Ref(r) => self.resolve_ref(r), BastType::Struct(s) => Ok(BastDef { - name: "", + name: String::new(), kind: BastDefKind::Struct(s.clone()), - source: s.source, + source: s.source.clone(), }), BastType::Union(u) => Ok(BastDef { - name: "", + name: String::new(), kind: BastDefKind::Union(u.clone()), - source: u.source, + source: u.source.clone(), }), BastType::Enum(e) => Ok(BastDef { - name: "", + name: String::new(), kind: BastDefKind::Enum(e.clone()), - source: e.source, + source: e.source.clone(), }), BastType::Primitive(_) | BastType::Array(_) | BastType::Record(_) => { Err(AlkTypeError::Schema(format!( @@ -162,7 +167,7 @@ impl<'a> BastDoc<'a> { } } - fn lookup_def_raw(root: &'a Value, name: &str) -> Result<&'a Value, AlkTypeError> { + fn lookup_def_raw<'d>(root: &'d Value, name: &str) -> Result<&'d Value, AlkTypeError> { let defs = root .get(DEFS_KEY) .and_then(Value::as_object) @@ -180,33 +185,33 @@ impl<'a> BastDoc<'a> { /// arrays, and records only appear as field/element/value types /// ([`BastType`]), not as named definitions. #[derive(Debug, Clone)] -pub struct BastDef<'a> { - name: &'a str, - kind: BastDefKind<'a>, - source: &'a Value, +pub struct BastDef { + name: String, + kind: BastDefKind, + source: Value, } -impl<'a> BastDef<'a> { +impl BastDef { /// The `$defs` key this definition was registered under. Empty /// string for synthetic definitions produced by /// [`BastDoc::resolve_typeref_as_def`] from an inline type. - pub fn name(&self) -> &'a str { - self.name + pub fn name(&self) -> &str { + &self.name } /// The typed kind — struct, union, or enum. - pub fn kind(&self) -> &BastDefKind<'a> { + pub fn kind(&self) -> &BastDefKind { &self.kind } /// The raw JSON node this definition was parsed from. Retained for /// consumers that need properties the typed view doesn't expose yet /// (e.g. the layout engines during the step 4 migration). - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } - fn parse(node: &'a Value, name: &'a str, path: &str) -> Result { + fn parse(node: &Value, name: &str, path: &str) -> Result { let kind_str = node .get("kind") .and_then(Value::as_str) @@ -233,25 +238,25 @@ impl<'a> BastDef<'a> { } }; Ok(Self { - name, + name: name.to_string(), kind, - source: node, + source: node.clone(), }) } } /// The typed kind of a [`BastDef`]. #[derive(Debug, Clone)] -pub enum BastDefKind<'a> { +pub enum BastDefKind { /// `kind: "struct"` — an ordered list of fields. - Struct(BastStruct<'a>), + Struct(BastStruct), /// `kind: "union"` — discriminator + mapping of variant TypeRefs. - Union(BastUnion<'a>), + Union(BastUnion), /// `kind: "enum"` — a non-empty list of string values. - Enum(BastEnum<'a>), + Enum(BastEnum), } -impl<'a> BastDefKind<'a> { +impl BastDefKind { /// The [`AlkTypeKind`] of this definition. pub fn alk_kind(&self) -> AlkTypeKind { match self { @@ -269,14 +274,14 @@ impl<'a> BastDefKind<'a> { /// ([bast-format.md §Design Principles](../docs/architecture/bast-format.md#design-principles) /// #4). #[derive(Debug, Clone)] -pub struct BastStruct<'a> { +pub struct BastStruct { endian: Endian, align: Option, - fields: Vec>, - source: &'a Value, + fields: Vec, + source: Value, } -impl<'a> BastStruct<'a> { +impl BastStruct { /// The struct-level default endianness. Defaults to /// [`Endian::Little`] when absent (ADR-003). pub fn endian(&self) -> Endian { @@ -290,16 +295,16 @@ impl<'a> BastStruct<'a> { } /// The ordered field list. Array position is field order. - pub fn fields(&self) -> &[BastField<'a>] { + pub fn fields(&self) -> &[BastField] { &self.fields } /// The raw JSON node this struct was parsed from. - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { let endian = parse_endian_opt(node).unwrap_or(Endian::Little); let align = parse_align(node); let raw_fields = node @@ -319,32 +324,33 @@ impl<'a> BastStruct<'a> { endian, align, fields, - source: node, + source: node.clone(), }) } } /// A field within a struct or field-name-discriminator union. #[derive(Debug, Clone)] -pub struct BastField<'a> { - name: &'a str, - ty: BastType<'a>, +pub struct BastField { + name: String, + ty: BastType, endian: Option, align: Option, encoding: VariableEncoding, max_length: Option, - source: &'a Value, + source: Value, } -impl<'a> BastField<'a> { +impl BastField { /// Construct a synthetic field wrapping `ty` with no annotations. /// Used internally by the materializer for array elements, record /// values, and union variants — these are TypeRefs, not fields, so /// they don't carry field-level annotations. `source` is the /// `BastType`'s source node (or `&Value::Null` for primitives). - pub(crate) fn synthetic(ty: BastType<'a>, source: &'a Value) -> Self { + #[allow(dead_code)] + pub(crate) fn synthetic(ty: BastType, source: Value) -> Self { Self { - name: "", + name: String::new(), ty, endian: None, align: None, @@ -356,12 +362,12 @@ impl<'a> BastField<'a> { /// The field name. Guaranteed to match `^[a-zA-Z_][a-zA-Z0-9_]*$` by /// the meta-schema; the parser does not re-check the pattern. - pub fn name(&self) -> &'a str { - self.name + pub fn name(&self) -> &str { + &self.name } /// The field's type — a primitive, `$ref`, array, or record. - pub fn ty(&self) -> &BastType<'a> { + pub fn ty(&self) -> &BastType { &self.ty } @@ -391,8 +397,8 @@ impl<'a> BastField<'a> { } /// The raw JSON node this field was parsed from. - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } /// The effective endianness: this field's override, or the container @@ -401,7 +407,7 @@ impl<'a> BastField<'a> { self.endian.unwrap_or(default) } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { let name = node .get("name") .and_then(Value::as_str) @@ -423,13 +429,13 @@ impl<'a> BastField<'a> { let encoding = parse_encoding(node); let max_length = parse_max_length(node); Ok(Self { - name, + name: name.to_string(), ty, endian, align, encoding, max_length, - source: node, + source: node.clone(), }) } } @@ -440,52 +446,52 @@ impl<'a> BastField<'a> { /// Variant `$ref`s are resolved **lazily** by the materializer/validator /// via [`BastDoc::resolve_typeref`] — no compile-time inlining. #[derive(Debug, Clone)] -pub struct BastUnion<'a> { +pub struct BastUnion { endian: Endian, - discriminator: BastDiscriminator<'a>, - fields: Vec>, - mapping: Vec<(&'a str, BastType<'a>)>, - source: &'a Value, + discriminator: BastDiscriminator, + fields: Vec, + mapping: Vec<(String, BastType)>, + source: Value, } -impl<'a> BastUnion<'a> { +impl BastUnion { /// The union-level default endianness for variant fields. pub fn endian(&self) -> Endian { self.endian } /// The discriminator — byte-offset or field-name. - pub fn discriminator(&self) -> &BastDiscriminator<'a> { + pub fn discriminator(&self) -> &BastDiscriminator { &self.discriminator } /// The optional `fields` array. Only valid with field-name /// discriminators (D-BAST-005); empty for byte-offset discriminators. - pub fn fields(&self) -> &[BastField<'a>] { + pub fn fields(&self) -> &[BastField] { &self.fields } /// The `mapping` entries, in document order. Each key is a /// stringified discriminator value; each value is the variant /// [`BastType`] (typically a `$ref`). - pub fn mapping(&self) -> &[(&'a str, BastType<'a>)] { + pub fn mapping(&self) -> &[(String, BastType)] { &self.mapping } /// Look up the variant [`BastType`] for a discriminator value. - pub fn variant_for(&self, key: &str) -> Option<&BastType<'a>> { + pub fn variant_for(&self, key: &str) -> Option<&BastType> { self.mapping .iter() - .find(|(k, _)| *k == key) + .find(|(k, _)| k == key) .map(|(_, v)| v) } /// The raw JSON node this union was parsed from. - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { let endian = parse_endian_opt(node).unwrap_or(Endian::Little); let discriminator = BastDiscriminator::parse(node, path)?; let raw_fields = node.get("fields").and_then(Value::as_array); @@ -524,7 +530,7 @@ impl<'a> BastUnion<'a> { for (key, value) in raw_mapping.iter() { let entry_path = format!("{path}.mapping[{key}]"); let ty = BastType::parse(value, &entry_path)?; - mapping.push((key.as_str(), ty)); + mapping.push((key.clone(), ty)); } if mapping.is_empty() { return Err(AlkTypeError::Schema(format!( @@ -536,14 +542,14 @@ impl<'a> BastUnion<'a> { discriminator, fields, mapping, - source: node, + source: node.clone(), }) } } /// A union discriminator — byte-offset or field-name (ADR-003, D-BAST-005). #[derive(Debug, Clone)] -pub enum BastDiscriminator<'a> { +pub enum BastDiscriminator { /// A fixed-size integer at a known byte offset. Mapping keys are /// stringified integers. Byte { @@ -557,12 +563,12 @@ pub enum BastDiscriminator<'a> { /// declares the discriminator field alongside any shared fields. Field { /// The field name that holds the discriminator value. - name: &'a str, + name: String, }, } -impl<'a> BastDiscriminator<'a> { - fn parse(union_node: &'a Value, path: &str) -> Result { +impl BastDiscriminator { + fn parse(union_node: &Value, path: &str) -> Result { let disc = union_node.get("discriminator").ok_or_else(|| { AlkTypeError::Schema(format!( "bast: union at {path} has no `discriminator`" @@ -613,7 +619,9 @@ impl<'a> BastDiscriminator<'a> { "bast: field discriminator at {path} has no `name`" )) })?; - Ok(BastDiscriminator::Field { name }) + Ok(BastDiscriminator::Field { + name: name.to_string(), + }) } other => Err(AlkTypeError::Schema(format!( "bast: unknown discriminator kind {other:?} at {path}" @@ -631,24 +639,24 @@ impl<'a> BastDiscriminator<'a> { /// built-in `enum` keyword checked string membership but the materializer /// emitted a numeric index that never matched. #[derive(Debug, Clone)] -pub struct BastEnum<'a> { - values: Vec<&'a str>, - source: &'a Value, +pub struct BastEnum { + values: Vec, + source: Value, } -impl<'a> BastEnum<'a> { +impl BastEnum { /// The declared values, in declaration order. Non-empty (enforced /// by the meta-schema's `minItems: 1` and re-checked here). - pub fn values(&self) -> &[&'a str] { + pub fn values(&self) -> &[String] { &self.values } /// The raw JSON node this enum was parsed from. - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { let raw_values = node .get("values") .and_then(Value::as_array) @@ -669,11 +677,11 @@ impl<'a> BastEnum<'a> { "bast: enum at {path} values[{i}] is not a string" )) })?; - values.push(s); + values.push(s.to_string()); } Ok(Self { values, - source: node, + source: node.clone(), }) } } @@ -688,25 +696,25 @@ impl<'a> BastEnum<'a> { /// `enum`) appear inline here when a field/element/value is an anonymous /// composite; named composites go through [`BastType::Ref`]. #[derive(Debug, Clone)] -pub enum BastType<'a> { +pub enum BastType { /// A built-in primitive (`"uint32"`, `"string"`, etc.). Primitive(AlkTypeKind), /// A `$ref` to a named `$defs` entry: `{ "$ref": "#/$defs/Name" }`. - Ref(BastRef<'a>), + Ref(BastRef), /// `kind: "array"` — a fixed-size array (count required in v1, /// D-BAST-004). - Array(BastArray<'a>), + Array(BastArray), /// `kind: "record"` — a string-keyed map. - Record(BastRecord<'a>), + Record(BastRecord), /// An inline `kind: "struct"`. - Struct(BastStruct<'a>), + Struct(BastStruct), /// An inline `kind: "union"`. - Union(BastUnion<'a>), + Union(BastUnion), /// An inline `kind: "enum"`. - Enum(BastEnum<'a>), + Enum(BastEnum), } -impl<'a> BastType<'a> { +impl BastType { /// The [`AlkTypeKind`] of this type. For [`Ref`](Self::Ref), this is /// [`AlkTypeKind::Struct`] — the meta-schema only allows /// struct/union/enum as named definitions, so a `$ref` always lands @@ -724,7 +732,7 @@ impl<'a> BastType<'a> { } } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { if let Some(s) = node.as_str() { let k = AlkTypeKind::from_bast_str(s)?; return Ok(BastType::Primitive(k)); @@ -759,7 +767,7 @@ impl<'a> BastType<'a> { } } - fn from_def(def: BastDef<'a>) -> Self { + fn from_def(def: BastDef) -> Self { match def.kind { BastDefKind::Struct(s) => BastType::Struct(s), BastDefKind::Union(u) => BastType::Union(u), @@ -774,18 +782,18 @@ impl<'a> BastType<'a> { /// ([`BastDoc::lookup_def`]); the v0.1.0 engine needed a normalization /// pass for TypeBox's bare-name refs, but BAST forbids them. #[derive(Debug, Clone)] -pub struct BastRef<'a> { - name: &'a str, +pub struct BastRef { + name: String, } -impl<'a> BastRef<'a> { +impl BastRef { /// The referenced `$defs` entry name (e.g. `Read` for /// `#/$defs/Read`). - pub fn name(&self) -> &'a str { - self.name + pub fn name(&self) -> &str { + &self.name } - fn parse(ref_path: &'a str, path: &str) -> Result { + fn parse(ref_path: &str, path: &str) -> Result { let name = ref_path.strip_prefix(REF_PREFIX).ok_or_else(|| { AlkTypeError::Schema(format!( "bast: $ref at {path} is {ref_path:?}, expected \"#/$defs/\"" @@ -796,7 +804,9 @@ impl<'a> BastRef<'a> { "bast: $ref at {path} has an empty name after \"#/$defs/\"" ))); } - Ok(Self { name }) + Ok(Self { + name: name.to_string(), + }) } } @@ -807,15 +817,15 @@ impl<'a> BastRef<'a> { /// element type is itself a [`BastType`], so nested `$ref`s work: /// `{ "kind": "array", "element": { "$ref": "#/$defs/Point" }, "count": 4 }`. #[derive(Debug, Clone)] -pub struct BastArray<'a> { - element: Box>, +pub struct BastArray { + element: Box, count: usize, - source: &'a Value, + source: Value, } -impl<'a> BastArray<'a> { +impl BastArray { /// The element type. - pub fn element(&self) -> &BastType<'a> { + pub fn element(&self) -> &BastType { &self.element } @@ -825,11 +835,11 @@ impl<'a> BastArray<'a> { } /// The raw JSON node this array type was parsed from. - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { let element_node = node.get("element").ok_or_else(|| { AlkTypeError::Schema(format!( "bast: array at {path} has no `element`" @@ -844,7 +854,7 @@ impl<'a> BastArray<'a> { Ok(Self { element: Box::new(element), count, - source: node, + source: node.clone(), }) } } @@ -855,23 +865,23 @@ impl<'a> BastArray<'a> { /// `[count: u32][key_len: u32][key_bytes][value]...` repeated `count` /// times (per [bast-format.md §Record](../docs/architecture/bast-format.md#record)). #[derive(Debug, Clone)] -pub struct BastRecord<'a> { - values: Box>, - source: &'a Value, +pub struct BastRecord { + values: Box, + source: Value, } -impl<'a> BastRecord<'a> { +impl BastRecord { /// The value type. - pub fn values(&self) -> &BastType<'a> { + pub fn values(&self) -> &BastType { &self.values } /// The raw JSON node this record type was parsed from. - pub fn source(&self) -> &'a Value { - self.source + pub fn source(&self) -> &Value { + &self.source } - fn parse(node: &'a Value, path: &str) -> Result { + fn parse(node: &Value, path: &str) -> Result { let values_node = node.get("values").ok_or_else(|| { AlkTypeError::Schema(format!( "bast: record at {path} has no `values`" @@ -880,7 +890,7 @@ impl<'a> BastRecord<'a> { let values = BastType::parse(values_node, &format!("{path}.values"))?; Ok(Self { values: Box::new(values), - source: node, + source: node.clone(), }) } } @@ -954,7 +964,7 @@ fn parse_usize_field( } } -impl<'a> std::fmt::Display for BastType<'a> { +impl std::fmt::Display for BastType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BastType::Primitive(k) => write!(f, "{}", k.to_bast_str()), @@ -973,7 +983,7 @@ mod tests { use super::*; use serde_json::json; - fn doc_from<'a>(root: &'a Value, root_name: &'a str) -> BastDoc<'a> { + fn doc_from(root: &Value, root_name: &str) -> BastDoc { BastDoc::new(root, root_name).expect("bast doc") } @@ -1719,7 +1729,7 @@ mod tests { let arr = BastType::Array(BastArray { element: Box::new(BastType::Primitive(AlkTypeKind::Float32)), count: 3, - source: &Value::Null, + source: Value::Null, }); assert_eq!(format!("{arr}"), "array[3]"); } diff --git a/src/bast_validation.rs b/src/bast_validation.rs index 9192290..857d489 100644 --- a/src/bast_validation.rs +++ b/src/bast_validation.rs @@ -61,7 +61,7 @@ pub(crate) const DISCRIMINATOR_KEY: &str = "__discriminator"; /// constraint, or `Err(AlkTypeError::Schema(...))` if the BAST document /// is malformed (a dangling `$ref`, a cyclic `$ref`, a missing `values` /// array, etc.). -pub fn validate_value(doc: &BastDoc<'_>, value: &Value) -> Result<(), AlkTypeError> { +pub fn validate_value(doc: &BastDoc, value: &Value) -> Result<(), AlkTypeError> { let plan = crate::validation_plan::ValidationPlan::compile(doc)?; plan.validate(value) } @@ -85,7 +85,7 @@ mod tests { use crate::read_plan::ReadPlan; use serde_json::json; - fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> { + fn doc_from(root: &Value, name: &str) -> BastDoc { BastDoc::new(root, name).expect("bast doc") } @@ -101,7 +101,7 @@ mod tests { fn materialize_and_validate( root: &Value, - doc: &BastDoc<'_>, + doc: &BastDoc, buffer: &[u8], ) -> Result<(), AlkTypeError> { let plan = ReadPlan::compile(root, doc.root_name())?; diff --git a/src/engine.rs b/src/engine.rs index 112fbba..420c539 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -87,8 +87,7 @@ pub struct AlkTypeEngine { json_validator: Option, validation_plan: Arc, endian: Endian, - bast_doc: Value, - root_name: String, + doc: BastDoc, } impl AlkTypeEngine { @@ -114,10 +113,10 @@ impl AlkTypeEngine { /// BAST document — BAST describes bytes, not JSON shape. It may be /// authored separately or derived from BAST via future codegen. /// - /// The engine retains a clone of the BAST `Value` so that - /// [`AlkTypeEngine::sequential_reader`] and - /// [`AlkTypeEngine::read_field`] can re-parse the typed tree on - /// demand without lifetime entanglement with the caller's `Value`. + /// The engine retains an owned [`BastDoc`] (ADR-012 §2a) so that + /// [`AlkTypeEngine::validate_bytes`] and + /// [`AlkTypeEngine::read_field`] can walk the typed tree without + /// re-parsing the raw document per call. /// /// # Errors /// @@ -171,8 +170,7 @@ impl AlkTypeEngine { json_validator, validation_plan, endian, - bast_doc: bast_doc.clone(), - root_name: root_name.to_string(), + doc, }) } @@ -181,6 +179,11 @@ impl AlkTypeEngine { self.endian } + /// The root type name this engine was compiled with (D-BAST-001). + pub fn root_name(&self) -> &str { + self.doc.root_name() + } + /// The layout mode this engine was compiled with. pub fn mode(&self) -> LayoutMode { match self.layout { @@ -302,13 +305,12 @@ impl AlkTypeEngine { /// (D-BAST-009 — the variant type stays uniform with the /// `validate_json` path). pub fn validate_bytes(&self, buffer: &[u8]) -> Result<(), AlkTypeError> { - let doc = BastDoc::new(&self.bast_doc, &self.root_name)?; let value = match &self.layout { Layout::Packed { plan, .. } => { materialize::materialize_packed(plan, buffer)? } Layout::Aligned { offset_map } => { - materialize::materialize_aligned(&doc, buffer, offset_map)? + materialize::materialize_aligned(&self.doc, buffer, offset_map)? } }; self.validation_plan.validate(&value) @@ -369,8 +371,7 @@ impl AlkTypeEngine { field_path: field_path.to_string(), reason: "field not found in offset map".to_string(), })?; - let doc = BastDoc::new(&self.bast_doc, &self.root_name)?; - let leaf = lookup_leaf_field(&doc, field_path).ok_or_else(|| AlkTypeError::Offset { + let leaf = lookup_leaf_field(&self.doc, field_path).ok_or_else(|| AlkTypeError::Offset { field_path: field_path.to_string(), reason: "field schema not found in BAST tree or has no primitive kind".to_string(), })?; @@ -502,8 +503,7 @@ impl AlkTypeEngine { field_path: field_path.to_string(), reason: "field not found in offset map".to_string(), })?; - let doc = BastDoc::new(&self.bast_doc, &self.root_name)?; - let leaf = lookup_leaf_field(&doc, field_path).ok_or_else(|| AlkTypeError::Offset { + let leaf = lookup_leaf_field(&self.doc, field_path).ok_or_else(|| AlkTypeError::Offset { field_path: field_path.to_string(), reason: "field schema not found in BAST tree or has no primitive kind".to_string(), })?; @@ -583,7 +583,7 @@ impl fmt::Debug for AlkTypeEngine { .field("layout", &self.layout) .field("json_validator", &self.json_validator.as_ref().map(|_| "")) .field("endian", &self.endian) - .field("root_name", &self.root_name) + .field("root_name", &self.root_name()) .finish() } } @@ -605,9 +605,9 @@ struct LeafFieldInfo { /// `fields` at each step, resolving `$ref`s via [`BastDoc::resolve_typeref`]. /// Array element segments (`field[i]`) are not handled here — /// `read_field`/`write_field` only address leaf fields. -fn lookup_leaf_field(doc: &BastDoc<'_>, field_path: &str) -> Option { +fn lookup_leaf_field(doc: &BastDoc, field_path: &str) -> Option { let root_def = doc.root_def(); - let mut current_struct: BastStruct<'_> = match root_def.kind() { + let mut current_struct: BastStruct = match root_def.kind() { BastDefKind::Struct(s) => s.clone(), _ => return None, }; @@ -1399,6 +1399,11 @@ mod tests { AlkTypeEngine::compile(&doc, "S", LayoutMode::Packed, None).expect("compile"); fn assert_send_sync(_: &T) {} assert_send_sync(engine.validation_plan()); + // The engine itself must stay Send + Sync now that it holds the + // owned BastDoc (ADR-012 §2a) — alkcall shares engines across + // hub/spoke threads. + fn assert_engine_send_sync(_: &T) {} + assert_engine_send_sync(&engine); let shared:std::sync::Arc<_> = engine.validation_plan().clone(); let handle = std::thread::spawn(move || shared.validate(&json!({"id": 1})).is_ok()); assert!(handle.join().expect("join")); diff --git a/src/layout_builder.rs b/src/layout_builder.rs index 7476478..fd35053 100644 --- a/src/layout_builder.rs +++ b/src/layout_builder.rs @@ -214,7 +214,7 @@ impl LayoutBuilder { /// Mutable context threaded through the recursive layout computation. struct BuildCtx<'d> { - doc: &'d BastDoc<'d>, + doc: &'d BastDoc, var_sizes: &'d HashMap, fields: Vec<(String, FieldPosition)>, } @@ -227,7 +227,7 @@ impl<'d> BuildCtx<'d> { /// top level). fn walk_struct( &mut self, - struct_node: &BastStruct<'d>, + struct_node: &BastStruct, prefix: &str, offset: &mut usize, ) -> Result<(), AlkTypeError> { @@ -246,7 +246,7 @@ impl<'d> BuildCtx<'d> { /// appending any field paths to `self.fields`. fn walk_field( &mut self, - field: &BastField<'d>, + field: &BastField, field_path: &str, offset: &mut usize, ) -> Result<(), AlkTypeError> { @@ -330,7 +330,7 @@ impl<'d> BuildCtx<'d> { /// supported in v1. fn walk_array( &mut self, - array: &BastArray<'d>, + array: &BastArray, field_path: &str, offset: &mut usize, ) -> Result<(), AlkTypeError> { @@ -394,7 +394,7 @@ impl<'d> BuildCtx<'d> { /// Compute the layout for a `BastUnion` field. fn walk_union( &mut self, - union_node: &BastUnion<'d>, + union_node: &BastUnion, field_path: &str, offset: &mut usize, ) -> Result<(), AlkTypeError> { @@ -415,7 +415,7 @@ impl<'d> BuildCtx<'d> { offset: &mut usize, disc_type: AlkTypeKind, disc_off: usize, - union_node: &BastUnion<'d>, + union_node: &BastUnion, ) -> Result<(), AlkTypeError> { let disc_size = disc_type.type_size().ok_or_else(|| AlkTypeError::Offset { field_path: field_path.to_string(), @@ -488,7 +488,7 @@ impl<'d> BuildCtx<'d> { &mut self, field_path: &str, offset: &mut usize, - union_node: &BastUnion<'d>, + union_node: &BastUnion, ) -> Result<(), AlkTypeError> { let variant_key = format!("{field_path}.{VARIANT_KEY}"); let variant_index = diff --git a/src/lib.rs b/src/lib.rs index d053545..5d17ddd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,9 @@ //! //! - **BAST parser** ([`bast`]): Typed tree over a BAST document — //! `BastDoc`/`BastDef`/`BastStruct`/`BastField`/`BastType`/etc. -//! Borrows from the source `serde_json::Value` without cloning field -//! data. +//! Owns its data (ADR-012 §2a) so consumers (`AlkTypeEngine`, +//! `LayoutBuilder`) can store the parsed tree without lifetime +//! entanglement; the clone happens once at `BastDoc::new`. //! - **Layout engine** ([`offset_map`], [`layout_builder`], //! [`sequential_reader`], [`read_plan`]): Two layout modes — aligned //! static for mmap-friendly formats, packed sequential for protocol diff --git a/src/materialize.rs b/src/materialize.rs index ca0a9ad..7601f07 100644 --- a/src/materialize.rs +++ b/src/materialize.rs @@ -460,7 +460,7 @@ fn materialize_plan_record( /// from the same BAST document. Endianness is read from the root struct's /// `endian` annotation (defaults to little-endian). pub fn materialize_aligned( - doc: &BastDoc<'_>, + doc: &BastDoc, buffer: &[u8], offset_map: &crate::offset_map::OffsetMap, ) -> Result { @@ -480,8 +480,8 @@ pub fn materialize_aligned( fn materialize_struct_packed( - doc: &BastDoc<'_>, - struct_node: &BastStruct<'_>, + doc: &BastDoc, + struct_node: &BastStruct, path_prefix: &str, endian: Endian, buffer: &[u8], @@ -502,8 +502,8 @@ fn materialize_struct_packed( } fn materialize_field_packed( - doc: &BastDoc<'_>, - field: &BastField<'_>, + doc: &BastDoc, + field: &BastField, field_path: &str, endian: Endian, buffer: &[u8], @@ -512,13 +512,12 @@ fn materialize_field_packed( let field_endian = field.effective_endian(endian); let ty = field.ty(); let resolved = doc.resolve_typeref(ty)?; - materialize_typeref_packed(doc, &resolved, field, field_path, field_endian, buffer, offset) + materialize_typeref_packed(doc, &resolved, field_path, field_endian, buffer, offset) } fn materialize_typeref_packed( - doc: &BastDoc<'_>, - ty: &BastType<'_>, - _field: &BastField<'_>, + doc: &BastDoc, + ty: &BastType, field_path: &str, endian: Endian, buffer: &[u8], @@ -626,8 +625,8 @@ fn materialize_typeref_packed( } fn materialize_array_packed( - doc: &BastDoc<'_>, - array: &BastArray<'_>, + doc: &BastDoc, + array: &BastArray, field_path: &str, endian: Endian, buffer: &[u8], @@ -642,7 +641,6 @@ fn materialize_array_packed( let value = materialize_typeref_packed( doc, &resolved_elem, - &dummy_field_for(&resolved_elem), &path, endian, buffer, @@ -654,8 +652,8 @@ fn materialize_array_packed( } fn materialize_union_packed( - doc: &BastDoc<'_>, - union_node: &BastUnion<'_>, + doc: &BastDoc, + union_node: &BastUnion, field_path: &str, endian: Endian, buffer: &[u8], @@ -709,7 +707,6 @@ fn materialize_union_packed( let variant_value = materialize_typeref_packed( doc, &resolved_variant, - &dummy_field_for(&resolved_variant), field_path, endian, buffer, @@ -744,7 +741,6 @@ fn materialize_union_packed( let variant_value = materialize_typeref_packed( doc, &resolved_variant, - &dummy_field_for(&resolved_variant), field_path, endian, buffer, @@ -760,8 +756,8 @@ fn materialize_union_packed( } fn materialize_record_packed( - doc: &BastDoc<'_>, - record: &BastRecord<'_>, + doc: &BastDoc, + record: &BastRecord, field_path: &str, endian: Endian, buffer: &[u8], @@ -784,7 +780,6 @@ fn materialize_record_packed( let val = materialize_typeref_packed( doc, &resolved_values, - &dummy_field_for(&resolved_values), &val_path, endian, buffer, @@ -842,8 +837,8 @@ fn number_from_f64(f: f64, field_path: &str) -> Result { /// sequential walks for composites (Record) whose internal layout can't /// be precomputed into the static offset map. fn materialize_struct_aligned( - doc: &BastDoc<'_>, - struct_node: &BastStruct<'_>, + doc: &BastDoc, + struct_node: &BastStruct, path_prefix: &str, offset_map: &crate::offset_map::OffsetMap, endian: Endian, @@ -891,7 +886,6 @@ fn materialize_struct_aligned( materialize_typeref_packed( doc, &resolved, - field, &path, field_endian, buffer, @@ -917,8 +911,8 @@ fn materialize_struct_aligned( /// at its `vals[i]` offset-map entry. Array elements are always fixed-size /// in aligned mode (the offset map rejects variable-length elements). fn materialize_array_aligned( - doc: &BastDoc<'_>, - array: &BastArray<'_>, + doc: &BastDoc, + array: &BastArray, field_path: &str, offset_map: &crate::offset_map::OffsetMap, endian: Endian, @@ -949,9 +943,9 @@ fn materialize_array_aligned( /// trailing NUL bytes are trimmed. /// - inline length-prefixed (default): a 4-byte length prefix at `start`. fn materialize_variable_aligned( - doc: &BastDoc<'_>, - field: &BastField<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + field: &BastField, + ty: &BastType, field_path: &str, range: &crate::offset_map::ByteRange, endian: Endian, @@ -1010,49 +1004,15 @@ fn materialize_variable_aligned( } fn materialize_leaf_at( - doc: &BastDoc<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + ty: &BastType, field_path: &str, offset: usize, endian: Endian, buffer: &[u8], ) -> Result { let mut pos = offset; - materialize_typeref_packed( - doc, - ty, - &dummy_field_for(ty), - field_path, - endian, - buffer, - &mut pos, - ) -} - -/// Construct a synthetic `BastField` borrowing from a resolved -/// `BastType`'s source node. Used when materializing array elements / -/// record values / union variants — these are TypeRefs, not fields, so -/// they don't carry field-level annotations. The synthetic field -/// inherits no annotations (which is correct: `maxLength`/`encoding`/ -/// `endian`/`align` are field-level properties, and array elements and -/// record values don't have them in BAST). -/// -/// The `BastField::parse` path validates the node; this helper bypasses -/// re-validation for a node we've already resolved. The returned field -/// borrows from the `BastType`'s source. -fn dummy_field_for<'d>(ty: &BastType<'d>) -> BastField<'d> { - let source = ty_source(ty); - BastField::synthetic(ty.clone(), source) -} - -fn ty_source<'d>(ty: &BastType<'d>) -> &'d serde_json::Value { - match ty { - BastType::Struct(s) => s.source(), - BastType::Union(u) => u.source(), - BastType::Array(a) => a.source(), - BastType::Record(r) => r.source(), - _ => &serde_json::Value::Null, - } + materialize_typeref_packed(doc, ty, field_path, endian, buffer, &mut pos) } #[cfg(test)] @@ -1060,13 +1020,13 @@ mod tests { use super::*; use serde_json::json; - fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> { + fn doc_from(root: &Value, name: &str) -> BastDoc { BastDoc::new(root, name).expect("bast doc") } fn materialize_packed_strict( root: &Value, - doc: &BastDoc<'_>, + doc: &BastDoc, buffer: &[u8], ) -> Result { let plan = ReadPlan::compile(root, doc.root_name())?; @@ -1457,7 +1417,7 @@ mod tests { // ----- Aligned-mode materialization (M2 fixes) ---------------------- fn materialize_aligned_strict( - doc: &BastDoc<'_>, + doc: &BastDoc, buffer: &[u8], ) -> Result { let offset_map = crate::offset_map::OffsetMap::compute(doc)?; diff --git a/src/offset_map.rs b/src/offset_map.rs index db305dd..80cf609 100644 --- a/src/offset_map.rs +++ b/src/offset_map.rs @@ -76,7 +76,7 @@ impl OffsetMap { /// Returns [`AlkTypeError::Offset`] for unsupported type combinations /// encountered during the walk (e.g. unions, which are rejected in /// aligned mode per ADR-008). - pub fn compute<'a>(doc: &'a BastDoc<'a>) -> Result { + pub fn compute(doc: &BastDoc) -> Result { let root_def = doc.root_def(); let struct_node = match root_def.kind() { BastDefKind::Struct(s) => s, @@ -133,7 +133,7 @@ impl OffsetMap { /// reference to the BAST doc for `$ref` resolution. Grouping these /// keeps the recursive helper signatures small. struct ComputeCtx<'d> { - doc: &'d BastDoc<'d>, + doc: &'d BastDoc, fields: Vec<(String, ByteRange)>, offset: usize, } @@ -158,7 +158,7 @@ impl<'d> ComputeCtx<'d> { /// a natural alignment larger than the default. fn compute_struct( &mut self, - struct_node: &BastStruct<'d>, + struct_node: &BastStruct, prefix: &str, parent_struct_align: usize, ) -> Result<(usize, usize), AlkTypeError> { @@ -210,7 +210,7 @@ impl<'d> ComputeCtx<'d> { /// and appending any field paths to `self.fields`. fn compute_field( &mut self, - field: &BastField<'d>, + field: &BastField, field_path: &str, struct_default_align: usize, ) -> Result { @@ -253,7 +253,7 @@ impl<'d> ComputeCtx<'d> { fn compute_fixed_field( &mut self, kind: AlkTypeKind, - field: &BastField<'d>, + field: &BastField, field_path: &str, struct_default_align: usize, ) -> Result { @@ -282,8 +282,8 @@ impl<'d> ComputeCtx<'d> { /// then shifts the nested fields to their final positions. fn compute_struct_field( &mut self, - struct_node: &BastStruct<'d>, - field: &BastField<'d>, + struct_node: &BastStruct, + field: &BastField, field_path: &str, struct_default_align: usize, ) -> Result { @@ -321,8 +321,8 @@ impl<'d> ComputeCtx<'d> { /// Compute the layout for a `BastArray` field. fn compute_array_field( &mut self, - array: &BastArray<'d>, - field: &BastField<'d>, + array: &BastArray, + field: &BastField, field_path: &str, struct_default_align: usize, ) -> Result { @@ -396,7 +396,7 @@ impl<'d> ComputeCtx<'d> { /// - inline length-prefixing (default): a 4-byte length prefix. fn compute_variable_field( &mut self, - field: &BastField<'d>, + field: &BastField, field_path: &str, struct_default_align: usize, ) -> Result { @@ -430,7 +430,7 @@ impl<'d> ComputeCtx<'d> { } /// If the field's type is a variable-length primitive, return its kind. -fn field_variable_kind(field: &BastField<'_>) -> Option { +fn field_variable_kind(field: &BastField) -> Option { match field.ty() { BastType::Primitive(k) if k.is_variable_length() => Some(*k), _ => None, @@ -439,7 +439,7 @@ fn field_variable_kind(field: &BastField<'_>) -> Option { /// Resolve the field's alignment: field-level `align` annotation, /// then the struct default, then the natural alignment. -fn field_alignment(field: &BastField<'_>, struct_default_align: usize, natural: usize) -> usize { +fn field_alignment(field: &BastField, struct_default_align: usize, natural: usize) -> usize { if let Some(a) = field.align() { return a.max(1); } @@ -452,7 +452,7 @@ fn field_alignment(field: &BastField<'_>, struct_default_align: usize, natural: /// schemas (BAST field-level annotations live on the field, not the /// element TypeRef), so we fall back to the struct default. fn element_alignment( - elem_ty: &BastType<'_>, + elem_ty: &BastType, struct_default_align: usize, natural: usize, ) -> usize { diff --git a/src/read_plan.rs b/src/read_plan.rs index 8c86248..f3d32c2 100644 --- a/src/read_plan.rs +++ b/src/read_plan.rs @@ -257,8 +257,8 @@ fn dotted(prefix: &str, name: &str) -> String { } fn compile_struct( - doc: &BastDoc<'_>, - s: &BastStruct<'_>, + doc: &BastDoc, + s: &BastStruct, container_endian: Endian, path: &str, depth: usize, @@ -281,8 +281,8 @@ fn compile_struct( } fn compile_field_list( - doc: &BastDoc<'_>, - fields: &[BastField<'_>], + doc: &BastDoc, + fields: &[BastField], container_endian: Endian, path: &str, depth: usize, @@ -297,8 +297,8 @@ fn compile_field_list( } fn compile_field( - doc: &BastDoc<'_>, - field: &BastField<'_>, + doc: &BastDoc, + field: &BastField, container_endian: Endian, path: &str, depth: usize, @@ -320,8 +320,8 @@ fn compile_field( /// body, eagerly resolving the ref (with the cycle set guarding the /// definition-level recursion). fn compile_typeref( - doc: &BastDoc<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + ty: &BastType, container_endian: Endian, path: &str, depth: usize, @@ -344,8 +344,8 @@ fn compile_typeref( /// Compile a dereferenced type node (the `Ref` case never reaches here). fn compile_resolved( - doc: &BastDoc<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + ty: &BastType, container_endian: Endian, path: &str, depth: usize, @@ -379,8 +379,8 @@ fn compile_resolved( /// Compile a named definition's kind (the `$ref` target: struct, union, /// or enum — the parser rejects other kinds at `$defs` top level). fn compile_def_kind( - doc: &BastDoc<'_>, - kind: &BastDefKind<'_>, + doc: &BastDoc, + kind: &BastDefKind, container_endian: Endian, path: &str, depth: usize, @@ -400,8 +400,8 @@ fn compile_def_kind( } fn compile_union( - doc: &BastDoc<'_>, - u: &BastUnion<'_>, + doc: &BastDoc, + u: &BastUnion, container_endian: Endian, path: &str, depth: usize, @@ -462,8 +462,8 @@ fn compile_union( /// union (mirroring the 0.2.0 read loop's `resolve_and_walk_variant`); /// a `$ref` variant resolves through the cycle set. fn compile_variant( - doc: &BastDoc<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + ty: &BastType, container_endian: Endian, path: &str, depth: usize, @@ -515,8 +515,8 @@ fn compile_variant( } fn compile_array( - doc: &BastDoc<'_>, - a: &BastArray<'_>, + doc: &BastDoc, + a: &BastArray, container_endian: Endian, path: &str, depth: usize, @@ -537,8 +537,8 @@ fn compile_array( } fn compile_record( - doc: &BastDoc<'_>, - r: &BastRecord<'_>, + doc: &BastDoc, + r: &BastRecord, container_endian: Endian, path: &str, depth: usize, diff --git a/src/tunion.rs b/src/tunion.rs index 2fe19c9..e11da40 100644 --- a/src/tunion.rs +++ b/src/tunion.rs @@ -46,7 +46,7 @@ pub struct UnionDispatch { /// `mapping`. pub fn read_byte_discriminator( buffer: &[u8], - union_node: &BastUnion<'_>, + union_node: &BastUnion, endian: Endian, ) -> Result { let (offset, disc_type) = match union_node.discriminator() { @@ -112,12 +112,12 @@ pub fn read_byte_discriminator( /// union's `mapping`. pub fn read_field_discriminator( buffer: &[u8], - union_node: &BastUnion<'_>, + union_node: &BastUnion, disc_field_offset: usize, endian: Endian, ) -> Result { let name = match union_node.discriminator() { - BastDiscriminator::Field { name } => *name, + BastDiscriminator::Field { name } => name.as_str(), BastDiscriminator::Byte { .. } => { return Err(AlkTypeError::Schema( "read_field_discriminator requires a field-name discriminator".to_string(), @@ -193,9 +193,9 @@ pub fn read_field_discriminator( /// - [`AlkTypeError::Schema`] if the union has no `mapping` entries or /// the `key` is not present. pub fn resolve_variant<'a>( - union_node: &'a BastUnion<'a>, + union_node: &'a BastUnion, key: &str, -) -> Result<&'a BastType<'a>, AlkTypeError> { +) -> Result<&'a BastType, AlkTypeError> { union_node .variant_for(key) .ok_or_else(|| AlkTypeError::Schema(format!("unknown mapping key: {key}"))) @@ -211,7 +211,7 @@ pub fn resolve_variant<'a>( /// /// - [`AlkTypeError::Schema`] if the discriminator is a field-name /// discriminator. -pub fn discriminator_size(union_node: &BastUnion<'_>) -> Result { +pub fn discriminator_size(union_node: &BastUnion) -> Result { match union_node.discriminator() { BastDiscriminator::Byte { disc_type, .. } => match disc_type { AlkTypeKind::Uint8 => Ok(1), @@ -228,7 +228,7 @@ pub fn discriminator_size(union_node: &BastUnion<'_>) -> Result, + union_node: &BastUnion, key: &str, field_path: &str, raw_value: &str, @@ -252,7 +252,7 @@ mod tests { const LE: Endian = Endian::Little; const BE: Endian = Endian::Big; - fn doc_union<'a>(root: &'a serde_json::Value, name: &'a str) -> BastUnion<'a> { + fn doc_union(root: &serde_json::Value, name: &str) -> BastUnion { let doc = BastDoc::new(root, name).expect("bast doc"); match doc.root_def().kind() { crate::bast::BastDefKind::Union(u) => u.clone(), diff --git a/src/validation_plan.rs b/src/validation_plan.rs index d546c73..635b9e1 100644 --- a/src/validation_plan.rs +++ b/src/validation_plan.rs @@ -111,7 +111,7 @@ impl ValidationPlan { /// nests deeper than the compile depth cap (128) or contains a /// reference cycle (a compile-time rejection of self-recursive /// schemas). - pub fn compile(doc: &BastDoc<'_>) -> Result { + pub fn compile(doc: &BastDoc) -> Result { let root_def = doc.root_def(); let mut seen = BTreeSet::new(); let root = match root_def.kind() { @@ -255,7 +255,7 @@ impl ValidVariant { } } -fn compile_enum_node(e: &crate::bast::BastEnum<'_>) -> Result { +fn compile_enum_node(e: &crate::bast::BastEnum) -> Result { let count = u64::try_from(e.values().len()).map_err(|_| { AlkTypeError::Schema("bast_validation: enum values len overflows u64".to_string()) })?; @@ -280,8 +280,8 @@ fn cycle_err(name: &str, path: &str) -> AlkTypeError { /// `String`/`Bytes` leaves; never crosses a `$ref`, whose target is /// always a struct/union/enum `$defs` entry). fn compile_typeref( - doc: &BastDoc<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + ty: &BastType, max_len: Option, path: &str, depth: usize, @@ -311,8 +311,8 @@ fn compile_typeref( /// Compile a dereferenced type node (the `Ref` case never reaches here). fn compile_resolved( - doc: &BastDoc<'_>, - ty: &BastType<'_>, + doc: &BastDoc, + ty: &BastType, max_len: Option, path: &str, depth: usize, @@ -367,8 +367,8 @@ fn compile_resolved( } fn compile_struct( - doc: &BastDoc<'_>, - s: &BastStruct<'_>, + doc: &BastDoc, + s: &BastStruct, path: &str, depth: usize, seen: &mut BTreeSet, @@ -400,8 +400,8 @@ fn compile_struct( } fn compile_union( - doc: &BastDoc<'_>, - u: &BastUnion<'_>, + doc: &BastDoc, + u: &BastUnion, path: &str, depth: usize, seen: &mut BTreeSet, @@ -692,7 +692,7 @@ mod tests { use super::*; use serde_json::json; - fn doc_from<'a>(root: &'a Value, name: &'a str) -> BastDoc<'a> { + fn doc_from(root: &Value, name: &str) -> BastDoc { BastDoc::new(root, name).expect("bast doc") } diff --git a/tests/tunion_dispatch.rs b/tests/tunion_dispatch.rs index c8d5f03..5141c3b 100644 --- a/tests/tunion_dispatch.rs +++ b/tests/tunion_dispatch.rs @@ -49,7 +49,7 @@ fn sftp_like_byte_union_doc() -> serde_json::Value { }) } -fn union_of<'a>(root: &'a serde_json::Value, name: &'a str) -> BastUnion<'a> { +fn union_of(root: &serde_json::Value, name: &str) -> BastUnion { let doc = BastDoc::new(root, name).expect("bast doc"); match doc.root_def().kind() { BastDefKind::Union(u) => u.clone(),