| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | src/any.rs | 21 | ||||
| -rw-r--r-- | src/any/static_wrapper.rs | 28 | ||||
| -rw-r--r-- | src/build.rs | 216 | ||||
| -rw-r--r-- | src/build/builders/core/bool.rs | 4 | ||||
| -rw-r--r-- | src/build/builders/debug.rs | 20 | ||||
| -rw-r--r-- | src/effect.rs | 2 | ||||
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/mock.rs | 81 | ||||
| -rw-r--r-- | src/mock/builder.rs | 99 | ||||
| -rw-r--r-- | src/mock/protocol.rs | 2 | ||||
| -rw-r--r-- | src/mock/protocol/tag.rs | 33 | ||||
| -rw-r--r-- | src/mock/protocol/value.rs | 38 | ||||
| -rw-r--r-- | src/protocol/visitor.rs | 1 | ||||
| -rw-r--r-- | src/protocol/visitor/value.rs | 6 | ||||
| -rw-r--r-- | src/walk.rs | 4 | ||||
| -rw-r--r-- | src/walk/walkers/core/noop.rs | 1 | ||||
| -rw-r--r-- | src/walk/walkers/core/struct.rs | 152 | ||||
| -rw-r--r-- | src/walk/walkers/core/value.rs | 1 |
19 files changed, 471 insertions, 245 deletions
@@ -11,12 +11,14 @@ include = ["LICENSE-APACHE", "LICENSE-MIT", "README.md", "empty.rs"] [dependencies] serde = { version = "1.0", default-features = false, optional = true } +mockall = { version = "0.12.1", optional = true } [features] -default = ["std", "serde"] +default = ["std", "serde", "mock"] std = ["alloc", "serde?/std"] alloc = ["serde?/alloc"] serde = ["dep:serde"] +mock = ["std", "dep:mockall"] [dev-dependencies] macro_rules_attribute = "0.2.0" @@ -23,6 +23,7 @@ pub mod static_wrapper; use crate::{bijective_higher_ranked_trait, bijective_higher_ranked_type}; use core::{ + any::TypeId, marker::{PhantomData, PhantomPinned}, mem::{ManuallyDrop, MaybeUninit}, }; @@ -35,7 +36,7 @@ bijective_higher_ranked_trait! { } bijective_higher_ranked_trait! { - pub type class TypeName[][]: {'static} [for<'lt> MaybeSized::Trait<'lt>] + pub type class TypeName[][]: {'static} [for<'lt> MaybeSized::Trait<'lt> + Send] } bijective_higher_ranked_type! { @@ -117,6 +118,14 @@ pub struct LtTypeId<'lt> { name: &'static str, } +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)] +pub struct ForAnyTypeId { + /// The type ID of the name type of the type. + name_id: core::any::TypeId, + + name: &'static str, +} + impl<'lt> LtTypeId<'lt> { /// Get the ID of a type. /// @@ -129,6 +138,16 @@ impl<'lt> LtTypeId<'lt> { name: core::any::type_name::<T>(), } } + + /// Get the type ID of the static form. + /// + /// This will be the same for all lifetimes. + pub fn as_type_id(&self) -> ForAnyTypeId { + ForAnyTypeId { + name_id: self.name_id, + name: self.name, + } + } } impl<'lt> core::fmt::Display for LtTypeId<'lt> { diff --git a/src/any/static_wrapper.rs b/src/any/static_wrapper.rs index fb719e0..4acb200 100644 --- a/src/any/static_wrapper.rs +++ b/src/any/static_wrapper.rs @@ -7,19 +7,31 @@ use super::*; #[repr(transparent)] pub struct OwnedStatic<T: ?Sized>(pub T); -bijective_higher_ranked_type! { - pub type DynOwnedStatic['lt][T][]: MaybeSized['lt][] - for<'a> - (OwnedStatic<T>) - where { - T: ?Sized + 'lt - } +// bijective_higher_ranked_type! { +// pub type DynOwnedStatic['lt][T][]: MaybeSized['lt][] +// for<'a> +// (OwnedStatic<T>) +// where { +// T: ?Sized + 'lt +// } +// } + +pub struct DynOwnedStatic<T: ?Sized>(PhantomData<fn() -> *const T>); + +impl<'a, 'lt, T: ?Sized + 'lt> MaybeSized::LowerForLt<'a, 'lt, &'a (&'lt (),)> + for DynOwnedStatic<T> +{ + type T = OwnedStatic<T>; +} + +impl<'a, 'lt, T: ?Sized + 'lt> MaybeSized::RaiseForLt<'a, 'lt, &'a (&'lt (),)> for OwnedStatic<T> { + type HigherRanked = DynOwnedStatic<T>; } bijective_higher_ranked_type! { pub type [][T][]: TypeName[][] for<'lt> - (DynOwnedStatic<'lt, T>) + (DynOwnedStatic<T>) where { T: ?Sized + 'static } diff --git a/src/build.rs b/src/build.rs index f116bcb..24232a0 100644 --- a/src/build.rs +++ b/src/build.rs @@ -52,219 +52,3 @@ pub trait Builder<'ctx, E: Effect<'ctx>>: BuilderTypes + Sized + Send { /// This is expected to just be `self`. fn as_visitor(&mut self) -> Visitor<'_, 'ctx>; } - -#[cfg(test)] -pub mod test { - use std::{ - collections::HashMap, - sync::{Mutex, MutexGuard, PoisonError}, - }; - - use crate::{ - any::{ - static_wrapper::{DynOwnedStatic, OwnedStatic}, - AnyTrait, Boxed, Indirect, IndirectLtAny, LtTypeId, MaybeSized, Mut, Ref, TypeName, - }, - effect::{BlockOn, Blocking, Spin}, - protocol::visitor::value::{DynValue, Value}, - Flow, - }; - - use super::*; - - use mockall::mock; - use mockall::predicate::eq; - - pub mod mock { - use std::collections::HashMap; - - use super::*; - - mock! { - pub Builder<Seed: 'static, Value: 'static, Error: 'static> { - pub fn private_from_seed(seed: Seed) -> Self; - pub fn private_build(self) -> Result<Value, Error>; - - // pub fn private_upcast_to_id(&self, id: LtTypeId<'static>) -> Option<Box<dyn for<'a> MockIndirect>>; - // pub fn private_upcast_to_id_mut(&mut self, id: LtTypeId<'static>) -> Option<Box<dyn for<'a> MockIndirect<'a, Mut>>>; - - pub fn lookup(&self) -> &HashMap<LtTypeId<'static>, Box<dyn MockIndirect>>; - pub fn lookup_mut(&mut self) -> &mut HashMap<LtTypeId<'static>, Box<dyn MockIndirect>>; - } - } - } - - pub type MockBuilder<Seed = (), Value = (), Error = ()> = mock::MockBuilder<Seed, Value, Error>; - - pub trait MockIndirect: Send { - fn indirect(&self) -> IndirectLtAny<'_, 'static, Ref>; - fn indirect_mut<'a: 'b, 'b>(&'a mut self) -> IndirectLtAny<'b, 'static, Mut>; - } - - impl MockIndirect for MockValueVisitor { - fn indirect(&self) -> IndirectLtAny<'_, 'static, Ref> { - IndirectLtAny::new::<DynValue<'static, DynOwnedStatic<'static, i32>, Blocking>>( - self as _, - ) - } - - fn indirect_mut<'a: 'b, 'b>(&'a mut self) -> IndirectLtAny<'b, 'static, Mut> { - IndirectLtAny::<'b, 'static, Mut>::new::< - DynValue<'static, DynOwnedStatic<'static, i32>, Blocking>, - >(self as _) - } - } - - // pub struct IndirectBox<'a, 'ctx>(IndirectLtAny<'a, 'ctx, Boxed>); - // - // impl MockIndirect for IndirectBox - // { - // fn indirect<'a>(&'a self) -> IndirectLtAny<'a, 'static, Ref> { - // // let x: &MaybeSized::T<'a, 'static, T> = &*self.0; - // // IndirectLtAny::<'a, 'static, Ref>::new::<T>(x) - // todo!() - // } - // - // fn indirect_mut<'a>(&'a mut self) -> IndirectLtAny<'a, 'static, Mut> { - // let x: &'a mut MaybeSized::T<'static, 'static, T> = &mut *self.0; - // IndirectLtAny::<'static, 'static, Mut>::new::<T>(x) - // } - // } - - impl<Seed: Send, Value: Send, Error: Send> BuilderTypes for MockBuilder<Seed, Value, Error> { - type Seed = Seed; - - type Error = Error; - - type Value = Value; - } - - impl<Seed, Value, Error> MockBuilder<Seed, Value, Error> { - pub fn lock_context<'a>() -> MutexGuard<'a, ()> { - static LOCK: Mutex<()> = Mutex::new(()); - LOCK.lock().unwrap_or_else(PoisonError::into_inner) - } - } - - impl<Seed: Send, Value: Send, Error: Send, E: Effect<'static>> Builder<'static, E> - for MockBuilder<Seed, Value, Error> - { - fn from_seed<'a>(seed: Self::Seed) -> Future<'a, 'static, Self, E> - where - Self: 'a, - { - E::ready(Self::private_from_seed(seed)) - } - - fn build<'a>(self) -> Future<'a, 'static, Result<Self::Value, Self::Error>, E> - where - Self: 'a, - { - E::ready(self.private_build()) - } - - fn as_visitor(&mut self) -> Visitor<'_, 'static> { - self - } - } - - impl<Seed, Value, Error> AnyTrait<'static> for MockBuilder<Seed, Value, Error> { - fn upcast_to_id<'a>( - &'a self, - id: LtTypeId<'static>, - ) -> Option<IndirectLtAny<'a, 'static, Ref>> - where - 'static: 'a, - { - self.lookup().get(&id).map(|x| x.indirect()) - } - - fn upcast_to_id_mut<'a: 'b, 'b>( - &'a mut self, - id: LtTypeId<'static>, - ) -> Option<IndirectLtAny<'b, 'static, Mut>> - where - 'static: 'a, - { - self.lookup_mut().get_mut(&id).map(|x| x.indirect_mut()) - } - } - - mock! { - ValueVisitor { - fn private_visit(&mut self, value: OwnedStatic<i32>) -> Flow; - } - } - - impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, i32>, E> for MockValueVisitor { - fn visit<'a>(&'a mut self, value: OwnedStatic<i32>) -> Future<'a, 'ctx, Flow, E> { - E::ready(self.private_visit(value)) - } - } - - #[test] - fn demo2() { - let _lock = MockBuilder::<(), (), ()>::lock_context(); - let ctx = MockBuilder::<(), (), ()>::private_from_seed_context(); - - // Expect one mock builder to be made from the from_seed static method; - ctx.expect().once().returning(|_| { - let mut mock = MockBuilder::new(); - - // Expect that we will lookup one trait on the visitor. - mock.expect_lookup_mut().once().returning(|| { - let mut map = HashMap::<LtTypeId<'static>, Box<dyn MockIndirect>>::new(); - - let mut mock = MockValueVisitor::new(); - - // Expect that we will pass the value 42 to the visitor. - mock.expect_private_visit() - .once() - .with(eq(OwnedStatic(42))) - .return_const(Flow::Done); - - // This mock will be for the value protocol. - map.insert( - LtTypeId::of::<DynValue<'static, DynOwnedStatic<'static, i32>, Blocking>>(), - Box::new(mock), - ); - - map - }); - - mock.expect_private_build().once().returning(|| Ok(())); - - mock - }); - - // Create a mock builder using the static from_seed method. - let mut builder = Spin::block_on(<MockBuilder as Builder<Blocking>>::from_seed(())); - - { - // Get the mock builder as a visitor. - let visitor = <MockBuilder as Builder<Blocking>>::as_visitor(&mut builder); - - // Upcast the mock visitor into a trait object for the value protocol. - // This resolves to one of the values in the hashmap above and not the builder itself. - let x = visitor - .upcast_to_id_mut(LtTypeId::of::< - DynValue<'_, DynOwnedStatic<'_, i32>, Blocking>, - >()) - .unwrap(); - - // Finish the upcast by downcasting to the trait object. - let Ok(x) = x.downcast::<DynValue<'_, DynOwnedStatic<'_, i32>, Blocking>>() else { - panic!(); - }; - - // Use the visit method on the mock visitor. - assert_eq!(Spin::block_on(x.visit(OwnedStatic(42))), Flow::Done); - } - - // Finish building the mock builder. - assert_eq!( - Spin::block_on(<MockBuilder as Builder<Blocking>>::build(builder)).unwrap(), - () - ); - } -} diff --git a/src/build/builders/core/bool.rs b/src/build/builders/core/bool.rs index 346441e..208e88b 100644 --- a/src/build/builders/core/bool.rs +++ b/src/build/builders/core/bool.rs @@ -61,11 +61,11 @@ impl<'ctx, E: Effect<'ctx>> crate::Builder<'ctx, E> for Builder<E> { any_trait! { impl['ctx, E] Builder<E> = [ - DynValue<'ctx, DynOwnedStatic<'ctx, bool>, E>, + DynValue<'ctx, DynOwnedStatic<bool>, E>, ] where E: Effect<'ctx> } -impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, bool>, E> for Builder<E> { +impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<bool>, E> for Builder<E> { #[inline] fn visit<'a>(&'a mut self, OwnedStatic(value): OwnedStatic<bool>) -> Future<'a, 'ctx, Flow, E> { self.0 = Some(value); diff --git a/src/build/builders/debug.rs b/src/build/builders/debug.rs index 2914423..a97662e 100644 --- a/src/build/builders/debug.rs +++ b/src/build/builders/debug.rs @@ -28,10 +28,10 @@ any_trait! { DynRequestHint<'ctx, E>, // DynRecoverable<'a, 'ctx, E>, DynTag<'ctx, TagDyn, E>, - DynValue<'ctx, DynOwnedStatic<'ctx, &'static str>, E>, - DynValue<'ctx, DynOwnedStatic<'ctx, TypeId>, E>, - DynValue<'ctx, DynOwnedStatic<'ctx, usize>, E>, - DynValue<'ctx, DynOwnedStatic<'ctx, bool>, E>, + DynValue<'ctx, DynOwnedStatic<&'static str>, E>, + DynValue<'ctx, DynOwnedStatic<TypeId>, E>, + DynValue<'ctx, DynOwnedStatic<usize>, E>, + DynValue<'ctx, DynOwnedStatic<bool>, E>, // DynValue<'a, 'ctx, OwnedStatic<&'static [&'static str]>, E>, DynSequence<'ctx, E>, ] else fallback where E: Effect<'ctx> @@ -112,7 +112,7 @@ impl<'ctx, E: Effect<'ctx>> Tag<'ctx, TagDyn, E> for Visitor<E> { } } -impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, &'static str>, E> for Visitor<E> { +impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<&'static str>, E> for Visitor<E> { fn visit<'a>( &'a mut self, OwnedStatic(value): OwnedStatic<&'static str>, @@ -123,7 +123,7 @@ impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, &'static str>, E> f } } -impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, usize>, E> for Visitor<E> { +impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<usize>, E> for Visitor<E> { fn visit<'a>( &'a mut self, OwnedStatic(value): OwnedStatic<usize>, @@ -134,7 +134,7 @@ impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, usize>, E> for Visi } } -impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, bool>, E> for Visitor<E> { +impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<bool>, E> for Visitor<E> { fn visit<'a>(&'a mut self, OwnedStatic(value): OwnedStatic<bool>) -> Future<'a, 'ctx, Flow, E> { self.tab(); println!("{}", value); @@ -142,9 +142,7 @@ impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, bool>, E> for Visit } } -impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, &'static [&'static str]>, E> - for Visitor<E> -{ +impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<&'static [&'static str]>, E> for Visitor<E> { fn visit<'a>( &'a mut self, OwnedStatic(value): OwnedStatic<&'static [&'static str]>, @@ -155,7 +153,7 @@ impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, &'static [&'static } } -impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<'ctx, TypeId>, E> for Visitor<E> { +impl<'ctx, E: Effect<'ctx>> Value<'ctx, DynOwnedStatic<TypeId>, E> for Visitor<E> { fn visit<'a>( &'a mut self, OwnedStatic(value): OwnedStatic<TypeId>, diff --git a/src/effect.rs b/src/effect.rs index 3ce7a07..b52f94a 100644 --- a/src/effect.rs +++ b/src/effect.rs @@ -18,7 +18,7 @@ higher_ranked_trait! { } /// Trait for effects. -pub trait Effect<'ctx>: 'static { +pub trait Effect<'ctx>: Send + 'static { type Future<T: Send>: SendFuture::Trait<'ctx, T>; fn wrap<'a, F>(future: F) -> SendFuture::T<'a, 'ctx, Self::Future<F::Output>, F::Output> @@ -16,6 +16,9 @@ pub mod symbol; mod transform; mod walk; +#[cfg(any(test, feature = "mock"))] +pub mod mock; + // pub use build::Build; // pub use build::Builder; // diff --git a/src/mock.rs b/src/mock.rs new file mode 100644 index 0000000..89a7a64 --- /dev/null +++ b/src/mock.rs @@ -0,0 +1,81 @@ +use core::{ + any::{Any, TypeId}, + ops::Deref, +}; +use std::{ + collections::HashMap, + sync::{Mutex, MutexGuard, OnceLock, RwLock}, +}; + +pub mod builder; +pub mod protocol; + +pub struct StaticTypeMap { + map: OnceLock<RwLock<HashMap<TypeId, &'static (dyn Any + Send + Sync)>>>, +} + +impl StaticTypeMap { + pub const fn new() -> Self { + Self { + map: OnceLock::new(), + } + } + + pub fn get_or_init<T: Send + Sync + 'static, F: FnOnce() -> T>(&self, f: F) -> &'static T { + let map_init = || RwLock::new(HashMap::new()); + + let map = self.map.get_or_init(map_init).read().unwrap(); + + if let Some(once) = map.get(&TypeId::of::<T>()) { + return once.downcast_ref::<T>().unwrap(); + } + + drop(map); + + let mut map = self.map.get_or_init(map_init).write().unwrap(); + let once = &*Box::leak(Box::new(f())); + map.insert(TypeId::of::<T>(), once); + + once + } +} + +pub struct ContextLock<T> { + lock: Mutex<T>, + checkpoint: fn(&T), +} + +impl<T> ContextLock<T> { + pub const fn new(context: T, checkpoint: fn(&T)) -> Self { + Self { + lock: Mutex::new(context), + checkpoint, + } + } + + pub fn lock(&self) -> ContextGuard<'_, T> { + ContextGuard { + lock: self, + guard: self.lock.lock().unwrap(), + } + } +} + +pub struct ContextGuard<'a, T> { + lock: &'a ContextLock<T>, + guard: MutexGuard<'a, T>, +} + +impl<'a, T> Drop for ContextGuard<'a, T> { + fn drop(&mut self) { + (self.lock.checkpoint)(&*self.guard) + } +} + +impl<'a, T> Deref for ContextGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} diff --git a/src/mock/builder.rs b/src/mock/builder.rs new file mode 100644 index 0000000..31c1623 --- /dev/null +++ b/src/mock/builder.rs @@ -0,0 +1,99 @@ +use mockall::mock; + +use crate::{ + any::{AnyTrait, IndirectLtAny, LtTypeId, Mut, Ref}, + effect::{Effect, Future}, + mock::{ContextLock, StaticTypeMap}, + protocol::Visitor, + Builder, BuilderTypes, +}; + +use self::__mock_MockBuilder::__from_seed::Context; + +use super::ContextGuard; + +use crate::any::ForAnyTypeId; + +mock! { + pub Builder<Seed: 'static, Value: 'static, Error: 'static> { + pub fn from_seed(seed: Seed) -> Self; + pub fn build(self) -> Result<Value, Error>; + + pub fn traits(&self, id: ForAnyTypeId) -> &Option<Box<dyn for<'ctx> AnyTrait<'ctx> + Send>>; + pub fn traits_mut(&mut self, id: ForAnyTypeId) -> &mut Option<Box<dyn for<'ctx> AnyTrait<'ctx> + Send>>; + } +} + +impl<Seed: Send, Value: Send, Error: Send> BuilderTypes for MockBuilder<Seed, Value, Error> { + type Seed = Seed; + + type Error = Error; + + type Value = Value; +} + +impl<Seed: 'static, Value: 'static, Error: 'static> MockBuilder<Seed, Value, Error> { + pub fn lock_from_seed_context<'a>() -> ContextGuard<'a, Context<Seed, Value, Error>> { + static LOCKS: StaticTypeMap = StaticTypeMap::new(); + + LOCKS + .get_or_init(|| { + ContextLock::new(MockBuilder::from_seed_context(), |context| { + context.checkpoint() + }) + }) + .lock() + } +} + +impl<'ctx, Seed: Send, Value: Send, Error: Send, E: Effect<'ctx>> Builder<'ctx, E> + for MockBuilder<Seed, Value, Error> +{ + #[track_caller] + fn from_seed<'a>(seed: Self::Seed) -> Future<'a, 'ctx, Self, E> + where + Self: 'a, + { + E::ready(Self::from_seed(seed)) + } + + #[track_caller] + fn build<'a>(self) -> Future<'a, 'ctx, Result<Self::Value, Self::Error>, E> + where + Self: 'a, + { + E::ready(self.build()) + } + + #[track_caller] + fn as_visitor(&mut self) -> Visitor<'_, 'ctx> { + self + } +} + +impl<'ctx, Seed, Value, Error> AnyTrait<'ctx> for MockBuilder<Seed, Value, Error> { + #[track_caller] + fn upcast_to_id<'a>(&'a self, id: LtTypeId<'ctx>) -> Option<IndirectLtAny<'a, 'ctx, Ref>> + where + 'ctx: 'a, + { + // Find the first trait handler that wants to upcast. + self.traits(id.as_type_id()) + .as_ref() + .and_then(|t| t.upcast_to_id(id)) + } + + #[track_caller] + fn upcast_to_id_mut<'a: 'b, 'b>( + &'a mut self, + id: LtTypeId<'ctx>, + ) -> Option<IndirectLtAny<'b, 'ctx, Mut>> + where + 'ctx: 'a, + { + // Find the first trait handler that wants to upcast. + self.traits_mut(id.as_type_id()) + .as_mut() + .and_then(|t| t.upcast_to_id_mut(id)) + } +} diff --git a/src/mock/protocol.rs b/src/mock/protocol.rs new file mode 100644 index 0000000..422fa94 --- /dev/null +++ b/src/mock/protocol.rs @@ -0,0 +1,2 @@ +pub mod tag; +pub mod value; diff --git a/src/mock/protocol/tag.rs b/src/mock/protocol/tag.rs new file mode 100644 index 0000000..e1c9551 --- /dev/null +++ b/src/mock/protocol/tag.rs @@ -0,0 +1,33 @@ +use mockall::mock; + +use crate::{ + any_trait, + effect::{Effect, Future}, + protocol::visitor::tag::{DynTag, Tag, TagKind}, + DynWalker, +}; + +mock! { + pub TagVisitor<K: TagKind, E> { + pub fn visit<'a, 'ctx>(&'a mut self, kind: K, walker: DynWalker<'a, 'ctx, E>) -> K::Flow; + } +} + +any_trait! { + impl['ctx, K, E] MockTagVisitor<K, E> = [ + DynTag<'ctx, K, E>, + ] where + K: TagKind, + E: Effect<'ctx>, +} + +impl<'ctx, K: TagKind, E: Effect<'ctx>> Tag<'ctx, K, E> for MockTagVisitor<K, E> { + #[track_caller] + fn visit<'a>( + &'a mut self, + kind: K, + walker: DynWalker<'a, 'ctx, E>, + ) -> Future<'a, 'ctx, <K as TagKind>::Flow, E> { + E::ready(self.visit(kind, walker)) + } +} diff --git a/src/mock/protocol/value.rs b/src/mock/protocol/value.rs new file mode 100644 index 0000000..99ba045 --- /dev/null +++ b/src/mock/protocol/value.rs @@ -0,0 +1,38 @@ +use mockall::mock; + +use crate::{ + any::{MaybeSized, TypeName}, + any_trait, + effect::{Effect, Future}, + protocol::visitor::value::{DynValue, Value}, + Flow, +}; + +mock! { + pub ValueVisitor<T: for<'ctx> MaybeSized::Trait<'ctx>, E> + where + for<'a, 'ctx> MaybeSized::T<'a, 'ctx, T>: Sized + { + pub fn visit<'a, 'ctx>(&'a mut self, value: MaybeSized::T<'a, 'ctx, T>) -> Flow; + } +} + +any_trait! { + impl['ctx, T, E] MockValueVisitor<T, E> = [ + DynValue<'ctx, T, E> + ] where + T: for<'lt> TypeName::Member<'lt> + 'ctx, + for<'a, 'lt> MaybeSized::T<'a, 'lt, T>: Sized, + E: Effect<'ctx>, +} + +impl<'ctx, T: for<'lt> MaybeSized::Trait<'lt>, E: Effect<'ctx>> Value<'ctx, T, E> + for MockValueVisitor<T, E> +where + for<'a, 'lt> MaybeSized::T<'a, 'lt, T>: Sized, +{ + #[track_caller] + fn visit<'a>(&'a mut self, value: MaybeSized::T<'a, 'ctx, T>) -> Future<'a, 'ctx, Flow, E> { + E::ready(self.visit(value)) + } +} diff --git a/src/protocol/visitor.rs b/src/protocol/visitor.rs index de31813..af82045 100644 --- a/src/protocol/visitor.rs +++ b/src/protocol/visitor.rs @@ -6,6 +6,7 @@ pub mod sequence; pub mod tag; pub mod value; +#[derive(Debug)] pub enum Status<S = ()> { /// The protocol was not used. Skipped(S), diff --git a/src/protocol/visitor/value.rs b/src/protocol/visitor/value.rs index ca5d046..08f06d4 100644 --- a/src/protocol/visitor/value.rs +++ b/src/protocol/visitor/value.rs @@ -113,7 +113,7 @@ mod test { fn visit() { struct Visitor<E>(Option<i32>, PhantomData<fn() -> E>); - impl<'ctx, E> Value<'ctx, DynOwnedStatic<'ctx, i32>, E> for Visitor<E> + impl<'ctx, E> Value<'ctx, DynOwnedStatic<i32>, E> for Visitor<E> where E: Effect<'ctx>, { @@ -145,7 +145,7 @@ mod test { any_trait! { impl['ctx, E] Visitor<E> = [ - DynValue<'ctx, DynOwnedStatic<'ctx, i32>, E>, + DynValue<'ctx, DynOwnedStatic<i32>, E>, DynValue<'ctx, DynBorrowedStatic<'ctx, i32>, E>, ] where E: Effect<'ctx>, } @@ -154,7 +154,7 @@ mod test { let object: &mut (dyn AnyTrait<'_> + Send) = &mut v; Spin::block_on( object - .upcast_mut::<DynValue<'_, DynOwnedStatic<'_, i32>, Blocking>>() + .upcast_mut::<DynValue<'_, DynOwnedStatic<i32>, Blocking>>() .unwrap() .visit(OwnedStatic(42)), ); diff --git a/src/walk.rs b/src/walk.rs index c99251d..c991377 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -115,7 +115,7 @@ impl<'ctx, W: Walker<'ctx, E>, E: Effect<'ctx>> WalkerObjSafe<'ctx, E> for DynWa match walker.walk(visitor).await { Ok(value) => { self.state = DynWalkerState::Done(value); - Flow::Continue + Flow::Done } Err(err) => { self.state = DynWalkerState::Err(err); @@ -127,7 +127,7 @@ impl<'ctx, W: Walker<'ctx, E>, E: Effect<'ctx>> WalkerObjSafe<'ctx, E> for DynWa } } else { // Can't do anything if the walker has already been walked. - Flow::Continue + Flow::Done } }) } diff --git a/src/walk/walkers/core/noop.rs b/src/walk/walkers/core/noop.rs index 03b4b7c..ec7f8ae 100644 --- a/src/walk/walkers/core/noop.rs +++ b/src/walk/walkers/core/noop.rs @@ -9,6 +9,7 @@ use crate::{ /// /// This walker is useful for tags that don't need a value. #[non_exhaustive] +#[derive(Debug)] pub struct NoopWalker; impl NoopWalker { diff --git a/src/walk/walkers/core/struct.rs b/src/walk/walkers/core/struct.rs index 5ffb224..cbcb5c0 100644 --- a/src/walk/walkers/core/struct.rs +++ b/src/walk/walkers/core/struct.rs @@ -659,3 +659,155 @@ where }) } } + +#[cfg(test)] +mod test { + use mockall::{predicate::eq, Sequence}; + + use crate::{ + any::{ + static_wrapper::{DynOwnedStatic, OwnedStatic}, + LtTypeId, + }, + effect::{BlockOn as _, Blocking, Spin}, + mock::{ + builder::MockBuilder, + protocol::{tag::MockTagVisitor, value::MockValueVisitor}, + }, + symbol::Symbol, + Builder, DefaultMode, Walker, + }; + + use super::*; + + struct Demo { + a: bool, + b: bool, + } + + impl<'ctx, M> StructTypeInfo<'ctx, M> for Demo { + const NAME: &'static str = "Demo"; + + const FIELDS: &'static [&'static str] = &["a", "b"]; + + type FieldError = (); + + type T = Demo; + + fn walk_field<'a, E: Effect<'ctx>>( + index: usize, + value: &'ctx Self::T, + visitor: Visitor<'a, 'ctx>, + ) -> Future<'a, 'ctx, Result<Flow, Self::FieldError>, E> { + E::wrap(async move { + match index { + 0 => { + let walker = ValueWalker::<bool>::new(value.a); + Walker::<E>::walk(walker, visitor).await.unwrap(); + Ok(Flow::Continue) + } + 1 => { + let walker = ValueWalker::<bool>::new(value.b); + Walker::<E>::walk(walker, visitor).await.unwrap(); + Ok(Flow::Continue) + } + _ => Ok(Flow::Done), + } + }) + } + } + + #[test] + fn demo2() { + let mut builder = MockBuilder::<(), (), ()>::new(); + + let mut seq = Sequence::new(); + + builder + .expect_traits_mut() + .times(4) + .in_sequence(&mut seq) + .return_var(None); + + builder + .expect_traits_mut() + .once() + .with(eq(LtTypeId::of::< + DynTag<'static, TagConst<{ TAG_STRUCT.to_int() }>, Blocking>, + >() + .as_type_id())) + .in_sequence(&mut seq) + .return_var(Some(Box::new({ + let mut mock = MockTagVisitor::<TagConst<{ TAG_STRUCT.to_int() }>, Blocking>::new(); + + mock.expect_visit().once().returning(|_, walker| { + let mut builder = MockBuilder::<(), (), ()>::new(); + assert_eq!( + Spin::block_on(walker.walk(Builder::<Blocking>::as_visitor(&mut builder))), + Flow::Done + ); + + Flow::Continue + }); + + mock + }))); + + builder + .expect_traits_mut() + .once() + .with(eq(LtTypeId::of::< + DynTag<'static, TagConst<{ TAG_TYPE_NAME.to_int() }>, Blocking>, + >() + .as_type_id())) + .in_sequence(&mut seq) + .return_var(Some(Box::new({ + let mut mock = + MockTagVisitor::<TagConst<{ TAG_TYPE_NAME.to_int() }>, Blocking>::new(); + + mock.expect_visit().return_once(|_, walker| { + let mut builder = MockBuilder::<(), (), ()>::new(); + + builder + .expect_traits_mut() + .once() + .with(eq(LtTypeId::of::< + DynValue<'static, DynOwnedStatic<&'static str>, Blocking>, + >() + .as_type_id())) + .return_var(Some(Box::new({ + let mut mock = + MockValueVisitor::<DynOwnedStatic<&'static str>, Blocking>::new(); + + mock.expect_visit() + .once() + .with(eq(OwnedStatic("Demo"))) + .return_const(Flow::Done); + + mock + }))); + + assert_eq!( + Spin::block_on(walker.walk(Builder::<Blocking>::as_visitor(&mut builder))), + Flow::Done + ); + + Flow::Continue + }); + + mock + }))); + + builder + .expect_traits_mut() + .times(3) + .in_sequence(&mut seq) + .return_var(None); + + let value = Demo { a: true, b: false }; + + let walker = StructWalker::<Demo, Demo, DefaultMode, Blocking>::new(&value); + + Spin::block_on(walker.walk(Builder::<Blocking>::as_visitor(&mut builder))).unwrap(); + } +} diff --git a/src/walk/walkers/core/value.rs b/src/walk/walkers/core/value.rs index 4f75750..64586ca 100644 --- a/src/walk/walkers/core/value.rs +++ b/src/walk/walkers/core/value.rs @@ -10,6 +10,7 @@ use crate::{ /// /// Primitive types use this walker as their main walker. /// This walker doesn't consider it an error if the visitor doesn't have the protocol. +#[derive(Debug)] pub struct ValueWalker<T>(T); impl<T> ValueWalker<T> { |