Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions artifacts/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3055,6 +3055,58 @@ artifacts:
- type: traces-to
target: REQ-CODEGEN-WIT-STATE-001

- id: REQ-CODEGEN-WIT-ARRAY-001
type: requirement
title: "Structured WIT list from a Data Modeling Annex Array data type"
description: >
Codegen WIT emission shall map an AADL `data` type whose Data Modeling Annex
`Data_Representation` is `Array` to a typed WIT `list`, closing the #345 gap
where EVERY array data type degraded to the opaque `type x = list<u8>` blob
(element type, signedness/float, and dimension all dropped), so an inter-core
contract could only say `list`, not the real payload. Delivered and
oracle-verified:
(1) `resolve_data_shape` gains a `DataShape::Array { element, len }` arm
(spar-hir-def): a `data` type with `Data_Representation => Array` resolves its
`Base_Type` classifier to the element shape (recursively, reusing the same
scalar/record resolution as records) and reads the first `Dimension` value as
the length. Both the maintainer's list-wrapped property syntax
(`Base_Type => (classifier(Pkg::T))`, `Dimension => (16)`) and the bare forms
are accepted (typed_value: `List([ClassifierValue])`/`ClassifierValue`,
`List([Integer])`/`Integer`).
(2) A FIXED `Dimension` emits a BOUNDED fixed-size WIT `list<T, N>` — no
`cabi_realloc`, consistent with the RECORDS-002 no_alloc discipline (only
unbounded `list`/`string` force dynamic allocation); an array with NO
`Dimension` emits an unbounded `list<T>`; an `Array` with NO resolvable
`Base_Type` is NOT fabricated — it keeps the legacy opaque `list<u8>`.
(3) The element resolves through the shared `shape_wit_type` helper, so an
array-of-record and an array-typed record FIELD both work; the generated Rust
port type mirrors wit-bindgen's lowering (`list<T, N>` -> `[T; N]`,
`list<T>` -> `Vec<T>`).
ORACLE (executed unit tests, spar-codegen/src/wit_gen.rs): the #345 NavState
case (`Data_Representation => Array` + `Base_Type => (classifier(Float32))` +
`Dimension => (16)`) generates `type vehicle-state = list<f32, 16>;`; an
unbounded array -> `list<f32>`; a Base_Type-less array -> `list<u8>`
(negative control); an array-typed record field -> `nav: list<f32, 16>`. Each
asserts the emitted WIT PARSES + RESOLVES under `wit_parser` (a real
fixed-size-list bind, not a string match).
SCOPE / NOT CLAIMED: element bit-width comes from the `Base_Type`'s own
`Data_Size`; when the `Base_Type` resolves to an element with no concrete
shape (an unsized `Base_Types::Float` with no `Data_Size`, or an
unresolvable classifier) `array_of` returns `None`, so the array type is
NOT fabricated — a top-level array port GRACEFULLY falls back to the legacy
opaque `type x = list<u8>` (no regression, no hard error), and an
array-typed record FIELD takes the record no_alloc hard-error path (as an
opaque field would). Multi-dimensional arrays use only the first
`Dimension`. `list<T>`/`list<T, N>` byte-layout-vs-canonical-ABI
equivalence is NOT proven here (that is #327's ordeal work), and the Rust
port type (`[T; N]`/`Vec<T>`) mirrors wit-bindgen's documented lowering but
no compiled-crate oracle for array ports is asserted in this requirement.
status: implemented
tags: [codegen, wit, interop, no-alloc]
links:
- type: traces-to
target: REQ-CODEGEN-WIT-RECORDS-001

- id: REQ-CODEGEN-VERIFY-WORKSPACE-001
type: requirement
title: "spar codegen --verify test/build emits inert artifacts"
Expand Down
105 changes: 103 additions & 2 deletions crates/spar-codegen/src/rust_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ fn data_shape_to_rust_type(shape: &DataShape) -> String {
DataShape::Record { type_name, .. } => {
format!("crate::types::{}", to_pascal_case(type_name))
}
// wit-bindgen lowers a fixed-size `list<T, N>` to a Rust `[T; N]` and an
// unbounded `list<T>` to `Vec<T>`; mirror that so the port field type
// matches the ABI lowering. REQ-CODEGEN-WIT-ARRAY-001 (#345).
DataShape::Array { element, len } => {
let elem = data_shape_to_rust_type(element);
match len {
Some(n) => format!("[{elem}; {n}]"),
None => format!("Vec<{elem}>"),
}
}
DataShape::Opaque => "Vec<u8>".to_string(),
}
}
Expand Down Expand Up @@ -833,11 +843,17 @@ fn flatten_record_rust(
}
let mut rows = Vec::with_capacity(fields.len());
for f in fields {
// Recurse into nested records first so they are defined before use.
// Recurse into nested records first so they are defined before use —
// including a record reached through array element(s) (#345), so an
// `array of record` field does not reference an undefined struct.
let mut inner = &f.shape;
while let DataShape::Array { element, .. } = inner {
inner = element;
}
if let DataShape::Record {
type_name: nt,
fields: nf,
} = &f.shape
} = inner
{
flatten_record_rust(nt, nf, out, seen);
}
Expand Down Expand Up @@ -919,4 +935,89 @@ mod tests {
"Vec<u8>"
);
}

