LayoutBuilder caches the owned BastDoc (ADR-012 §2a, plan phase 4)

The builder stores doc: BastDoc + endian (the doc_value: Value +
root_name: String cache is gone); new parses the typed tree once and
build walks &self.doc — the per-build BastDoc::new re-parse
(layout_builder.rs M1) is retired.

- build's root-is-struct re-check replaces its unreachable!() with a
  clean Schema error (AGENTS.md §3 never-panic; invariant unchanged —
  new already rejects non-struct roots).
- Boxing fallout: the builder now holds the full owned tree, so
  Layout::Packed boxes it (Box<LayoutBuilder>) to keep the engine's
  Layout enum variant sizes balanced (clippy large_enum_variant).
  layout_builder() still returns Option<&LayoutBuilder> via
  auto-deref; public API unchanged.

Verification: 465 tests pass unchanged (layout_builder.rs suites
drive new/build through the public API); clippy -D warnings clean;
wasm32 release build green.
This commit is contained in:
glm-5.3-flash committed 2026-09-02 08:59:13 +00:00
1 parent c583762352
commit b7c7dbe2a1
3 files changed
+28 -17

No files matched your search

+2 -2
View File
@@ -54,7 +54,7 @@ enum Layout {
/// hands out (ADR-007 — the reader owns its cursor state, so the
/// engine is a factory, not a holder).
Packed {
builder: LayoutBuilder,
builder: Box<LayoutBuilder>,
plan: Arc<ReadPlan>,
},
/// Aligned static layout. Field offsets are precomputed in an
@@ -152,7 +152,7 @@ impl AlkTypeEngine {
let validation_plan = Arc::new(ValidationPlan::compile(&doc)?);
let layout = match mode {
LayoutMode::Packed => {
let builder = LayoutBuilder::new(bast_doc, root_name)?;
let builder = Box::new(LayoutBuilder::new(bast_doc, root_name)?);
let plan = Arc::new(ReadPlan::compile(bast_doc, root_name)?);
Layout::Packed { builder, plan }
}
+12 -14
View File
@@ -129,8 +129,7 @@ impl PackedLayout {
/// this is correct for protocol wire formats, which pack fields tightly.
#[derive(Debug)]
pub struct LayoutBuilder {
doc_value: Value,
root_name: String,
doc: BastDoc,
endian: Endian,
}
@@ -140,9 +139,9 @@ impl LayoutBuilder {
/// The root type must be a struct. Endianness is read from the
/// root struct's `endian` annotation (defaults to little-endian).
///
/// The builder retains the raw BAST `Value` and re-parses the typed
/// tree on each [`build`](Self::build) call; this is cheap (the
/// typed tree borrows from the source without cloning field data).
/// The builder parses the owned [`BastDoc`] once here (ADR-012 §2a)
/// and every [`build`](Self::build) call reuses the cached tree —
/// no re-parse, no `Value` clone per build.
///
/// # Errors
///
@@ -161,11 +160,7 @@ impl LayoutBuilder {
}
};
let endian = struct_node.endian();
Ok(Self {
doc_value: bast_doc.clone(),
root_name: root_name.to_string(),
endian,
})
Ok(Self { doc, endian })
}
/// Build the packed layout given actual data sizes for variable-length
@@ -187,14 +182,17 @@ impl LayoutBuilder {
/// sizes in `var_sizes`, missing discriminator values, or unknown
/// discriminator values.
pub fn build(&self, var_sizes: &HashMap<String, usize>) -> Result<PackedLayout, AlkTypeError> {
let doc = BastDoc::new(&self.doc_value, &self.root_name)?;
let root_def = doc.root_def();
let root_def = self.doc.root_def();
let struct_node = match root_def.kind() {
BastDefKind::Struct(s) => s,
_ => unreachable!("checked in new; doc_value is immutable"),
_ => {
return Err(AlkTypeError::Schema(
"internal: LayoutBuilder root is not a struct (checked in new)".to_string(),
));
}
};
let mut ctx = BuildCtx {
doc: &doc,
doc: &self.doc,
var_sizes,
fields: Vec::new(),
};