added mocking and started testing the struct walker
Konnor Andrews 2024-04-12
parent 58347ea · commit 15cbd6f
-rw-r--r--Cargo.toml4
-rw-r--r--src/any.rs21
-rw-r--r--src/any/static_wrapper.rs28
-rw-r--r--src/build.rs216
-rw-r--r--src/build/builders/core/bool.rs4
-rw-r--r--src/build/builders/debug.rs20
-rw-r--r--src/effect.rs2
-rw-r--r--src/lib.rs3
-rw-r--r--src/mock.rs81
-rw-r--r--src/mock/builder.rs99
-rw-r--r--src/mock/protocol.rs2
-rw-r--r--src/mock/protocol/tag.rs33
-rw-r--r--src/mock/protocol/value.rs38
-rw-r--r--src/protocol/visitor.rs1
-rw-r--r--src/protocol/visitor/value.rs6
-rw-r--r--src/walk.rs4
-rw-r--r--src/walk/walkers/core/noop.rs1
-rw-r--r--src/walk/walkers/core/struct.rs152
-rw-r--r--src/walk/walkers/core/value.rs1
19 files changed, 471 insertions, 245 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 1bc4f96..18ca4fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/any.rs b/src/any.rs
index ed6c1ef..72a0c5f 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -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>
diff --git a/src/lib.rs b/src/lib.rs
index 8a7e2fa..043f6b7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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> {