From 5b23a1eac6439fff930c9b0eafae7680339dcd4b Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 27 Aug 2025 10:46:47 -0600 Subject: [PATCH] destructure --- patch-db-macro-internals/src/lib.rs | 60 ++++++++++++++++++++++++++++- patch-db/src/lib.rs | 2 +- patch-db/src/model.rs | 17 +++++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/patch-db-macro-internals/src/lib.rs b/patch-db-macro-internals/src/lib.rs index eb1ae25..98ab367 100644 --- a/patch-db-macro-internals/src/lib.rs +++ b/patch-db-macro-internals/src/lib.rs @@ -131,14 +131,19 @@ struct Fns { impl_fns: TokenStream, impl_ref_fns: TokenStream, impl_mut_fns: TokenStream, + impl_mut_destructure: TokenStream, } -fn impl_fns(model_ty: &Type, children: &[ChildInfo]) -> Fns { +fn impl_fns(model_ty: &Type, children: &[ChildInfo], name: &Ident) -> Fns { let mut parts_args = TokenStream::new(); let mut parts_assignments = TokenStream::new(); let mut impl_fns = TokenStream::new(); let mut impl_ref_fns = TokenStream::new(); let mut impl_mut_fns = TokenStream::new(); + let mut destructure_members = TokenStream::new(); + let mut init_destructure_members = TokenStream::new(); + let mut mkdestructure_members = TokenStream::new(); + let mut destructure_member_idents = TokenStream::new(); for ChildInfo { vis, name, @@ -210,6 +215,7 @@ fn impl_fns(model_ty: &Type, children: &[ChildInfo]) -> Fns { } else { quote! { v } }; + let child_model_ty = replace_self(model_ty.clone(), &ty); parts_args.extend(quote_spanned! { name.span() => #name: #child_model_ty, @@ -235,7 +241,33 @@ fn impl_fns(model_ty: &Type, children: &[ChildInfo]) -> Fns { self.transmute_mut(|v| #accessor_mut) } }); + if let Some(accessor) = accessor { + destructure_members.extend(quote_spanned! { name.span() => + #vis #name: &'a mut #child_model_ty, + }); + init_destructure_members.extend(quote_spanned! { name.span() => + { + #[allow(unused_imports)] + use patch_db::value::index::Index; + #accessor.index_or_insert(self.as_value_mut()) + } + }); + mkdestructure_members.extend(quote_spanned! { name.span() => + let #name = <#child_model_ty>::value_as_mut(__patch_db__destructure_map.remove(#accessor).unwrap()); + }); + destructure_member_idents.extend(quote_spanned! { name.span() => #name, }); + } } + + let mod_name = Ident::new(&format!("__patch_db__destructure_{name}"), name.span()); + let explicit_model_ty = replace_self( + model_ty.clone(), + &Type::Path(TypePath { + qself: None, + path: name.clone().into(), + }), + ); + Fns { from_parts: quote! { pub fn from_parts(#parts_args) -> Self { @@ -249,6 +281,28 @@ fn impl_fns(model_ty: &Type, children: &[ChildInfo]) -> Fns { impl_fns, impl_ref_fns, impl_mut_fns, + impl_mut_destructure: quote! { + #[allow(non_snake_case)] + mod #mod_name { + use super::*; + pub struct DestructuredMut<'a> { + __patch_db__destructure_phantom: std::marker::PhantomData<&'a ()>, + #destructure_members + } + impl patch_db::DestructureMut for #explicit_model_ty { + type Destructured<'a> = DestructuredMut<'a>; + fn destructure_mut<'a>(&'a mut self) -> Self::Destructured<'a> { + use patch_db::ModelExt; + let mut __patch_db__destructure_map: std::collections::BTreeMap<_, _> = self.children_mut().into_iter().collect(); + #mkdestructure_members + DestructuredMut { + __patch_db__destructure_phantom: std::marker::PhantomData, + #destructure_member_idents + } + } + } + } + }, } } @@ -349,7 +403,8 @@ fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream { impl_fns, impl_ref_fns, impl_mut_fns, - } = impl_fns(&model_ty, &children); + impl_mut_destructure, + } = impl_fns(&model_ty, &children, name); quote! { impl patch_db::HasModel for #name { type Model = #model_ty; @@ -360,6 +415,7 @@ fn build_model_struct(base: &DeriveInput, ast: &DataStruct) -> TokenStream { #impl_ref_fns #impl_mut_fns } + #impl_mut_destructure } } diff --git a/patch-db/src/lib.rs b/patch-db/src/lib.rs index 0f69c2d..95dbfee 100644 --- a/patch-db/src/lib.rs +++ b/patch-db/src/lib.rs @@ -15,7 +15,7 @@ mod subscriber; mod test; pub use imbl_value::Value; -pub use model::{HasModel, Model, ModelExt}; +pub use model::{DestructureMut, HasModel, Model, ModelExt}; pub use patch::{DiffPatch, Dump, Revision}; pub use patch_db_macro::HasModel; pub use store::{MutateResult, PatchDb, Store, TypedPatchDb}; diff --git a/patch-db/src/model.rs b/patch-db/src/model.rs index 0a68cdd..3444aee 100644 --- a/patch-db/src/model.rs +++ b/patch-db/src/model.rs @@ -1,4 +1,4 @@ -use imbl_value::Value; +use imbl_value::{InternedString, Value}; pub trait HasModel: Sized { type Model: Model; @@ -78,9 +78,24 @@ pub trait ModelExt: Model { ) -> &'a mut Self::Model { Self::Model::::value_as_mut(f(::as_value_mut(self))) } + fn children_mut<'a>( + &'a mut self, + ) -> impl IntoIterator + Send + Sync { + ModelExt::::as_value_mut(self) + .as_object_mut() + .into_iter() + .flat_map(|o| o.iter_mut().map(|(k, v)| (&*k, v))) + } } impl> ModelExt for M {} +pub trait DestructureMut { + type Destructured<'a> + where + Self: 'a; + fn destructure_mut<'a>(&'a mut self) -> Self::Destructured<'a>; +} + #[cfg(test)] mod test { use std::marker::PhantomData;