From 64b668c77c5b66634cd5578ac998d920bb825b84 Mon Sep 17 00:00:00 2001 From: alec-aftermath Date: Thu, 11 Jun 2026 13:56:40 -0700 Subject: [PATCH] feat(af-move-type)!: add non-allocating `MoveTypeTag::matches` --- crates/af-move-type-derive/src/move_struct.rs | 67 +++++++++++++++++++ crates/af-move-type/src/any.rs | 4 +- crates/af-move-type/src/lib.rs | 23 ++----- crates/af-move-type/src/primitives.rs | 8 ++- crates/af-move-type/src/string.rs | 20 +++++- crates/af-move-type/src/vector.rs | 11 ++- 6 files changed, 111 insertions(+), 22 deletions(-) diff --git a/crates/af-move-type-derive/src/move_struct.rs b/crates/af-move-type-derive/src/move_struct.rs index 93eff073..dd73b225 100644 --- a/crates/af-move-type-derive/src/move_struct.rs +++ b/crates/af-move-type-derive/src/move_struct.rs @@ -101,6 +101,9 @@ fn impl_type_tag(type_tag_params: TypeTagParameters, thecrate: Path) -> TokenStr struct_tag_const_vals, struct_tag_consts_checks, mut type_param_idents, + address_const, + module_const, + name_const, } = type_tag_params; let attr_declarations: Vec<_> = attr_idents @@ -114,6 +117,17 @@ fn impl_type_tag(type_tag_params: TypeTagParameters, thecrate: Path) -> TokenStr .map(|(ident, val)| quote!(#ident: #val)) .collect(); + // Pascal-cased generic type param idents (e.g. `T`), in declaration order. + // Needed BEFORE we reverse `type_param_idents` below for `unpack_type_params`. + let type_param_pascals: Vec = generics + .params + .iter() + .filter_map(|p| match p { + GenericParam::Type(t) => Some(t.ident.clone()), + _ => None, + }) + .collect(); + type_param_idents.reverse(); let unpack_type_params = quote!( // Unwrap here since we already checked the vector length @@ -143,6 +157,48 @@ 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`). + 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 + { + fn matches(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)* + } + } + } + } + _ => quote! { + impl #impl_generics #thecrate::MoveTypeTag for #ident #type_generics + #where_clause + {} + }, + }; + quote! { #[derive( Clone, @@ -239,6 +295,8 @@ fn impl_type_tag(type_tag_params: TypeTagParameters, thecrate: Path) -> TokenStr #result_type::Ok(stag.try_into()?) } } + + #move_type_tag_impl } } @@ -310,6 +368,9 @@ struct TypeTagParameters { struct_tag_const_vals: Vec, struct_tag_consts_checks: TokenStream, type_param_idents: Vec, + address_const: Option, + module_const: Option, + name_const: Option, } fn type_tag_parameters( @@ -330,6 +391,9 @@ fn type_tag_parameters( struct_tag_const_vals: vec![], struct_tag_consts_checks: quote!(), type_param_idents: vec![], + address_const: None, + module_const: None, + name_const: None, }; let address_check = if let Some(address) = address { @@ -342,6 +406,7 @@ fn type_tag_parameters( } ); params.struct_tag_const_vals.push(value); + params.address_const = Some(address); check } else { params.attr_idents.push(quote!(address)); @@ -360,6 +425,7 @@ fn type_tag_parameters( } ); params.struct_tag_const_vals.push(value); + params.module_const = Some(module); check } else { params.attr_idents.push(quote!(module)); @@ -388,6 +454,7 @@ fn type_tag_parameters( } ); params.struct_tag_const_vals.push(value); + params.name_const = Some(name); check }; diff --git a/crates/af-move-type/src/any.rs b/crates/af-move-type/src/any.rs index b59664fa..05427e8c 100644 --- a/crates/af-move-type/src/any.rs +++ b/crates/af-move-type/src/any.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use af_sui_types::TypeTag; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::{MoveType, ParseTypeTagError, TypeTagError}; +use crate::{MoveType, MoveTypeTag, ParseTypeTagError, TypeTagError}; /// Generic type that accepts **any** Move type argument in its slot, /// including phantom-paramed generics such as `VENDOR` as well as @@ -69,6 +69,8 @@ impl TryFrom for AnyTTypeTag { } } +impl MoveTypeTag for AnyTTypeTag {} + 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 82f9a9bb..043cf878 100644 --- a/crates/af-move-type/src/lib.rs +++ b/crates/af-move-type/src/lib.rs @@ -103,22 +103,13 @@ pub trait MoveTypeTag: + Ord + Serialize { -} - -impl MoveTypeTag for T where - T: Into - + TryFrom - + FromStr - + Clone - + Debug - + PartialEq - + Eq - + Hash - + for<'de> Deserialize<'de> - + PartialOrd - + Ord - + Serialize -{ + /// Returns whether `tag` is the runtime representation of `Self`. + /// + /// Equivalent to `Self::try_from(tag.clone()).is_ok()` but implementors are + /// expected to override this to avoid materializing `Self` or cloning `tag`. + fn matches(tag: &TypeTag) -> bool { + Self::try_from(tag.clone()).is_ok() + } } // ============================================================================= diff --git a/crates/af-move-type/src/primitives.rs b/crates/af-move-type/src/primitives.rs index ac58707b..091ae732 100644 --- a/crates/af-move-type/src/primitives.rs +++ b/crates/af-move-type/src/primitives.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use af_sui_types::{Address, TypeTag, U256}; use serde::{Deserialize, Serialize}; -use crate::{MoveType, ParseTypeTagError, StaticTypeTag, TypeTagError}; +use crate::{MoveType, MoveTypeTag, ParseTypeTagError, StaticTypeTag, TypeTagError}; macro_rules! impl_primitive_type_tags { ($($typ:ty: ($type_:ident, $variant:ident)),*) => { @@ -41,6 +41,12 @@ macro_rules! impl_primitive_type_tags { } } + impl MoveTypeTag for $type_ { + fn matches(tag: &TypeTag) -> bool { + matches!(tag, TypeTag::$variant) + } + } + impl FromStr for $type_ { type Err = ParseTypeTagError; diff --git a/crates/af-move-type/src/string.rs b/crates/af-move-type/src/string.rs index 46b37b63..951e7cb5 100644 --- a/crates/af-move-type/src/string.rs +++ b/crates/af-move-type/src/string.rs @@ -3,10 +3,12 @@ use std::str::FromStr; use af_sui_types::{Address, IdentStr, Identifier, StructTag, TypeTag}; use serde::{Deserialize, Serialize}; +use af_sui_types::MOVE_STDLIB_ADDRESS; + use crate::{ - MoveStruct, MoveType, ParseStructTagError, StaticAddress, StaticModule, StaticName, - StaticStructTag as _, StaticTypeParams, StaticTypeTag, StructTagError, TypeParamsError, - TypeTagError, + MoveStruct, MoveType, MoveTypeTag, ParseStructTagError, StaticAddress, StaticModule, + StaticName, StaticStructTag as _, StaticTypeParams, StaticTypeTag, StructTagError, + TypeParamsError, TypeTagError, }; #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, PartialOrd, Ord, Serialize)] @@ -72,6 +74,18 @@ impl TryFrom for StringTypeTag { } } +impl MoveTypeTag for StringTypeTag { + fn matches(tag: &TypeTag) -> bool { + let TypeTag::Struct(stag) = tag else { + return false; + }; + stag.address() == &MOVE_STDLIB_ADDRESS + && stag.module().as_str() == "string" + && stag.name().as_str() == "String" + && stag.type_params().is_empty() + } +} + impl FromStr for StringTypeTag { type Err = ParseStructTagError; diff --git a/crates/af-move-type/src/vector.rs b/crates/af-move-type/src/vector.rs index 259b5d74..41778d90 100644 --- a/crates/af-move-type/src/vector.rs +++ b/crates/af-move-type/src/vector.rs @@ -3,7 +3,7 @@ use derive_more::{Deref, DerefMut, From, Into}; use derive_where::derive_where; use serde::{Deserialize, Serialize}; -use crate::{MoveType, ParseTypeTagError, StaticTypeTag, TypeTagError}; +use crate::{MoveType, MoveTypeTag, ParseTypeTagError, StaticTypeTag, TypeTagError}; #[derive( Clone, Debug, Deref, DerefMut, Deserialize, From, Into, Serialize, PartialEq, Eq, Hash, @@ -72,6 +72,15 @@ impl TryFrom for VecTypeTag { } } +impl MoveTypeTag for VecTypeTag { + fn matches(tag: &TypeTag) -> bool { + let TypeTag::Vector(inner) = tag else { + return false; + }; + T::TypeTag::matches(inner) + } +} + impl std::str::FromStr for VecTypeTag { type Err = ParseTypeTagError;