mizan-axum + macros: state threading, array/map lowering, merge shape semantics
Three substrate extensions surfaced by the Blazr session port: 1. **App-state threading.** mizan-axum::router() is now generic over a user-supplied state type and threads `Arc<dyn Any + Send + Sync>` into every dispatch via RequestHandle. Handlers downcast to their concrete AppState. The stateless AFI fixture uses `router_stateless()` (matches the prior signature). RequestHandle gains a `from_dyn()` constructor to wrap already-erased trait-object references. 2. **`[T; N]` and `BTreeMap<K, V>` lowering in #[derive(Mizan)].** Fixed arrays emit as `List<T>` (matches Python `tuple[float,...]` → JSON array). String-keyed maps emit as `List<V>` — closest approximation until KDL grows a `dict` shape. Also: vec-element registrations get a per-function scope suffix so two handlers returning `Vec<Same>` don't collide at the static-name layer. 3. **`types_match` for merge: upsert-into-list semantics.** Now matches Python `types_match_for_merge`: direct (T == T), upsert (slot is `Alias(List(T))`, value is T), and list-replace (both sides list). The AFI fixture only exercised the direct path; the Blazr port's `morph_set_value` returning a single `MorphLayer` into a context with `Vec<MorphLayer>` slot is what surfaced the gap. AFI codegen + wire parity stays 12/12 green after these substrate changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -240,7 +240,11 @@ pub fn expand(args: FunctionArgs, item: ItemFn) -> TokenStream {
|
||||
});
|
||||
// Also register the element type itself by its own name. `TYPE_NAME`
|
||||
// is an associated const, so this is usable in a static initializer.
|
||||
let elem_static = element_type_static_ident(elem);
|
||||
// The static ident scopes by the function name so two handlers
|
||||
// returning `Vec<Same>` don't collide; the IrSnapshot's BTreeMap
|
||||
// dedupes by the entry's `name` at emit time.
|
||||
let elem_static =
|
||||
element_type_static_ident_scoped(elem, &fn_name.to_shouty_snake_case());
|
||||
type_registrations.push(quote! {
|
||||
#[::mizan_core::__priv::linkme::distributed_slice(::mizan_core::TYPES)]
|
||||
#[linkme(crate = ::mizan_core::__priv::linkme)]
|
||||
@@ -481,14 +485,16 @@ fn build_dispatch(
|
||||
}
|
||||
}
|
||||
|
||||
fn element_type_static_ident(ty: &Type) -> syn::Ident {
|
||||
// Derive a unique static-name for the type's registration entry. Uses
|
||||
// the last path segment's identifier as the discriminator.
|
||||
fn element_type_static_ident_scoped(ty: &Type, fn_scope: &str) -> syn::Ident {
|
||||
// Derive a unique static-name for the type's registration entry,
|
||||
// scoped by the surrounding function so siblings returning the same
|
||||
// `Vec<T>` don't collide at the static-name layer. The IR-side
|
||||
// BTreeMap dedupes by TypeEntry.name at emission time.
|
||||
let last = match ty {
|
||||
Type::Path(tp) => tp.path.segments.last().map(|s| s.ident.to_string()),
|
||||
_ => None,
|
||||
};
|
||||
let suffix = last.unwrap_or_else(|| "ANON".to_string()).to_shouty_snake_case();
|
||||
format_ident!("__MIZAN_TYPE_ELEM_{}", suffix)
|
||||
format_ident!("__MIZAN_TYPE_ELEM_{}_FOR_{}", suffix, fn_scope)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user