From ff85258d03bba76c281f63b87c278c8a57608d43 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Wed, 2 Sep 2026 07:19:54 +0000 Subject: [PATCH] Implement ReadPlan type + compile (ADR-011 step 1, plan phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure addition: the packed read-side compiled form (src/read_plan.rs) and lib.rs wiring (module + re-exports of ReadPlan, FieldPlan, CompositePlan, ReadKind, DiscriminatorPlan). No existing engine code touched — phases 2-5 wire the plan into the reader/materializer/engine. - Refined union shape (ADR-011 as refined by review #005): CompositePlan::Union { disc, shared, variants } with shared: Option> for field-disc unions and variants: Vec<(String, CompositePlan)> — no VariantPlan/VariantKind, nested-union variants work by ordinary CompositePlan recursion (restores the 0.2.0 capability the POC rejected). - by_name is BTreeMap (ADR-012 §1 Hash-derive prerequisite). - True array strides (deferred decision 4): fixed struct/nested-array elements compute their real stride via fixed_composite_size; variable-length elements stay 0. 0.2.0 returned 0 for fixed struct arrays; that behavioral change rides the 0.3.0 bump (phase 2 will surface it through SequentialReader). - Endianness: effective endian baked at every node. Parity lock: the plan propagates the referring field's effective endian into nested structs/unions — what the 0.2.0 packed reader/materializer actually do — and ignores nested containers' own endian annotations (the POC baked s.endian() there; latent divergence, never exercised by its equivalence tests). Nested-annotation tests lock this in. - Untrusted input: compile carries its own depth cap (128) + definition-level cycle set (mirrors ValidationPlan::compile), so standalone compile is safe on adversarial docs: cyclic refs, deep chains, dangling refs, non-struct roots, and non-struct/union variants all surface as AlkTypeError::Schema, never a panic. Overflow-safe stride arithmetic (checked_mul). Verification: 388 tests pass (355 existing + 33 new: every BastType arm coverage, field-disc shared/nested-union compile shape, stride computation, endian parity, cycle/depth/malformed rejection, Send + Sync static-bound assertion); clippy -D warnings clean; cargo doc zero warnings; wasm32-unknown-unknown release build green. Next: phase 2 (SequentialReader + materialize_packed consume the plan). --- docs/plans/030-compiled-forms.md | 26 +- src/lib.rs | 11 +- src/read_plan.rs | 1256 ++++++++++++++++++++++++++++++ 3 files changed, 1283 insertions(+), 10 deletions(-) create mode 100644 src/read_plan.rs diff --git a/docs/plans/030-compiled-forms.md b/docs/plans/030-compiled-forms.md index b013d83..6c5c23e 100644 --- a/docs/plans/030-compiled-forms.md +++ b/docs/plans/030-compiled-forms.md @@ -1,7 +1,7 @@ --- status: in-progress created: 2026-08-19 -last_updated: 2026-08-31 +last_updated: 2026-09-02 adr: ADR-011, ADR-012 --- @@ -113,14 +113,26 @@ accessors (signatures), `FieldValue`, `AlkTypeKind`, `AlkTypeError`, bug, the stride is behaviorally observable, and we're bumping. The plan step calls this out explicitly. Decided in phase 2. -## Phase 1 — `ReadPlan` type + `compile` (ADR-011 step 1) +## Phase 1 — `ReadPlan` type + `compile` (ADR-011 step 1) — **DONE (2026-09-02)** + +> **Status: implemented.** `src/read_plan.rs` builds the refined +> `CompositePlan::Union` shape (`shared: Option>` + +> `variants: Vec<(String, CompositePlan)>`, no `VariantPlan`/`VariantKind`) +> with eager `$ref` resolution, `BTreeMap` `by_name`, field-disc `shared` +> sub-plans, nested-union variant support, and true array strides +> (deferred decision 4 resolved: fixed struct arrays compute their real +> stride, not the 0.2.0 reader's `0`). Two parity notes recorded as +> compile-behavior locks in tests: (a) the plan propagates the +> *referring field's* effective endianness into nested structs/unions — +> exactly what the 0.2.0 packed reader/materializer do — rather than +> consulting nested containers' own `endian` annotations (the POC baked +> `s.endian()` there; its equivalence tests never covered a nested +> annotation, so the divergence was latent); (b) `compile` carries its +> own depth cap (128) + definition-level cycle set, so standalone +> `ReadPlan::compile` is untrusted-input-safe independent of the +> meta-schema and the engine's `ValidationPlan` gate. **Goal:** Add the `ReadPlan`/`FieldPlan`/`CompositePlan`/`ReadKind`/ -`DiscriminatorPlan` types and `ReadPlan::compile(&Value, &str) -> -Result`. Pure addition; no existing code touched. -This is the foundation — later phases wire it into the engine and -reader. (The POC's `VariantPlan`/`VariantKind` types are **dropped** -in the production shape — see the union-shape note below.) **ADR reference:** [ADR-011 §The `ReadPlan` shape](../architecture/decisions/011-compiled-read-plan-for-packed-mode.md#the-readplan-shape), [ADR-011 §Construction](../architecture/decisions/011-compiled-read-plan-for-packed-mode.md#construction). diff --git a/src/lib.rs b/src/lib.rs index 839cc5d..d053545 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,9 +12,10 @@ //! Borrows from the source `serde_json::Value` without cloning field //! data. //! - **Layout engine** ([`offset_map`], [`layout_builder`], -//! [`sequential_reader`]): Two layout modes — aligned static for -//! mmap-friendly formats, packed sequential for protocol wire formats. -//! All three consume the BAST typed tree. +//! [`sequential_reader`], [`read_plan`]): Two layout modes — aligned +//! static for mmap-friendly formats, packed sequential for protocol +//! wire formats. All consume the BAST typed tree; `read_plan` is the +//! packed read-side compiled form (ADR-011). //! - **Data access** ([`data_access`]): Typed read/write at computed //! offsets, zero-copy for fixed-size types. //! - **TUnion dispatch** ([`tunion`]): Byte-offset and field-name @@ -52,6 +53,7 @@ pub mod error; pub mod layout_builder; pub mod materialize; pub mod offset_map; +pub mod read_plan; pub mod schema; pub mod sequential_reader; pub mod tunion; @@ -68,6 +70,9 @@ pub use engine::{LayoutMode, AlkTypeEngine}; pub use error::AlkTypeError; pub use layout_builder::{FieldPosition, LayoutBuilder, PackedLayout}; pub use offset_map::{ByteRange, OffsetMap}; +pub use read_plan::{ + CompositePlan, DiscriminatorPlan, FieldPlan, ReadKind, ReadPlan, +}; pub use schema::{Endian, AlkTypeKind, VariableEncoding}; pub use sequential_reader::{FieldValue, SequentialReader}; pub use tunion::UnionDispatch; diff --git a/src/read_plan.rs b/src/read_plan.rs new file mode 100644 index 0000000..56c0b62 --- /dev/null +++ b/src/read_plan.rs @@ -0,0 +1,1256 @@ +//! `ReadPlan` — the compiled read-side form for packed mode (ADR-011). +//! +//! A pre-resolved tree of read instructions: every `$ref` is resolved +//! eagerly, every endianness is computed at every node, every union +//! variant is inlined. The packed read loop +//! ([`crate::sequential_reader`]) and the packed materializer +//! ([`crate::materialize`]) walk this plan instead of re-touching the +//! BAST document or the raw `Value` — one compiled form for both packed +//! read-side consumers, mirroring how [`crate::offset_map::OffsetMap`] +//! unifies the aligned read and write sides. +//! +//! ## Endianness +//! +//! The effective endianness is baked at every node at compile time: the +//! root struct's `endian` annotation is the container default, and +//! field-level `endian` overrides apply at each field. Nested struct and +//! union `endian` annotations are **not** consulted on the packed read +//! path — the 0.2.0 packed reader and materializer propagate the +//! referring field's effective endianness into nested containers, and +//! the plan preserves that behavior exactly. +//! +//! ## Array strides +//! +//! `CompositePlan::Array` carries the element stride. Fixed-size +//! elements (primitives, enums, structs whose every field has a +//! compile-time-known size) get their true stride; variable-length +//! elements get `0`, the same convention as `FieldValue::Array`. Note +//! the deliberate behavior change vs 0.2.0: the old reader returned `0` +//! for arrays of fixed-size struct elements; the plan computes the true +//! stride (0.3.0 deferred decision 4). +//! +//! ## Untrusted input +//! +//! Compile is the trust boundary: a malformed or cyclically-referencing +//! document surfaces as [`AlkTypeError::Schema`] (a depth cap plus a +//! definition-level cycle set guard the walk), never a panic +//! (AGENTS.md §3). All size/stride arithmetic is checked (AGENTS.md §4). + +use crate::bast::{ + BastArray, BastDefKind, BastDiscriminator, BastDoc, BastField, BastRecord, BastStruct, + BastType, BastUnion, +}; +use crate::error::AlkTypeError; +use crate::schema::{AlkTypeKind, Endian, VariableEncoding}; +use serde_json::Value; +use std::collections::{BTreeMap, BTreeSet}; + +/// Maximum recursion depth for the compile walk. Bounds deeply-nested +/// (or cyclically-referencing) adversarial schemas; pairs with the +/// definition-level cycle set below. The 0.2.0 read path relied on the +/// meta-schema's cycle rejection; the plan carries its own guard so +/// standalone `compile` calls are safe too. +const MAX_COMPILE_DEPTH: usize = 128; + +/// The compiled read-side form for packed mode (ADR-011). +/// +/// Built once via [`ReadPlan::compile`]; the packed read loop and packed +/// materializer walk it per buffer. Immutable owned data — `Send + +/// Sync`, shareable via `Arc` from the engine (ADR-011 §"Engine +/// integration"). `PartialEq` holds: two equal plans produce identical +/// reads over identical buffers (the compile is a pure function of the +/// document + root name). +#[derive(Debug, Clone, PartialEq)] +pub struct ReadPlan { + endian: Endian, + fields: Vec, + by_name: BTreeMap, +} + +/// One compiled struct field: the read dispatch kind, the effective +/// endianness, the field-level annotations, and (for composites) the +/// compiled body. +#[derive(Debug, Clone, PartialEq)] +pub struct FieldPlan { + name: String, + kind: ReadKind, + endian: Endian, + encoding: VariableEncoding, + max_length: Option, + body: Option, +} + +/// The read dispatch kind for a field. Composites carry their compiled +/// body in [`FieldPlan::body`]; primitives and enums read directly. +#[derive(Debug, Clone, PartialEq)] +pub enum ReadKind { + Primitive(AlkTypeKind), + Enum, + Struct, + Union, + Array, + Record, +} + +/// The compiled body of a composite node — the packed-read analog of +/// `BastType`'s composite arms. +#[derive(Debug, Clone, PartialEq)] +pub enum CompositePlan { + /// A nested struct — its own field list and container default. + Struct(ReadPlan), + /// A tagged union. `disc` locates the discriminator; `shared` + /// carries the union's declared `fields` (the discriminator field + + /// any shared fields) for the field-name-discriminator case — the + /// read loop walks `shared` first, then the selected variant's + /// `CompositePlan` starting after the shared fields. The + /// byte-offset-discriminator case has no shared fields (`None`): the + /// variant starts immediately after the discriminator's bytes. + /// A variant may itself be `CompositePlan::Union` (nested unions — + /// ordinary recursion, no separate variant type). + Union { + disc: DiscriminatorPlan, + shared: Option>, + variants: Vec<(String, CompositePlan)>, + }, + /// A fixed-count array. `element_stride` is the true stride for + /// fixed-size elements; `0` signals variable-length elements (the + /// read loop walks each element sequentially). + Array { + element: Box, + count: usize, + element_stride: usize, + }, + /// A string-keyed map; `value` is the compiled value type. + Record { + value: Box, + }, +} + +/// The compiled union discriminator (ADR-003, D-BAST-005). +#[derive(Debug, Clone, PartialEq)] +pub enum DiscriminatorPlan { + /// A fixed-size integer at a known byte offset within the union's + /// buffer. Mapping keys are stringified integers. + Byte { + offset: usize, + disc_type: AlkTypeKind, + }, + /// A field within the union's `shared` sub-plan. Mapping keys are + /// stringified field values. `field_index` indexes into + /// `CompositePlan::Union`'s `shared` field list. + Field { + name: String, + field_index: usize, + }, +} + +impl ReadPlan { + /// Compile the packed read plan for a BAST document. + /// + /// Walks the typed tree once, resolving every `$ref` eagerly, + /// computing effective endianness at every node, and inlining every + /// union variant. The root type must be a struct (matching the + /// packed read entry points). + /// + /// # Errors + /// + /// Returns [`AlkTypeError::Schema`] if the document is malformed + /// (dangling `$ref`, non-struct root, unsupported union variant), if + /// the reference graph nests deeper than the compile depth cap + /// (128), or if it contains a reference cycle. + pub fn compile(bast_doc: &Value, root_name: &str) -> Result { + let doc = BastDoc::new(bast_doc, root_name)?; + let root_def = doc.root_def(); + let struct_node = match root_def.kind() { + BastDefKind::Struct(s) => s, + other => { + return Err(AlkTypeError::Schema(format!( + "ReadPlan root must be a struct, got {kind}", + kind = other.alk_kind() + ))); + } + }; + let mut seen = BTreeSet::new(); + compile_struct(&doc, struct_node, struct_node.endian(), "", 0, &mut seen) + } + + /// The root container default endianness. + pub fn endian(&self) -> Endian { + self.endian + } + + /// The compiled root field list. + pub fn fields(&self) -> &[FieldPlan] { + &self.fields + } + + /// The index of the first root field named `name`, if any. + pub fn field_index(&self, name: &str) -> Option { + self.by_name.get(name).copied() + } +} + +impl FieldPlan { + /// The field's declared name. + pub fn name(&self) -> &str { + &self.name + } + + /// The read dispatch kind. + pub fn kind(&self) -> &ReadKind { + &self.kind + } + + /// The effective endianness (field override, else the container + /// default propagated from the root). + pub fn endian(&self) -> Endian { + self.endian + } + + /// The variable-length encoding strategy. + pub fn encoding(&self) -> VariableEncoding { + self.encoding + } + + /// The byte-length cap, if declared. + pub fn max_length(&self) -> Option { + self.max_length + } + + /// The compiled composite body, if this field is a composite. + pub fn body(&self) -> Option<&CompositePlan> { + self.body.as_ref() + } +} + +fn depth_err(path: &str) -> AlkTypeError { + AlkTypeError::Schema(format!( + "read_plan: compile depth exceeded {MAX_COMPILE_DEPTH} at {path} \ + (cyclic $ref or adversarially deep nesting)" + )) +} + +fn cycle_err(name: &str, path: &str) -> AlkTypeError { + AlkTypeError::Schema(format!( + "read_plan: cyclic $ref through {name:?} at {path}" + )) +} + +fn dotted(prefix: &str, name: &str) -> String { + if prefix.is_empty() { + name.to_string() + } else { + format!("{prefix}.{name}") + } +} + +fn compile_struct( + doc: &BastDoc<'_>, + s: &BastStruct<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result { + if depth > MAX_COMPILE_DEPTH { + return Err(depth_err(path)); + } + let fields = compile_field_list(doc, s.fields(), container_endian, path, depth, seen)?; + let mut by_name = BTreeMap::new(); + for (i, field) in fields.iter().enumerate() { + by_name.entry(field.name().to_string()).or_insert(i); + } + Ok(ReadPlan { + endian: container_endian, + fields, + by_name, + }) +} + +fn compile_field_list( + doc: &BastDoc<'_>, + fields: &[BastField<'_>], + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result, AlkTypeError> { + let mut out = Vec::with_capacity(fields.len()); + for field in fields { + let field_path = dotted(path, field.name()); + out.push(compile_field(doc, field, container_endian, &field_path, depth, seen)?); + } + Ok(out) +} + +fn compile_field( + doc: &BastDoc<'_>, + field: &BastField<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result { + let endian = field.effective_endian(container_endian); + let (kind, body) = compile_typeref(doc, field.ty(), endian, path, depth, seen)?; + Ok(FieldPlan { + name: field.name().to_string(), + kind, + endian, + encoding: field.encoding(), + max_length: field.max_length(), + body, + }) +} + +/// Compile a possibly-`$ref` type node to its read dispatch kind and +/// body, eagerly resolving the ref (with the cycle set guarding the +/// definition-level recursion). +fn compile_typeref( + doc: &BastDoc<'_>, + ty: &BastType<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result<(ReadKind, Option), AlkTypeError> { + match ty { + BastType::Ref(r) => { + let name = r.name(); + if !seen.insert(name.to_string()) { + return Err(cycle_err(name, path)); + } + let def = doc.resolve_ref(r)?; + let out = compile_def_kind(doc, def.kind(), container_endian, path, depth + 1, seen)?; + seen.remove(name); + Ok(out) + } + other => compile_resolved(doc, other, container_endian, path, depth, seen), + } +} + +/// Compile a dereferenced type node (the `Ref` case never reaches here). +fn compile_resolved( + doc: &BastDoc<'_>, + ty: &BastType<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result<(ReadKind, Option), AlkTypeError> { + match ty { + BastType::Primitive(k) => Ok((ReadKind::Primitive(*k), None)), + BastType::Enum(_) => Ok((ReadKind::Enum, None)), + BastType::Struct(s) => { + let plan = compile_struct(doc, s, container_endian, path, depth + 1, seen)?; + Ok((ReadKind::Struct, Some(CompositePlan::Struct(plan)))) + } + BastType::Union(u) => { + let plan = compile_union(doc, u, container_endian, path, depth + 1, seen)?; + Ok((ReadKind::Union, Some(plan))) + } + BastType::Array(a) => { + let plan = compile_array(doc, a, container_endian, path, depth + 1, seen)?; + Ok((ReadKind::Array, Some(plan))) + } + BastType::Record(r) => { + let plan = compile_record(doc, r, container_endian, path, depth + 1, seen)?; + Ok((ReadKind::Record, Some(plan))) + } + BastType::Ref(_) => Err(AlkTypeError::Schema(format!( + "internal: read_plan compile saw an unresolved $ref at {path}" + ))), + } +} + +/// 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<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result<(ReadKind, Option), AlkTypeError> { + match kind { + BastDefKind::Struct(s) => { + let plan = compile_struct(doc, s, container_endian, path, depth, seen)?; + Ok((ReadKind::Struct, Some(CompositePlan::Struct(plan)))) + } + BastDefKind::Union(u) => { + let plan = compile_union(doc, u, container_endian, path, depth, seen)?; + Ok((ReadKind::Union, Some(plan))) + } + BastDefKind::Enum(_) => Ok((ReadKind::Enum, None)), + } +} + +fn compile_union( + doc: &BastDoc<'_>, + u: &BastUnion<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result { + if depth > MAX_COMPILE_DEPTH { + return Err(depth_err(path)); + } + let disc = match u.discriminator() { + BastDiscriminator::Byte { offset, disc_type } => DiscriminatorPlan::Byte { + offset: *offset, + disc_type: *disc_type, + }, + BastDiscriminator::Field { name } => { + let field_index = u.fields().iter().position(|f| f.name() == *name).ok_or_else( + || { + AlkTypeError::Schema(format!( + "read_plan: union at {path} has no discriminator field {name:?}" + )) + }, + )?; + DiscriminatorPlan::Field { + name: name.to_string(), + field_index, + } + } + }; + let shared = match u.discriminator() { + BastDiscriminator::Byte { .. } => None, + BastDiscriminator::Field { .. } => { + let plan = compile_field_list(doc, u.fields(), container_endian, path, depth, seen)?; + let mut by_name = BTreeMap::new(); + for (i, field) in plan.iter().enumerate() { + by_name.entry(field.name().to_string()).or_insert(i); + } + Some(Box::new(ReadPlan { + endian: container_endian, + fields: plan, + by_name, + })) + } + }; + let mut variants = Vec::with_capacity(u.mapping().len()); + for (key, variant_ty) in u.mapping() { + let variant_path = format!("{path}.mapping[{key}]"); + let body = compile_variant(doc, variant_ty, container_endian, &variant_path, depth, seen)?; + variants.push(((*key).to_string(), body)); + } + Ok(CompositePlan::Union { + disc, + shared, + variants, + }) +} + +/// Compile one union mapping entry. A variant must be a struct or a +/// 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<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result { + if depth > MAX_COMPILE_DEPTH { + return Err(depth_err(path)); + } + match ty { + BastType::Ref(r) => { + let name = r.name(); + if !seen.insert(name.to_string()) { + return Err(cycle_err(name, path)); + } + let def = doc.resolve_ref(r)?; + let body = match def.kind() { + BastDefKind::Struct(s) => CompositePlan::Struct(compile_struct( + doc, + s, + container_endian, + path, + depth + 1, + seen, + )?), + BastDefKind::Union(u) => compile_union(doc, u, container_endian, path, depth + 1, seen)?, + BastDefKind::Enum(_) => { + return Err(AlkTypeError::Schema(format!( + "read_plan: union variant at {path} must be a struct or union, got enum" + ))); + } + }; + seen.remove(name); + Ok(body) + } + BastType::Struct(s) => Ok(CompositePlan::Struct(compile_struct( + doc, + s, + container_endian, + path, + depth + 1, + seen, + )?)), + BastType::Union(u) => compile_union(doc, u, container_endian, path, depth + 1, seen), + other => Err(AlkTypeError::Schema(format!( + "read_plan: union variant at {path} must be a struct or union, got {kind}", + kind = other.alk_kind() + ))), + } +} + +fn compile_array( + doc: &BastDoc<'_>, + a: &BastArray<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result { + if depth > MAX_COMPILE_DEPTH { + return Err(depth_err(path)); + } + let element_path = format!("{path}.element"); + let (kind, body) = compile_typeref(doc, a.element(), container_endian, &element_path, depth, seen)?; + let element = wrap_leaf(kind, body, container_endian, &element_path)?; + let element_stride = fixed_composite_size(&element).unwrap_or_default(); + Ok(CompositePlan::Array { + element: Box::new(element), + count: a.count(), + element_stride, + }) +} + +fn compile_record( + doc: &BastDoc<'_>, + r: &BastRecord<'_>, + container_endian: Endian, + path: &str, + depth: usize, + seen: &mut BTreeSet, +) -> Result { + if depth > MAX_COMPILE_DEPTH { + return Err(depth_err(path)); + } + let values_path = format!("{path}.values"); + let (kind, body) = compile_typeref(doc, r.values(), container_endian, &values_path, depth, seen)?; + let value = wrap_leaf(kind, body, container_endian, &values_path)?; + Ok(CompositePlan::Record { + value: Box::new(value), + }) +} + +/// Wrap a compiled leaf kind (primitive/enum, `body: None`) in a +/// single-field anonymous struct plan so array elements and record +/// values — which are TypeRefs, not fields — have a uniform +/// `CompositePlan` the read loop can walk. Composite bodies pass +/// through unchanged. +fn wrap_leaf( + kind: ReadKind, + body: Option, + container_endian: Endian, + path: &str, +) -> Result { + match body { + Some(body) => Ok(body), + None => match kind { + ReadKind::Primitive(_) | ReadKind::Enum => { + let field = FieldPlan { + name: String::new(), + kind, + endian: container_endian, + encoding: VariableEncoding::LengthPrefixed, + max_length: None, + body: None, + }; + Ok(CompositePlan::Struct(ReadPlan { + endian: container_endian, + fields: vec![field], + by_name: BTreeMap::new(), + })) + } + _ => Err(AlkTypeError::Schema(format!( + "internal: composite read kind at {path} compiled without a body" + ))), + }, + } +} + +/// The compile-time-known byte size of a composite node, or `None` when +/// variable-length. Used for array strides: primitives and enums +/// contribute their fixed size; structs sum their fields; nested arrays +/// with fixed elements contribute `count × stride`; unions, records, +/// and variable-length primitives make the whole node variable. +fn fixed_composite_size(body: &CompositePlan) -> Option { + match body { + CompositePlan::Struct(plan) => fixed_plan_size(plan), + CompositePlan::Union { .. } | CompositePlan::Record { .. } => None, + CompositePlan::Array { + count, + element_stride, + .. + } => { + if *element_stride == 0 { + None + } else { + count.checked_mul(*element_stride) + } + } + } +} + +fn fixed_plan_size(plan: &ReadPlan) -> Option { + let mut total = 0usize; + for field in plan.fields() { + let size = match field.kind() { + ReadKind::Primitive(k) if k.is_fixed_size() => k.type_size()?, + ReadKind::Enum => AlkTypeKind::Enum.type_size()?, + ReadKind::Struct | ReadKind::Array => { + fixed_composite_size(field.body().as_ref()?)? + } + ReadKind::Union | ReadKind::Record => return None, + ReadKind::Primitive(_) => return None, + }; + total = total.checked_add(size)?; + } + Some(total) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + const LE: Endian = Endian::Little; + const BE: Endian = Endian::Big; + + fn plan(root: &Value, name: &str) -> ReadPlan { + ReadPlan::compile(root, name).expect("plan") + } + + // ----- Coverage: every BastType arm compiles to the expected shape -- + + #[test] + fn cov_all_fixed_primitives() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "i8", "kind": "int8" }, + { "name": "i16", "kind": "int16" }, + { "name": "i32", "kind": "int32" }, + { "name": "i64", "kind": "int64" }, + { "name": "u8", "kind": "uint8" }, + { "name": "u16", "kind": "uint16" }, + { "name": "u32", "kind": "uint32" }, + { "name": "u64", "kind": "uint64" }, + { "name": "f32", "kind": "float32" }, + { "name": "f64", "kind": "float64" }, + { "name": "b", "kind": "bool" }, + { "name": "s", "kind": "string" }, + { "name": "y", "kind": "bytes" } + ]}}}); + let p = plan(&root, "S"); + let kinds: Vec<&ReadKind> = p.fields().iter().map(|f| f.kind()).collect(); + assert!(matches!(kinds[0], ReadKind::Primitive(AlkTypeKind::Int8))); + assert!(matches!(kinds[1], ReadKind::Primitive(AlkTypeKind::Int16))); + assert!(matches!(kinds[2], ReadKind::Primitive(AlkTypeKind::Int32))); + assert!(matches!(kinds[3], ReadKind::Primitive(AlkTypeKind::Int64))); + assert!(matches!(kinds[4], ReadKind::Primitive(AlkTypeKind::Uint8))); + assert!(matches!(kinds[5], ReadKind::Primitive(AlkTypeKind::Uint16))); + assert!(matches!(kinds[6], ReadKind::Primitive(AlkTypeKind::Uint32))); + assert!(matches!(kinds[7], ReadKind::Primitive(AlkTypeKind::Uint64))); + assert!(matches!(kinds[8], ReadKind::Primitive(AlkTypeKind::Float32))); + assert!(matches!(kinds[9], ReadKind::Primitive(AlkTypeKind::Float64))); + assert!(matches!(kinds[10], ReadKind::Primitive(AlkTypeKind::Boolean))); + assert!(matches!(kinds[11], ReadKind::Primitive(AlkTypeKind::String))); + assert!(matches!(kinds[12], ReadKind::Primitive(AlkTypeKind::Bytes))); + for f in p.fields() { + assert!(f.body().is_none(), "primitive field {} needs no body", f.name()); + } + } + + #[test] + fn cov_enum_ref_resolves_to_enum_kind() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "e", "kind": { "$ref": "#/$defs/E" } } + ]}, + "E": { "kind": "enum", "values": ["A", "B"] } + }}); + let p = plan(&root, "S"); + assert!(matches!(p.fields()[0].kind(), ReadKind::Enum)); + assert!(p.fields()[0].body().is_none()); + } + + #[test] + fn cov_struct_inline() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "inner", "kind": { "kind": "struct", "fields": [ + { "name": "x", "kind": "uint8" }, + { "name": "y", "kind": "uint16" } + ]}} + ]}}}); + let p = plan(&root, "S"); + assert!(matches!(p.fields()[0].kind(), ReadKind::Struct)); + match p.fields()[0].body().expect("struct body") { + CompositePlan::Struct(inner) => { + assert_eq!(inner.fields().len(), 2); + assert_eq!(inner.field_index("y"), Some(1)); + } + other => panic!("expected Struct body, got {other:?}"), + } + } + + #[test] + fn cov_struct_ref() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "pt", "kind": { "$ref": "#/$defs/Point" } } + ]}, + "Point": { "kind": "struct", "fields": [ + { "name": "x", "kind": "uint16" }, + { "name": "y", "kind": "uint16" } + ]} + }}); + let p = plan(&root, "S"); + assert!(matches!(p.fields()[0].kind(), ReadKind::Struct)); + assert!(matches!(p.fields()[0].body(), Some(CompositePlan::Struct(_)))); + } + + #[test] + fn cov_union_byte_disc() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "packet", "kind": { "$ref": "#/$defs/Packet" } } + ]}, + "Packet": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "5": { "$ref": "#/$defs/Read" } } + }, + "Read": { "kind": "struct", "fields": [ { "name": "x", "kind": "uint8" } ] } + }}); + let p = plan(&root, "S"); + assert!(matches!(p.fields()[0].kind(), ReadKind::Union)); + match p.fields()[0].body().expect("union body") { + CompositePlan::Union { + disc, + shared, + variants, + } => { + assert!(matches!( + disc, + DiscriminatorPlan::Byte { offset: 0, disc_type: AlkTypeKind::Uint8 } + )); + assert!(shared.is_none(), "byte-disc unions have no shared fields"); + assert_eq!(variants.len(), 1); + assert_eq!(variants[0].0, "5"); + assert!(matches!(variants[0].1, CompositePlan::Struct(_))); + } + other => panic!("expected Union body, got {other:?}"), + } + } + + #[test] + fn cov_union_field_disc_compiles_shared() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "event", "kind": { "$ref": "#/$defs/Event" } } + ]}, + "Event": { "kind": "union", + "discriminator": { "kind": "field", "name": "type" }, + "fields": [ + { "name": "type", "kind": "string" }, + { "name": "seq", "kind": "uint32" } + ], + "mapping": { "read": { "$ref": "#/$defs/Read" } } + }, + "Read": { "kind": "struct", "fields": [ { "name": "n", "kind": "uint32" } ] } + }}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("union body") { + CompositePlan::Union { + disc, + shared, + variants, + } => { + assert!(matches!( + disc, + DiscriminatorPlan::Field { name, field_index: 0 } if name == "type" + )); + let shared = shared.as_ref().expect("field-disc unions carry shared"); + assert_eq!(shared.fields().len(), 2); + assert_eq!(shared.field_index("type"), Some(0)); + assert_eq!(shared.field_index("seq"), Some(1)); + assert_eq!(shared.fields()[0].endian(), LE); + assert!(matches!(variants[0].1, CompositePlan::Struct(_))); + } + other => panic!("expected Union body, got {other:?}"), + } + } + + #[test] + fn cov_array_fixed_primitive_element() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "vals", "kind": { "kind": "array", "element": "uint32", "count": 3 } } + ]}}}); + let p = plan(&root, "S"); + assert!(matches!(p.fields()[0].kind(), ReadKind::Array)); + match p.fields()[0].body().expect("array body") { + CompositePlan::Array { + element, + count, + element_stride, + } => { + assert_eq!(*count, 3); + assert_eq!(*element_stride, 4); + match &**element { + CompositePlan::Struct(inner) => { + assert_eq!(inner.fields().len(), 1); + assert!(matches!( + inner.fields()[0].kind(), + ReadKind::Primitive(AlkTypeKind::Uint32) + )); + } + other => panic!("expected Struct-wrapped leaf, got {other:?}"), + } + } + other => panic!("expected Array body, got {other:?}"), + } + } + + #[test] + fn cov_array_variable_element_stride_zero() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "items", "kind": { "kind": "array", "element": "string", "count": 2 } } + ]}}}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("array body") { + CompositePlan::Array { + count, + element_stride, + .. + } => { + assert_eq!(*count, 2); + assert_eq!(*element_stride, 0, "variable-length element => stride 0"); + } + other => panic!("expected Array body, got {other:?}"), + } + } + + #[test] + fn cov_array_ref_element_computes_true_stride() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "pts", "kind": { "kind": "array", + "element": { "$ref": "#/$defs/Point" }, "count": 2 } } + ]}, + "Point": { "kind": "struct", "fields": [ + { "name": "x", "kind": "uint16" }, + { "name": "y", "kind": "uint16" } + ]} + }}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("array body") { + CompositePlan::Array { + element, + count, + element_stride, + } => { + assert_eq!(*count, 2); + assert_eq!(*element_stride, 4, "Point is u16+u16 = 4 bytes fixed"); + assert!(matches!(&**element, CompositePlan::Struct(_))); + } + other => panic!("expected Array body, got {other:?}"), + } + } + + #[test] + fn cov_array_struct_array_element_computes_nested_stride() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "grid", "kind": { "kind": "array", + "element": { "kind": "array", "element": "uint8", "count": 4 }, + "count": 3 } } + ]}}}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("array body") { + CompositePlan::Array { element_stride, .. } => { + assert_eq!(*element_stride, 4, "inner array is 4 × u8 = 4 bytes"); + } + other => panic!("expected Array body, got {other:?}"), + } + } + + #[test] + fn cov_array_nested_variable_element_is_stride_zero() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "grid", "kind": { "kind": "array", + "element": { "kind": "array", "element": "string", "count": 2 }, + "count": 3 } } + ]}}}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("array body") { + CompositePlan::Array { element_stride, .. } => { + assert_eq!(*element_stride, 0, "inner array of strings is variable"); + } + other => panic!("expected Array body, got {other:?}"), + } + } + + #[test] + fn cov_record() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "counts", "kind": { "kind": "record", "values": "uint32" } } + ]}}}); + let p = plan(&root, "S"); + assert!(matches!(p.fields()[0].kind(), ReadKind::Record)); + match p.fields()[0].body().expect("record body") { + CompositePlan::Record { value } => match &**value { + CompositePlan::Struct(inner) => { + assert!(matches!( + inner.fields()[0].kind(), + ReadKind::Primitive(AlkTypeKind::Uint32) + )); + } + other => panic!("expected Struct-wrapped leaf, got {other:?}"), + }, + other => panic!("expected Record body, got {other:?}"), + } + } + + #[test] + fn cov_field_level_endian_override() { + let root = json!({ "$defs": { "S": { "kind": "struct", "endian": "big", "fields": [ + { "name": "crc", "kind": "uint32", "endian": "little" } + ]}}}); + let p = plan(&root, "S"); + assert_eq!(p.endian(), BE); + assert_eq!(p.fields()[0].endian(), LE, "field-level override must win"); + } + + #[test] + fn cov_maxlength_and_encoding_preserved() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "blob", "kind": "bytes", "encoding": "offset-indirect", "maxLength": 256 } + ]}}}); + let p = plan(&root, "S"); + let f = &p.fields()[0]; + assert_eq!(f.max_length(), Some(256)); + assert_eq!(f.encoding(), VariableEncoding::OffsetIndirect); + } + + // ----- Endianness parity with the 0.2.0 packed read path ------------ + + #[test] + fn nested_struct_endian_propagates_not_resets() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "endian": "big", "fields": [ + { "name": "inner", "kind": { "$ref": "#/$defs/Inner" } } + ]}, + "Inner": { "kind": "struct", "endian": "little", "fields": [ + { "name": "v", "kind": "uint32" } + ]} + }}); + let p = plan(&root, "S"); + assert_eq!(p.endian(), BE); + assert_eq!(p.fields()[0].endian(), BE); + match p.fields()[0].body().expect("struct body") { + CompositePlan::Struct(inner) => { + assert_eq!(inner.fields()[0].endian(), BE, + "0.2.0 packed parity: the referring field's effective endian \ + propagates; the nested struct's own `endian` annotation is not \ + consulted on the packed read path"); + } + other => panic!("expected Struct body, got {other:?}"), + } + } + + #[test] + fn nested_union_variant_endian_propagates_not_resets() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "endian": "big", "fields": [ + { "name": "packet", "kind": { "$ref": "#/$defs/Packet" } } + ]}, + "Packet": { "kind": "union", "endian": "little", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "5": { "$ref": "#/$defs/Read" } } + }, + "Read": { "kind": "struct", "fields": [ { "name": "v", "kind": "uint32" } ] } + }}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("union body") { + CompositePlan::Union { variants, .. } => match &variants[0].1 { + CompositePlan::Struct(inner) => { + assert_eq!(inner.fields()[0].endian(), BE, + "0.2.0 packed parity: variant fields inherit the referring \ + field's endian, not the union's own annotation"); + } + other => panic!("expected Struct variant, got {other:?}"), + }, + other => panic!("expected Union body, got {other:?}"), + } + } + + // ----- Untrusted input: cycle / depth / malformed schemas ----------- + + #[test] + fn cyclic_ref_is_schema_error_not_stack_overflow() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "next", "kind": { "$ref": "#/$defs/S" } } + ]} + }}); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn cyclic_ref_through_two_defs_is_schema_error() { + let root = json!({ "$defs": { + "A": { "kind": "struct", "fields": [ + { "name": "b", "kind": { "$ref": "#/$defs/B" } } + ]}, + "B": { "kind": "struct", "fields": [ + { "name": "a", "kind": { "$ref": "#/$defs/A" } } + ]} + }}); + let err = ReadPlan::compile(&root, "A").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn deep_nesting_beyond_depth_cap_is_schema_error() { + let mut defs = serde_json::Map::new(); + let depth = MAX_COMPILE_DEPTH + 10; + for i in (0..depth).rev() { + let name = format!("D{i}"); + let next = if i + 1 == depth { + json!({ "kind": "struct", "fields": [] }) + } else { + json!({ "kind": "struct", "fields": [ + { "name": "n", "kind": { "$ref": format!("#/$defs/D{}", i + 1) } } + ]}) + }; + defs.insert(name, next); + } + defs.insert( + "S".to_string(), + json!({ "kind": "struct", "fields": [ + { "name": "n", "kind": { "$ref": "#/$defs/D0" } } + ]}), + ); + let root = Value::Object( + [( "$defs".to_string(), Value::Object(defs) )].into_iter().collect(), + ); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn repeated_ref_to_shared_def_is_allowed() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "a", "kind": { "$ref": "#/$defs/Point" } }, + { "name": "b", "kind": { "$ref": "#/$defs/Point" } } + ]}, + "Point": { "kind": "struct", "fields": [ { "name": "x", "kind": "uint16" } ]} + }}); + let p = plan(&root, "S"); + assert_eq!(p.fields().len(), 2); + } + + #[test] + fn dangling_ref_is_schema_error() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "x", "kind": { "$ref": "#/$defs/Ghost" } } + ]} + }}); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn non_struct_root_is_schema_error() { + let root = json!({ "$defs": { + "E": { "kind": "enum", "values": ["A"] } + }}); + let err = ReadPlan::compile(&root, "E").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn union_enum_variant_is_schema_error() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "u", "kind": { "$ref": "#/$defs/U" } } + ]}, + "U": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "1": { "$ref": "#/$defs/E" } } + }, + "E": { "kind": "enum", "values": ["A"] } + }}); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn union_primitive_variant_is_schema_error() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "u", "kind": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "1": "uint32" } } } + ]} + }}); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn field_disc_union_missing_disc_field_is_schema_error() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "u", "kind": { "kind": "union", + "discriminator": { "kind": "field", "name": "ghost" }, + "fields": [ { "name": "other", "kind": "uint8" } ], + "mapping": { "k": { "$ref": "#/$defs/V" } } } } + ]}, + "V": { "kind": "struct", "fields": [] } + }}); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + #[test] + fn malformed_doc_is_schema_error() { + let root = json!({ "type": "object" }); + let err = ReadPlan::compile(&root, "S").unwrap_err(); + assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}"); + } + + // ----- Refined-shape capability: nested unions ----------------------- + + #[test] + fn nested_union_variant_compiles_to_union_body() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "outer", "kind": { "$ref": "#/$defs/Outer" } } + ]}, + "Outer": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "1": { "$ref": "#/$defs/Inner" } } + }, + "Inner": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "2": { "$ref": "#/$defs/Leaf" } } + }, + "Leaf": { "kind": "struct", "fields": [ { "name": "v", "kind": "uint16" } ] } + }}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("outer union body") { + CompositePlan::Union { variants, .. } => { + assert_eq!(variants.len(), 1); + assert_eq!(variants[0].0, "1"); + match &variants[0].1 { + CompositePlan::Union { variants, .. } => { + assert_eq!(variants.len(), 1); + assert_eq!(variants[0].0, "2"); + assert!(matches!(variants[0].1, CompositePlan::Struct(_))); + } + other => panic!("expected nested Union body, got {other:?}"), + } + } + other => panic!("expected Union body, got {other:?}"), + } + } + + #[test] + fn nested_union_variant_inline_compiles() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "outer", "kind": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "1": { "kind": "union", + "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" }, + "mapping": { "2": { "kind": "struct", "fields": [ + { "name": "v", "kind": "uint16" } ] } } } } } } + ]} + }}); + let p = plan(&root, "S"); + match p.fields()[0].body().expect("outer union body") { + CompositePlan::Union { variants, .. } => match &variants[0].1 { + CompositePlan::Union { .. } => {} + other => panic!("expected nested Union body, got {other:?}"), + }, + other => panic!("expected Union body, got {other:?}"), + } + } + + // ----- Misc compile properties --------------------------------------- + + #[test] + fn by_name_maps_root_fields() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "a", "kind": "uint8" }, + { "name": "b", "kind": "uint8" }, + { "name": "c", "kind": "uint8" } + ]}}}); + let p = plan(&root, "S"); + assert_eq!(p.field_index("a"), Some(0)); + assert_eq!(p.field_index("b"), Some(1)); + assert_eq!(p.field_index("c"), Some(2)); + assert_eq!(p.field_index("missing"), None); + } + + #[test] + fn empty_struct_compiles() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [] } } }); + let p = plan(&root, "S"); + assert!(p.fields().is_empty()); + assert_eq!(p.endian(), LE); + } + + #[test] + fn compile_is_deterministic() { + let root = json!({ "$defs": { + "S": { "kind": "struct", "fields": [ + { "name": "pt", "kind": { "$ref": "#/$defs/Point" } }, + { "name": "items", "kind": { "kind": "array", + "element": { "$ref": "#/$defs/Point" }, "count": 2 } } + ]}, + "Point": { "kind": "struct", "fields": [ + { "name": "x", "kind": "uint16" }, + { "name": "y", "kind": "uint16" } + ]} + }}); + let p1 = plan(&root, "S"); + let p2 = plan(&root, "S"); + assert_eq!(p1, p2); + } + + #[test] + fn plan_is_clone_and_debug() { + let root = json!({ "$defs": { "S": { "kind": "struct", "fields": [ + { "name": "a", "kind": "uint8" } + ]}}}); + let p = plan(&root, "S"); + let clone = p.clone(); + assert_eq!(p, clone); + let debug = format!("{p:?}"); + assert!(debug.contains("ReadPlan")); + } + + // ----- Send + Sync (ADR-011 §"Engine integration") -------------------- + + #[test] + fn read_plan_is_send_sync() { + const _: fn() = || { + fn assert_send_sync() {} + assert_send_sync::(); + assert_send_sync::(); + assert_send_sync::(); + assert_send_sync::(); + assert_send_sync::(); + }; + } +} \ No newline at end of file