/// REQ-CODEGEN-WIT-ARRAY-001 (#345): an `Array` shape mirrors wit-bindgen's
/// lowering — a fixed `Dimension` → a Rust fixed array `[T; N]`, an unbounded
/// array → `Vec<T>`, recursively over the element (scalar, record, or a
/// nested array).
#[test]
fn array_shape_maps_to_rust_types() {
let f32_scalar = || DataShape::Scalar {
bytes: 4,
kind: ScalarKind::Float,
};
// Fixed 16 × f32 (the #345 NavState case) → `[f32; 16]`.
assert_eq!(
data_shape_to_rust_type(&DataShape::Array {
element: Box::new(f32_scalar()),
len: Some(16)
}),
"[f32; 16]"
);
// Unbounded (no Dimension) → `Vec<f32>`.
assert_eq!(
data_shape_to_rust_type(&DataShape::Array {
element: Box::new(f32_scalar()),
len: None
}),
"Vec<f32>"
);
// Array of a record → `[crate::types::Nav; 4]`.
assert_eq!(
data_shape_to_rust_type(&DataShape::Array {
element: Box::new(DataShape::Record {
type_name: "Nav".to_string(),
fields: vec![]
}),
len: Some(4)
}),
"[crate::types::Nav; 4]"
);
// Nested array (array of arrays) recurses on both levels.
assert_eq!(
data_shape_to_rust_type(&DataShape::Array {
element: Box::new(DataShape::Array {
element: Box::new(f32_scalar()),
len: Some(3)
}),
len: Some(2)
}),
"[[f32; 3]; 2]"
);
}

/// REQ-CODEGEN-WIT-ARRAY-001 (#345): a record FIELD whose type is an array
/// of a nested record still emits the nested record's struct (the array
/// recursion in `flatten_record_rust`), so `crate::types::Inner` resolves.
#[test]
fn record_field_of_array_of_record_defines_the_inner_struct() {
let inner = DataField {
name: spar_hir_def::name::Name::new("hi"),
shape: DataShape::Scalar {
bytes: 2,
kind: ScalarKind::Unsigned,
},
};
let outer_fields = vec![DataField {
name: spar_hir_def::name::Name::new("items"),
shape: DataShape::Array {
element: Box::new(DataShape::Record {
type_name: "Inner".to_string(),
fields: vec![inner],
}),
len: Some(8),
},
}];
let mut structs = Vec::new();
let mut seen = std::collections::BTreeSet::new();
flatten_record_rust("Outer", &outer_fields, &mut structs, &mut seen);

// Both the outer and the array-nested inner struct are emitted, inner-first.
let names: Vec<&str> = structs.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(names, vec!["Inner", "Outer"], "structs: {names:?}");
// The outer field references the array-of-record type.
let outer = &structs.iter().find(|(n, _)| n == "Outer").unwrap().1;
assert_eq!(outer[0].0, "items");
assert_eq!(outer[0].1, "[crate::types::Inner; 8]");
}
}
Loading
Loading