diff --git a/Cargo.toml b/Cargo.toml index a012c84..3fc6f3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-key-paths" -version = "2.9.2" +version = "2.9.3" edition = "2024" authors = ["Codefonsi "] license = "MPL-2.0" diff --git a/examples/basics.rs b/examples/basics.rs index 7e4cc94..266fe95 100644 --- a/examples/basics.rs +++ b/examples/basics.rs @@ -99,6 +99,12 @@ fn main() { // let kp = |root: &mut Rectangle| {(Rectangle::size().set)(root)}; // let x:fn() = || {}; + let kp = Rectangle::size().get; + let kp = Rectangle::size().set; + + let kp = Rectangle::size().then(Size::width()); + let kp= kp.get; + // let x: fn(&Rectangle) -> Option<&Size> = Rectangle::size().get; // let y = that_takes(x); diff --git a/examples/basics_casepath.rs b/examples/basics_casepath.rs index 474d9fb..c4642eb 100644 --- a/examples/basics_casepath.rs +++ b/examples/basics_casepath.rs @@ -1,6 +1,7 @@ use key_paths_derive::Kp; use parking_lot::{Mutex, RwLock}; use std::cell::{RefCell, RefMut}; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::sync::Arc; @@ -9,6 +10,9 @@ use std::sync::Arc; #[derive(Debug, Kp)] struct SomeComplexStruct { id: String, + scfs20: Option>, + scfs18: Option>, + scfs19: Option>, scsf: Option>, scfs2: Arc>, scfs3: Arc>, @@ -90,6 +94,8 @@ impl SomeComplexStruct { Self { id: String::from("SomeComplexStruct"), + scfs18: None, + scfs19: None, scsf: Some(Box::new(inner.clone())), // Arc> scfs2: Arc::new(std::sync::Mutex::new(inner.clone())), @@ -166,6 +172,7 @@ impl SomeComplexStruct { scfs_t_o_arc_rwo: Some(Arc::new(tokio::sync::RwLock::new(Some(SomeOtherStruct { sosf: Box::new(None), })))), + scfs20: Some(Vec::new()), } } } @@ -201,6 +208,10 @@ fn main() { // And Arc>> let x_pl_m: Option<&SomeOtherStruct> = SomeComplexStruct::scfs_arc_pl_mo().get(&instance); + let x = SomeComplexStruct::scfs18_at("testing".to_string()); + let x = SomeComplexStruct::scfs20(); + let x = SomeComplexStruct::scfs20_at(0); + println!("x = {:?}", x); assert!(x_pl_rw.is_some()); assert!(x_std_m.is_some()); assert!(x_std_rw.is_some()); diff --git a/key-paths-derive/Cargo.toml b/key-paths-derive/Cargo.toml index 83fac04..ce6a97a 100644 --- a/key-paths-derive/Cargo.toml +++ b/key-paths-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "key-paths-derive" -version = "2.4.0" +version = "2.6.0" edition = "2024" description = "Proc-macro derive to generate keypath methods" license = "MPL-2.0" diff --git a/key-paths-derive/src/lib.rs b/key-paths-derive/src/lib.rs index 2776624..3e1985a 100644 --- a/key-paths-derive/src/lib.rs +++ b/key-paths-derive/src/lib.rs @@ -624,6 +624,24 @@ fn extract_map_key_value(ty: &Type) -> Option<(Type, Type)> { None } +/// For `Option>` / `Option>`, returns `Some((key_ty, value_ty))`. +fn extract_map_key_value_through_option(ty: &Type) -> Option<(Type, Type)> { + use syn::{GenericArgument, PathArguments}; + + if let Type::Path(tp) = ty { + if let Some(seg) = tp.path.segments.last() { + if seg.ident == "Option" { + if let PathArguments::AngleBracketed(ab) = &seg.arguments { + if let Some(GenericArgument::Type(inner)) = ab.args.first() { + return extract_map_key_value(inner); + } + } + } + } + } + None +} + fn to_snake_case(name: &str) -> String { let mut out = String::new(); for (i, c) in name.chars().enumerate() { @@ -789,13 +807,183 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionVecDeque, Some(_inner_ty)) - | (WrapperKind::OptionLinkedList, Some(_inner_ty)) + (WrapperKind::OptionHashMap, Some(inner_ty)) => { + if let Some((key_ty, _)) = extract_map_key_value_through_option(ty) { + let type_name = name.to_string(); + let whole_fn = kp_fn.to_string(); + let at_fn = kp_at_fn.to_string(); + let whole_doc = format!( + "Keypath to the whole optional `HashMap` (`{type_name}::{whole_fn}`). For a value at one key when the map is `Some`, use `{type_name}::{at_fn}(key)`." + ); + let at_doc = format!( + "Keyed access into the inner `HashMap` (`{type_name}::{at_fn}`). `get`/`get_mut` return `None` if the field is `None` or the key is missing. See also `{type_name}::{whole_fn}` for the full optional map." + ); + tokens.extend(quote! { + #[doc = #whole_doc] + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + #[doc = #at_doc] + #[inline(always)] + pub fn #kp_at_fn(key: #key_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #key_ty: Clone + std::hash::Hash + Eq + 'static, + { + let key2 = key.clone(); + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#field_ident.as_ref().and_then(|m| m.get(&key))), + Box::new(move |root: &mut #name| root.#field_ident.as_mut().and_then(|m| m.get_mut(&key2))), + ) + } + }); + } else { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + }); + } + } + (WrapperKind::OptionBTreeMap, Some(inner_ty)) => { + if let Some((key_ty, _)) = extract_map_key_value_through_option(ty) { + let type_name = name.to_string(); + let whole_fn = kp_fn.to_string(); + let at_fn = kp_at_fn.to_string(); + let whole_doc = format!( + "Keypath to the whole optional `BTreeMap` (`{type_name}::{whole_fn}`). For a value at one key when the map is `Some`, use `{type_name}::{at_fn}(key)`." + ); + let at_doc = format!( + "Keyed access into the inner `BTreeMap` (`{type_name}::{at_fn}`). `get`/`get_mut` return `None` if the field is `None` or the key is missing. See also `{type_name}::{whole_fn}` for the full optional map." + ); + tokens.extend(quote! { + #[doc = #whole_doc] + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + #[doc = #at_doc] + #[inline(always)] + pub fn #kp_at_fn(key: #key_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #key_ty: Clone + Ord + 'static, + { + let key2 = key.clone(); + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#field_ident.as_ref().and_then(|m| m.get(&key))), + Box::new(move |root: &mut #name| root.#field_ident.as_mut().and_then(|m| m.get_mut(&key2))), + ) + } + }); + } else { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + }); + } + } + (WrapperKind::OptionHashSet, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + + /// _at: check if element exists and get reference. + /// HashSet does not allow mutable element access (would break hash invariant). + #[inline(always)] + pub fn #kp_at_fn(key: #inner_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #inner_ty: Clone + std::hash::Hash + Eq + 'static, + { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#field_ident.as_ref().and_then(|s| s.get(&key))), + Box::new(move |_root: &mut #name| None), + ) + } + }); + } + (WrapperKind::OptionBTreeSet, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + + /// _at: check if element exists and get reference. + /// BTreeSet does not allow mutable element access (would break ordering invariant). + #[inline(always)] + pub fn #kp_at_fn(key: #inner_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #inner_ty: Clone + Ord + 'static, + { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#field_ident.as_ref().and_then(|s| s.get(&key))), + Box::new(move |_root: &mut #name| None), + ) + } + }); + } + (WrapperKind::OptionVec, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + #[inline(always)] + pub fn #kp_at_fn(index: usize) -> rust_key_paths::KpDynamic<#name, #inner_ty> { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#field_ident.as_ref().and_then(|v| v.get(index))), + Box::new(move |root: &mut #name| root.#field_ident.as_mut().and_then(|v| v.get_mut(index))), + ) + } + }); + } + (WrapperKind::OptionVecDeque, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#field_ident), + |root: &mut #name| Some(&mut root.#field_ident), + ) + } + #[inline(always)] + pub fn #kp_at_fn(index: usize) -> rust_key_paths::KpDynamic<#name, #inner_ty> { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#field_ident.as_ref().and_then(|v| v.get(index))), + Box::new(move |root: &mut #name| root.#field_ident.as_mut().and_then(|v| v.get_mut(index))), + ) + } + }); + } + (WrapperKind::OptionLinkedList, Some(_inner_ty)) | (WrapperKind::OptionBinaryHeap, Some(_inner_ty)) - | (WrapperKind::OptionHashSet, Some(_inner_ty)) - | (WrapperKind::OptionBTreeSet, Some(_inner_ty)) - | (WrapperKind::OptionResult, Some(_inner_ty)) - | (WrapperKind::OptionBTreeMap, Some(_inner_ty)) => { + | (WrapperKind::OptionResult, Some(_inner_ty)) => { // Keypath to the Option container (reference), like Vec/HashSet tokens.extend(quote! { #[inline(always)] @@ -2102,13 +2290,183 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionVecDeque, Some(_inner_ty)) - | (WrapperKind::OptionLinkedList, Some(_inner_ty)) + (WrapperKind::OptionHashMap, Some(inner_ty)) => { + if let Some((key_ty, _)) = extract_map_key_value_through_option(ty) { + let type_name = name.to_string(); + let whole_fn = kp_fn.to_string(); + let at_fn = kp_at_fn.to_string(); + let whole_doc = format!( + "Keypath to the whole optional `HashMap` (`{type_name}::{whole_fn}`). For a value at one key when the map is `Some`, use `{type_name}::{at_fn}(key)`." + ); + let at_doc = format!( + "Keyed access into the inner `HashMap` (`{type_name}::{at_fn}`). `get`/`get_mut` return `None` if the field is `None` or the key is missing. See also `{type_name}::{whole_fn}` for the full optional map." + ); + tokens.extend(quote! { + #[doc = #whole_doc] + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + #[doc = #at_doc] + #[inline(always)] + pub fn #kp_at_fn(key: #key_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #key_ty: Clone + std::hash::Hash + Eq + 'static, + { + let key2 = key.clone(); + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#idx_lit.as_ref().and_then(|m| m.get(&key))), + Box::new(move |root: &mut #name| root.#idx_lit.as_mut().and_then(|m| m.get_mut(&key2))), + ) + } + }); + } else { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + }); + } + } + (WrapperKind::OptionBTreeMap, Some(inner_ty)) => { + if let Some((key_ty, _)) = extract_map_key_value_through_option(ty) { + let type_name = name.to_string(); + let whole_fn = kp_fn.to_string(); + let at_fn = kp_at_fn.to_string(); + let whole_doc = format!( + "Keypath to the whole optional `BTreeMap` (`{type_name}::{whole_fn}`). For a value at one key when the map is `Some`, use `{type_name}::{at_fn}(key)`." + ); + let at_doc = format!( + "Keyed access into the inner `BTreeMap` (`{type_name}::{at_fn}`). `get`/`get_mut` return `None` if the field is `None` or the key is missing. See also `{type_name}::{whole_fn}` for the full optional map." + ); + tokens.extend(quote! { + #[doc = #whole_doc] + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + #[doc = #at_doc] + #[inline(always)] + pub fn #kp_at_fn(key: #key_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #key_ty: Clone + Ord + 'static, + { + let key2 = key.clone(); + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#idx_lit.as_ref().and_then(|m| m.get(&key))), + Box::new(move |root: &mut #name| root.#idx_lit.as_mut().and_then(|m| m.get_mut(&key2))), + ) + } + }); + } else { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + }); + } + } + (WrapperKind::OptionHashSet, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + + /// _at: check if element exists and get reference. + /// HashSet does not allow mutable element access (would break hash invariant). + #[inline(always)] + pub fn #kp_at_fn(key: #inner_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #inner_ty: Clone + std::hash::Hash + Eq + 'static, + { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#idx_lit.as_ref().and_then(|s| s.get(&key))), + Box::new(move |_root: &mut #name| None), + ) + } + }); + } + (WrapperKind::OptionBTreeSet, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + + /// _at: check if element exists and get reference. + /// BTreeSet does not allow mutable element access (would break ordering invariant). + #[inline(always)] + pub fn #kp_at_fn(key: #inner_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #inner_ty: Clone + Ord + 'static, + { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#idx_lit.as_ref().and_then(|s| s.get(&key))), + Box::new(move |_root: &mut #name| None), + ) + } + }); + } + (WrapperKind::OptionVec, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + #[inline(always)] + pub fn #kp_at_fn(index: usize) -> rust_key_paths::KpDynamic<#name, #inner_ty> { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#idx_lit.as_ref().and_then(|v| v.get(index))), + Box::new(move |root: &mut #name| root.#idx_lit.as_mut().and_then(|v| v.get_mut(index))), + ) + } + }); + } + (WrapperKind::OptionVecDeque, Some(inner_ty)) => { + tokens.extend(quote! { + #[inline(always)] + pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { + rust_key_paths::Kp::new( + |root: &#name| Some(&root.#idx_lit), + |root: &mut #name| Some(&mut root.#idx_lit), + ) + } + #[inline(always)] + pub fn #kp_at_fn(index: usize) -> rust_key_paths::KpDynamic<#name, #inner_ty> { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| root.#idx_lit.as_ref().and_then(|v| v.get(index))), + Box::new(move |root: &mut #name| root.#idx_lit.as_mut().and_then(|v| v.get_mut(index))), + ) + } + }); + } + (WrapperKind::OptionLinkedList, Some(_inner_ty)) | (WrapperKind::OptionBinaryHeap, Some(_inner_ty)) - | (WrapperKind::OptionHashSet, Some(_inner_ty)) - | (WrapperKind::OptionBTreeSet, Some(_inner_ty)) - | (WrapperKind::OptionResult, Some(_inner_ty)) - | (WrapperKind::OptionBTreeMap, Some(_inner_ty)) => { + | (WrapperKind::OptionResult, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #kp_fn() -> rust_key_paths::KpType<'static, #name, #ty> { @@ -2992,13 +3350,245 @@ pub fn derive_keypaths(input: TokenStream) -> TokenStream { } }); } - (WrapperKind::OptionVecDeque, Some(_inner_ty)) - | (WrapperKind::OptionLinkedList, Some(_inner_ty)) + (WrapperKind::OptionHashMap, Some(inner_ty)) => { + let snake_at = format_ident!("{}_at", snake); + if let Some((key_ty, _)) = extract_map_key_value_through_option(field_ty) { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + #[inline(always)] + pub fn #snake_at(key: #key_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #key_ty: Clone + std::hash::Hash + Eq + 'static, + { + let key2 = key.clone(); + rust_key_paths::Kp::new( + Box::new(move |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().and_then(|m| m.get(&key)), + _ => None, + }), + Box::new(move |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_mut().and_then(|m| m.get_mut(&key2)), + _ => None, + }), + ) + } + }); + } else { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + }); + } + } + (WrapperKind::OptionBTreeMap, Some(inner_ty)) => { + let snake_at = format_ident!("{}_at", snake); + if let Some((key_ty, _)) = extract_map_key_value_through_option(field_ty) { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + #[inline(always)] + pub fn #snake_at(key: #key_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #key_ty: Clone + Ord + 'static, + { + let key2 = key.clone(); + rust_key_paths::Kp::new( + Box::new(move |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().and_then(|m| m.get(&key)), + _ => None, + }), + Box::new(move |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_mut().and_then(|m| m.get_mut(&key2)), + _ => None, + }), + ) + } + }); + } else { + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + }); + } + } + (WrapperKind::OptionHashSet, Some(inner_ty)) => { + let snake_at = format_ident!("{}_at", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + + /// _at: check if element exists and get reference. + /// HashSet does not allow mutable element access (would break hash invariant). + #[inline(always)] + pub fn #snake_at(key: #inner_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #inner_ty: Clone + std::hash::Hash + Eq + 'static, + { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().and_then(|s| s.get(&key)), + _ => None, + }), + Box::new(move |_root: &mut #name| None), + ) + } + }); + } + (WrapperKind::OptionBTreeSet, Some(inner_ty)) => { + let snake_at = format_ident!("{}_at", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + + /// _at: check if element exists and get reference. + /// BTreeSet does not allow mutable element access (would break ordering invariant). + #[inline(always)] + pub fn #snake_at(key: #inner_ty) -> rust_key_paths::KpDynamic<#name, #inner_ty> + where + #inner_ty: Clone + Ord + 'static, + { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().and_then(|s| s.get(&key)), + _ => None, + }), + Box::new(move |_root: &mut #name| None), + ) + } + }); + } + (WrapperKind::OptionVec, Some(inner_ty)) => { + let snake_at = format_ident!("{}_at", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + #[inline(always)] + pub fn #snake_at(index: usize) -> rust_key_paths::KpDynamic<#name, #inner_ty> { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().and_then(|v| v.get(index)), + _ => None, + }), + Box::new(move |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_mut().and_then(|v| v.get_mut(index)), + _ => None, + }), + ) + } + }); + } + (WrapperKind::OptionVecDeque, Some(inner_ty)) => { + let snake_at = format_ident!("{}_at", snake); + tokens.extend(quote! { + #[inline(always)] + pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { + rust_key_paths::Kp::new( + |root: &#name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + |root: &mut #name| match root { + #name::#v_ident(inner) => Some(inner), + _ => None, + }, + ) + } + #[inline(always)] + pub fn #snake_at(index: usize) -> rust_key_paths::KpDynamic<#name, #inner_ty> { + rust_key_paths::Kp::new( + Box::new(move |root: &#name| match root { + #name::#v_ident(inner) => inner.as_ref().and_then(|v| v.get(index)), + _ => None, + }), + Box::new(move |root: &mut #name| match root { + #name::#v_ident(inner) => inner.as_mut().and_then(|v| v.get_mut(index)), + _ => None, + }), + ) + } + }); + } + (WrapperKind::OptionLinkedList, Some(_inner_ty)) | (WrapperKind::OptionBinaryHeap, Some(_inner_ty)) - | (WrapperKind::OptionHashSet, Some(_inner_ty)) - | (WrapperKind::OptionBTreeSet, Some(_inner_ty)) - | (WrapperKind::OptionResult, Some(_inner_ty)) - | (WrapperKind::OptionBTreeMap, Some(_inner_ty)) => { + | (WrapperKind::OptionResult, Some(_inner_ty)) => { tokens.extend(quote! { #[inline(always)] pub fn #snake() -> rust_key_paths::KpType<'static, #name, #field_ty> { diff --git a/key-paths-derive/tests/derive_test.rs b/key-paths-derive/tests/derive_test.rs index dd35829..a395b57 100644 --- a/key-paths-derive/tests/derive_test.rs +++ b/key-paths-derive/tests/derive_test.rs @@ -1,5 +1,6 @@ use key_paths_derive::{Akp, Kp, Pkp}; -use rust_key_paths::KpType; +use rust_key_paths::{KpTrait, KpType}; +use std::collections::{HashMap, HashSet}; #[derive(Kp, Pkp, Akp)] struct Person { @@ -161,3 +162,70 @@ fn test_any_kps() { let age_val = kps[1].get_as::(&person); assert_eq!(age_val, Some(Some(&28))); } + +#[derive(Kp)] +struct WithOptionalCollections { + map: Option>, + set: Option>, +} + +#[test] +fn option_hash_map_at_returns_value_ref() { + let s = WithOptionalCollections { + map: Some(HashMap::from([("k".to_string(), 42)])), + set: Some(HashSet::from(["x".to_string()])), + }; + assert_eq!(WithOptionalCollections::map_at("k".to_string()).get(&s), Some(&42)); + + let mut s = s; + WithOptionalCollections::map_at("k".to_string()) + .get_mut(&mut s) + .map(|v| *v += 1); + assert_eq!(s.map.as_ref().unwrap().get("k"), Some(&43)); + + assert_eq!( + WithOptionalCollections::set_at("x".to_string()).get(&s), + Some(&"x".to_string()) + ); +} + +#[derive(Kp)] +enum OptMapEnum { + M(Option>), +} + +#[test] +fn option_hash_map_enum_variant_at() { + let e = OptMapEnum::M(Some(HashMap::from([(1u8, "a".to_string())]))); + assert_eq!(OptMapEnum::m_at(1u8).get(&e), Some(&"a".to_string())); +} + +#[derive(Kp)] +struct WithOptionalVec { + items: Option>, +} + +#[test] +fn option_vec_at_index() { + let s = WithOptionalVec { + items: Some(vec![10, 20]), + }; + assert_eq!(WithOptionalVec::items_at(1).get(&s), Some(&20)); + + let mut s = s; + WithOptionalVec::items_at(0) + .get_mut(&mut s) + .map(|x| *x = 99); + assert_eq!(s.items, Some(vec![99, 20])); +} + +#[derive(Kp)] +enum OptVecEnum { + V(Option>), +} + +#[test] +fn option_vec_enum_variant_at() { + let e = OptVecEnum::V(Some(vec![1, 2, 3])); + assert_eq!(OptVecEnum::v_at(2).get(&e), Some(&3u8)); +} diff --git a/src/async_lock.rs b/src/async_lock.rs index ad4de19..7070ecf 100644 --- a/src/async_lock.rs +++ b/src/async_lock.rs @@ -31,6 +31,7 @@ use crate::{AccessorTrait, Kp, KpTrait}; use async_trait::async_trait; +use std::fmt; // Re-export tokio sync types for convenience #[cfg(feature = "tokio")] pub use tokio::sync::{Mutex as TokioMutex, RwLock as TokioRwLock}; @@ -301,6 +302,134 @@ pub struct AsyncLockKp< pub(crate) next: Kp, } +impl< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, +> fmt::Debug + for AsyncLockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + > +where + Root: std::borrow::Borrow, + LockValue: std::borrow::Borrow, + MidValue: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutLock: std::borrow::BorrowMut, + MutMid: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G1: Fn(Root) -> Option + Clone, + S1: Fn(MutRoot) -> Option + Clone, + L: AsyncLockLike + AsyncLockLike + Clone, + G2: Fn(MidValue) -> Option + Clone, + S2: Fn(MutMid) -> Option + Clone, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AsyncLockKp") + .field("root_ty", &std::any::type_name::()) + .field("lock_ty", &std::any::type_name::()) + .field("mid_ty", &std::any::type_name::()) + .field("value_ty", &std::any::type_name::()) + .finish_non_exhaustive() + } +} + +impl< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, +> fmt::Display + for AsyncLockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + > +where + Root: std::borrow::Borrow, + LockValue: std::borrow::Borrow, + MidValue: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutLock: std::borrow::BorrowMut, + MutMid: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G1: Fn(Root) -> Option + Clone, + S1: Fn(MutRoot) -> Option + Clone, + L: AsyncLockLike + AsyncLockLike + Clone, + G2: Fn(MidValue) -> Option + Clone, + S2: Fn(MutMid) -> Option + Clone, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "AsyncLockKp<{}, {}, {}, {}>", + std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::() + ) + } +} + impl< R, Lock, diff --git a/src/lib.rs b/src/lib.rs index f77ae03..2386d0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ // type Getter where Root: std::borrow::Borrow, Value: std::borrow::Borrow = fn(Root) -> Option; // type Setter = fn(&'r mut R) -> Option<&'r mut V>; +use std::fmt; use std::sync::Arc; // Export the lock module @@ -324,10 +325,6 @@ where } impl<'a, R, V> From> for KpDynamic -where - 'a: 'static, - R: 'static, - V: 'static, { #[inline] fn from(kp: KpType<'a, R, V>) -> Self { @@ -690,6 +687,26 @@ impl AKp { } } } + +impl fmt::Debug for AKp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AKp") + .field("root_type_id", &self.root_type_id) + .field("value_type_id", &self.value_type_id) + .finish_non_exhaustive() + } +} + +impl fmt::Display for AKp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "AKp(root_type_id={:?}, value_type_id={:?})", + self.root_type_id, self.value_type_id + ) + } +} + pub struct PKp { getter: Rc Fn(&'r Root) -> Option<&'r dyn Any>>, value_type_id: TypeId, @@ -897,6 +914,26 @@ where } } +impl fmt::Debug for PKp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PKp") + .field("root_ty", &std::any::type_name::()) + .field("value_type_id", &self.value_type_id) + .finish_non_exhaustive() + } +} + +impl fmt::Display for PKp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "PKp<{}, value_type_id={:?}>", + std::any::type_name::(), + self.value_type_id + ) + } +} + // ========== ANY KEYPATHS (Hide Both Root and Value Types) ========== pub trait KpTrait { @@ -936,8 +973,7 @@ pub trait KpTrait { SubValue: std::borrow::Borrow, MutSubValue: std::borrow::BorrowMut, G2: Fn(Value) -> Option, - S2: Fn(MutValue) -> Option, - V: 'static; + S2: Fn(MutValue) -> Option,; } pub trait ChainExt { @@ -1034,7 +1070,6 @@ pub trait ChainExt { pin_fut: L, ) -> crate::pin::KpThenPinFuture where - V: 'static, Struct: Unpin + 'static, Output: 'static, Value: std::borrow::Borrow, @@ -1060,7 +1095,6 @@ pub trait ChainExt { AsyncKp, > where - V: 'static, Value: std::borrow::Borrow, MutValue: std::borrow::BorrowMut, AsyncKp: crate::async_lock::AsyncKeyPathLike, @@ -1178,7 +1212,6 @@ where pin_fut: L, ) -> crate::pin::KpThenPinFuture where - V: 'static, Struct: Unpin + 'static, Output: 'static, Value: std::borrow::Borrow, @@ -1209,7 +1242,6 @@ where AsyncKp, > where - V: 'static, Value: std::borrow::Borrow, MutValue: std::borrow::BorrowMut, AsyncKp: crate::async_lock::AsyncKeyPathLike, @@ -1383,7 +1415,6 @@ where > where F: Fn(&V) -> MappedValue + Copy + 'static, - V: 'static, MappedValue: 'static, { Kp::new( @@ -1418,16 +1449,15 @@ where > where F: Fn(&V) -> bool + Copy + 'static, - V: 'static, { Kp::new( - move |root: Root| { + move |root: Root| { self.get(root).filter(|value| { let v: &V = value.borrow(); predicate(v) }) }, - move |root: MutRoot| { + move |root: MutRoot| { self.get_mut(root).filter(|value| { let v: &V = value.borrow(); predicate(v) @@ -1452,8 +1482,6 @@ where > where F: Fn(&V) -> Option + Copy + 'static, - V: 'static, - MappedValue: 'static, { Kp::new( move |root: Root| { @@ -1487,7 +1515,6 @@ where > where F: Fn(&V) + Copy + 'static, - V: 'static, { Kp::new( move |root: Root| { @@ -1511,9 +1538,7 @@ where fn flat_map(&self, mapper: F) -> impl Fn(Root) -> Vec + '_ where F: Fn(&V) -> I + 'static, - V: 'static, I: IntoIterator, - Item: 'static, { move |root: Root| { self.get(root) @@ -1529,7 +1554,6 @@ where fn fold_value(&self, init: Acc, folder: F) -> impl Fn(Root) -> Acc + '_ where F: Fn(Acc, &V) -> Acc + 'static, - V: 'static, Acc: Copy + 'static, { move |root: Root| { @@ -1546,7 +1570,6 @@ where fn any(&self, predicate: F) -> impl Fn(Root) -> bool + '_ where F: Fn(&V) -> bool + 'static, - V: 'static, { move |root: Root| { self.get(root) @@ -1562,7 +1585,6 @@ where fn all(&self, predicate: F) -> impl Fn(Root) -> bool + '_ where F: Fn(&V) -> bool + 'static, - V: 'static, { move |root: Root| { self.get(root) @@ -1578,7 +1600,6 @@ where fn count_items(&self, counter: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V) -> usize + 'static, - V: 'static, { move |root: Root| { self.get(root).map(|value| { @@ -1592,8 +1613,6 @@ where fn find_in(&self, finder: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V) -> Option + 'static, - V: 'static, - Item: 'static, { move |root: Root| { self.get(root).and_then(|value| { @@ -1607,8 +1626,6 @@ where fn take(&self, n: usize, taker: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V, usize) -> Output + 'static, - V: 'static, - Output: 'static, { move |root: Root| { self.get(root).map(|value| { @@ -1622,8 +1639,6 @@ where fn skip(&self, n: usize, skipper: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V, usize) -> Output + 'static, - V: 'static, - Output: 'static, { move |root: Root| { self.get(root).map(|value| { @@ -1637,8 +1652,6 @@ where fn partition_value(&self, partitioner: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V) -> Output + 'static, - V: 'static, - Output: 'static, { move |root: Root| { self.get(root).map(|value| { @@ -1652,8 +1665,6 @@ where fn min_value(&self, min_fn: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V) -> Option + 'static, - V: 'static, - Item: 'static, { move |root: Root| { self.get(root).and_then(|value| { @@ -1667,8 +1678,6 @@ where fn max_value(&self, max_fn: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V) -> Option + 'static, - V: 'static, - Item: 'static, { move |root: Root| { self.get(root).and_then(|value| { @@ -1682,8 +1691,6 @@ where fn sum_value(&self, sum_fn: F) -> impl Fn(Root) -> Option + '_ where F: Fn(&V) -> Sum + 'static, - V: 'static, - Sum: 'static, { move |root: Root| { self.get(root).map(|value| { @@ -1773,7 +1780,6 @@ where MutSubValue: std::borrow::BorrowMut, G2: Fn(Value) -> Option, S2: Fn(MutValue) -> Option, - V: 'static, { Kp::new( move |root: Root| (self.get)(root).and_then(|value| (next.get)(value)), @@ -2025,14 +2031,53 @@ where MutSubValue: std::borrow::BorrowMut, G2: Fn(Value) -> Option, S2: Fn(MutValue) -> Option, - V: 'static, { + Kp::new( move |root: Root| (self.get)(root).and_then(|value| (next.get)(value)), move |root: MutRoot| (self.set)(root).and_then(|value| (next.set)(value)), ) } } + +impl fmt::Debug + for Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Kp") + .field("root_ty", &std::any::type_name::()) + .field("value_ty", &std::any::type_name::()) + .finish_non_exhaustive() + } +} + +impl fmt::Display + for Kp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Kp<{}, {}>", + std::any::type_name::(), + std::any::type_name::() + ) + } +} + /// Zip two keypaths together to create a tuple /// Works only with KpType (reference-based keypaths) /// @@ -2267,6 +2312,46 @@ where } } +impl fmt::Debug + for EnumKp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, + E: Fn(Variant) -> Enum, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EnumKp") + .field("enum_ty", &std::any::type_name::()) + .field("variant_ty", &std::any::type_name::()) + .finish_non_exhaustive() + } +} + +impl fmt::Display + for EnumKp +where + Root: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G: Fn(Root) -> Option, + S: Fn(MutRoot) -> Option, + E: Fn(Variant) -> Enum, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "EnumKp<{}, {}>", + std::any::type_name::(), + std::any::type_name::() + ) + } +} + // Type alias for the common case with references pub type EnumKpType<'a, Enum, Variant> = EnumKp< Enum, @@ -2571,6 +2656,35 @@ mod tests { } } + #[test] + fn kp_debug_display_uses_type_names() { + let kp = TestKP::a(); + let dbg = format!("{kp:?}"); + assert!(dbg.starts_with("Kp {"), "{dbg}"); + assert!(dbg.contains("root_ty") && dbg.contains("value_ty"), "{dbg}"); + let disp = format!("{kp}"); + assert!(disp.contains("TestKP"), "{disp}"); + assert!(disp.contains("String"), "{disp}"); + } + + #[test] + fn akp_and_pkp_debug_display() { + let akp = AKp::new(TestKP::a()); + assert!(format!("{akp:?}").starts_with("AKp")); + let pkp = PKp::new(TestKP::a()); + let pkp_dbg = format!("{pkp:?}"); + assert!(pkp_dbg.starts_with("PKp"), "{pkp_dbg}"); + assert!(format!("{pkp}").contains("TestKP")); + } + + #[test] + fn enum_kp_debug_display() { + let ok_kp = enum_ok::(); + assert!(format!("{ok_kp:?}").contains("EnumKp")); + let s = format!("{ok_kp}"); + assert!(s.contains("Result") && s.contains("i32"), "{s}"); + } + #[derive(Debug)] struct TestKP2 { a: String, diff --git a/src/lock.rs b/src/lock.rs index 33686b3..63db714 100644 --- a/src/lock.rs +++ b/src/lock.rs @@ -43,6 +43,7 @@ //! - Rust's ownership system enforces correctness use crate::Kp; +use std::fmt; use std::sync::{Arc, Mutex}; /// Trait for types that can provide lock/unlock behavior @@ -146,6 +147,134 @@ pub struct LockKp< pub(crate) next: Kp, } +impl< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, +> fmt::Debug + for LockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + > +where + Root: std::borrow::Borrow, + LockValue: std::borrow::Borrow, + MidValue: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutLock: std::borrow::BorrowMut, + MutMid: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G1: Fn(Root) -> Option, + S1: Fn(MutRoot) -> Option, + L: LockAccess + LockAccess, + G2: Fn(MidValue) -> Option, + S2: Fn(MutMid) -> Option, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LockKp") + .field("root_ty", &std::any::type_name::()) + .field("lock_ty", &std::any::type_name::()) + .field("mid_ty", &std::any::type_name::()) + .field("value_ty", &std::any::type_name::()) + .finish_non_exhaustive() + } +} + +impl< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, +> fmt::Display + for LockKp< + R, + Lock, + Mid, + V, + Root, + LockValue, + MidValue, + Value, + MutRoot, + MutLock, + MutMid, + MutValue, + G1, + S1, + L, + G2, + S2, + > +where + Root: std::borrow::Borrow, + LockValue: std::borrow::Borrow, + MidValue: std::borrow::Borrow, + Value: std::borrow::Borrow, + MutRoot: std::borrow::BorrowMut, + MutLock: std::borrow::BorrowMut, + MutMid: std::borrow::BorrowMut, + MutValue: std::borrow::BorrowMut, + G1: Fn(Root) -> Option, + S1: Fn(MutRoot) -> Option, + L: LockAccess + LockAccess, + G2: Fn(MidValue) -> Option, + S2: Fn(MutMid) -> Option, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "LockKp<{}, {}, {}, {}>", + std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::() + ) + } +} + impl< R, Lock, @@ -721,6 +850,32 @@ pub struct KpThenLockKp, } +impl fmt::Debug + for KpThenLockKp +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KpThenLockKp") + .field("root_ty", &std::any::type_name::()) + .field("via_ty", &std::any::type_name::()) + .field("value_ty", &std::any::type_name::()) + .finish_non_exhaustive() + } +} + +impl fmt::Display + for KpThenLockKp +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "KpThenLockKp<{}, {}, {}>", + std::any::type_name::(), + std::any::type_name::(), + std::any::type_name::() + ) + } +} + impl KpThenLockKp where