From bdf51a3eabcedc99ab5055398ab9e0a223c64999 Mon Sep 17 00:00:00 2001 From: alec-aftermath Date: Thu, 11 Jun 2026 15:55:02 -0700 Subject: [PATCH] feat(af-move-type): more allocationless type matching --- crates/af-move-type-derive/src/move_struct.rs | 113 ++++++++++++++++-- crates/af-move-type/src/any.rs | 10 +- crates/af-move-type/src/lib.rs | 11 ++ 3 files changed, 121 insertions(+), 13 deletions(-) diff --git a/crates/af-move-type-derive/src/move_struct.rs b/crates/af-move-type-derive/src/move_struct.rs index dd73b225..577ae947 100644 --- a/crates/af-move-type-derive/src/move_struct.rs +++ b/crates/af-move-type-derive/src/move_struct.rs @@ -127,6 +127,9 @@ fn impl_type_tag(type_tag_params: TypeTagParameters, thecrate: Path) -> TokenStr _ => None, }) .collect(); + // Snake-cased type-param idents in declaration order — these are the field + // names on the marker (e.g. `self.t`). Captured before the reverse below. + let type_param_snakes: Vec = type_param_idents.clone(); type_param_idents.reverse(); let unpack_type_params = quote!( @@ -157,19 +160,45 @@ fn impl_type_tag(type_tag_params: TypeTagParameters, thecrate: Path) -> TokenStr }; let serde_with_crate = quote!(#thecrate::external::serde_with).to_string(); - // Emit a non-allocating `matches` override when address, module, and name - // are all compile-time constants. Otherwise fall back to the default body - // (which delegates to `TryFrom`). + // Emit a non-allocating override appropriate to the marker's identity shape: + // - All three of address/module/name are compile-time literals → + // override both `matches` (static, recurses via static + // `T::TypeTag::matches`) AND `matches_instance` (recurses via + // `self..matches_instance`). The instance-based override exists + // so that fully-static outer markers wrapping runtime-identity inner + // type params (e.g. `Field>`) stay + // non-allocating when callers have a marker instance. + // - Module and name are literals but address is a runtime field → + // override `matches_instance` (uses `self.address`). Type-param + // recursion uses `self..matches_instance(...)`. + // - Address and module are literals but name is a runtime field (i.e. + // nameless markers like `Otw`) → override `matches_instance` (uses + // `self.name`). Same instance-based type-param recursion. + // - Anything else → empty impl, both methods fall through to the default + // `try_from(tag.clone()).is_ok()`. + let n_type_params = type_param_pascals.len(); + let type_param_checks_static: Vec<_> = type_param_pascals + .iter() + .enumerate() + .map(|(i, t)| { + quote!( + <<#t as #thecrate::MoveType>::TypeTag as #thecrate::MoveTypeTag>::matches( + &type_params[#i] + ) + ) + }) + .collect(); + let type_param_checks_instance: Vec<_> = type_param_snakes + .iter() + .enumerate() + .map(|(i, snake)| { + quote!( + #thecrate::MoveTypeTag::matches_instance(&self.#snake, &type_params[#i]) + ) + }) + .collect(); let move_type_tag_impl = match (&address_const, &module_const, &name_const) { (Some(addr), Some(module), Some(name)) => { - let n_type_params = type_param_pascals.len(); - let type_param_checks = type_param_pascals.iter().enumerate().map(|(i, t)| { - quote!( - <<#t as #thecrate::MoveType>::TypeTag as #thecrate::MoveTypeTag>::matches( - &type_params[#i] - ) - ) - }); quote! { impl #impl_generics #thecrate::MoveTypeTag for #ident #type_generics #where_clause @@ -187,7 +216,67 @@ fn impl_type_tag(type_tag_params: TypeTagParameters, thecrate: Path) -> TokenStr stag.address() == &EXPECTED_ADDRESS && stag.module().as_str() == #module && stag.name().as_str() == #name - #(&& #type_param_checks)* + #(&& #type_param_checks_static)* + } + + fn matches_instance(&self, tag: &#type_tag_type) -> bool { + const EXPECTED_ADDRESS: #thecrate::external::Address = + <#thecrate::external::Address>::from_static(#addr); + let #type_tag_type::Struct(stag) = tag else { + return false; + }; + let type_params = stag.type_params(); + if type_params.len() != #n_type_params { + return false; + } + stag.address() == &EXPECTED_ADDRESS + && stag.module().as_str() == #module + && stag.name().as_str() == #name + #(&& #type_param_checks_instance)* + } + } + } + } + (None, Some(module), Some(name)) => { + quote! { + impl #impl_generics #thecrate::MoveTypeTag for #ident #type_generics + #where_clause + { + fn matches_instance(&self, tag: &#type_tag_type) -> bool { + let #type_tag_type::Struct(stag) = tag else { + return false; + }; + let type_params = stag.type_params(); + if type_params.len() != #n_type_params { + return false; + } + stag.address() == &self.address + && stag.module().as_str() == #module + && stag.name().as_str() == #name + #(&& #type_param_checks_instance)* + } + } + } + } + (Some(addr), Some(module), None) => { + quote! { + impl #impl_generics #thecrate::MoveTypeTag for #ident #type_generics + #where_clause + { + fn matches_instance(&self, tag: &#type_tag_type) -> bool { + const EXPECTED_ADDRESS: #thecrate::external::Address = + <#thecrate::external::Address>::from_static(#addr); + let #type_tag_type::Struct(stag) = tag else { + return false; + }; + let type_params = stag.type_params(); + if type_params.len() != #n_type_params { + return false; + } + stag.address() == &EXPECTED_ADDRESS + && stag.module().as_str() == #module + && stag.name() == &self.name + #(&& #type_param_checks_instance)* } } } diff --git a/crates/af-move-type/src/any.rs b/crates/af-move-type/src/any.rs index 05427e8c..00f2c879 100644 --- a/crates/af-move-type/src/any.rs +++ b/crates/af-move-type/src/any.rs @@ -69,7 +69,15 @@ impl TryFrom for AnyTTypeTag { } } -impl MoveTypeTag for AnyTTypeTag {} +impl MoveTypeTag for AnyTTypeTag { + fn matches(_tag: &TypeTag) -> bool { + true + } + + fn matches_instance(&self, _tag: &TypeTag) -> bool { + true + } +} impl FromStr for AnyTTypeTag { type Err = ParseTypeTagError; diff --git a/crates/af-move-type/src/lib.rs b/crates/af-move-type/src/lib.rs index 043cf878..860699b6 100644 --- a/crates/af-move-type/src/lib.rs +++ b/crates/af-move-type/src/lib.rs @@ -110,6 +110,17 @@ pub trait MoveTypeTag: fn matches(tag: &TypeTag) -> bool { Self::try_from(tag.clone()).is_ok() } + + /// Like [`Self::matches`] but for markers whose identity depends on runtime + /// fields (e.g. a package address resolved at construction time). + /// + /// The default delegates to [`Self::matches`], which is correct for markers + /// whose identity is fully compile-time. Implementors that store identity + /// fields on the marker should override this to compare against `self` + /// without cloning `tag` or materializing a fresh marker. + fn matches_instance(&self, tag: &TypeTag) -> bool { + Self::matches(tag) + } } // =============================================================================