use effectful::{
bound::{Dynamic, ForceDynamic},
effective::{Effective, Canonical},
environment::{Environment},
DynBind,
SendSync,
};
use mockall::predicate::eq;
use treaty::{
any::{BorrowedStatic, OwnedStatic},
protocol::{
visitor::{tags, TagConst, VisitResult},
AsVisitor, DynVisitor,
},
walk::walkers::core::r#struct::{StructTypeInfo, StructWalker},
DefaultMode, Flow, Walker,
};
use effectful::blocking::BlockingSpin;
use crate::common::{
builder::{EmptyError, MockBuilder},
protocol::{sequence::MockSequenceVisitor, tag::MockTagVisitor, value::MockValueVisitor},
};
mod common;
#[derive(SendSync)]
struct X {
a: bool,
b: i32,
}
struct Info;
// This gives the struct walker enough information to walk the X struct.
impl<'ctx, M, E: Environment> StructTypeInfo<'ctx, M, E> for Info
where
Dynamic<OwnedStatic<bool>>: DynBind<E>,
Dynamic<OwnedStatic<i32>>: DynBind<E>,
{
const NAME: &'static str = "X";
const FIELDS: &'static [&'static str] = &["a", "b"];
type FieldError = ();
type S = StaticType;
type T = X;
fn walk_field<'a>(
index: usize,
value: &'ctx Self::T,
mut visitor: DynVisitor<'a, 'ctx, E>,
) -> Canonical<'a, Result<Flow, Self::FieldError>, E> {
E::future(unsafe {
ForceDynamic::new(async move {
match index {
// A real impl would be expected to tag these values with the field name.
0 => {
// Basic visit for the field value.
let obj = visitor
.upcast_mut::<ValueProto<OwnedStatic<bool>, E>>()
.unwrap();
// Emit the field value.
assert_eq!(
obj.visit(OwnedStatic(value.a)).into_future().await,
Flow::Done.into()
);
// There are more fields.
Ok(Flow::Continue)
}
1 => {
// Basic visit for the field value.
let obj = visitor
.upcast_mut::<ValueProto<OwnedStatic<i32>, E>>()
.unwrap();
// Emit the field value.
assert_eq!(
obj.visit(OwnedStatic(value.b)).into_future().await,
Flow::Done.into()
);
// There are no more fields.
Ok(Flow::Done)
}
_ => Ok(Flow::Done),
}
})
})
.cast()
}
}
/// Tests that a struct walker in its most basic form just emits a sequence of field values.
///
/// This makes structs behave like tuples (lists) if the builder doesn't know about any of the
/// extra tags.
#[test]
fn sequence_of_field_values() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, BlockingSpin>::new(&value);
let mut builder = MockBuilder::<(), (), EmptyError, BlockingSpin>::new();
// Expect a visit on the sequence protocol for the struct fields.
builder
.expect_traits_mut()
.once()
.with(eq(TypeNameId::of::<SequenceProto<BlockingSpin>, BlockingSpin>()))
.returning(|_| {
let mut visitor = MockSequenceVisitor::<BlockingSpin>::new();
// Expect the sequence visitor to be used.
visitor.expect_visit().once().returning(|scope| {
// The struct should have exactly 2 fields.
assert_eq!(scope.size_hint().into_value(), (2, Some(2)));
// Get the first field value.
{
let mut visitor = MockBuilder::<(), (), EmptyError, BlockingSpin>::new();
// Expect a bool value for the field.
visitor
.expect_traits_mut()
.once()
.with(eq(TypeNameId::of::<
ValueProto<OwnedStatic<bool>, BlockingSpin>,
BlockingSpin,
>()))
.returning(|_| {
let mut visitor =
MockValueVisitor::<OwnedStatic<bool>, BlockingSpin>::new();
// Expect true.
visitor
.expect_visit()
.once()
.with(eq(OwnedStatic(true)))
.return_const(Flow::Done);
Some(Box::new(DynamicShim(visitor)))
});
assert_eq!(
scope.next(AsVisitor::as_visitor(&mut visitor)).into_value(),
Flow::Continue
);
}
// Get the second field value.
{
let mut visitor = MockBuilder::<(), (), EmptyError, BlockingSpin>::new();
// Expect a i32 value.
visitor
.expect_traits_mut()
.once()
.with(eq(TypeNameId::of::<
ValueProto<OwnedStatic<i32>, BlockingSpin>,
BlockingSpin,
>()))
.returning(|_| {
let mut visitor = MockValueVisitor::<OwnedStatic<i32>, BlockingSpin>::new();
// Expect a 42.
visitor
.expect_visit()
.once()
.with(eq(OwnedStatic(42)))
.return_const(Flow::Done);
Some(Box::new(DynamicShim(visitor)))
});
assert_eq!(
scope.next(AsVisitor::as_visitor(&mut visitor)).into_value(),
Flow::Done
);
}
// We are done with the sequence of fields.
VisitResult::Control(Flow::Done)
});
Some(Box::new(DynamicShim(visitor)))
});
// All other protocols are not used for this test.
builder.expect_traits_mut().return_var(None);
// Walk the struct.
assert_eq!(
walker
.walk(AsVisitor::as_visitor(&mut builder))
.into_value(),
Ok(())
);
}
#[test]
fn has_struct_tag() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, BlockingSpin>::new(&value);
let mut builder = MockBuilder::<(), (), EmptyError, BlockingSpin>::new();
let mut seq = mockall::Sequence::new();
// Skip the request hint, borrowed value, and type id.
builder
.expect_traits_mut()
.times(4)
.in_sequence(&mut seq)
.return_var(None);
// Expect that the tag for a struct is emmited.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.with(eq(TypeNameId::of::<
TagProto<tags::Struct, BlockingSpin>,
Blocking,
>()))
.returning(|_| {
let mut visitor = MockTagVisitor::<tags::Struct, Blocking>::new();
visitor.expect_visit().once().returning(|TagConst, walker| {
let mut visitor = MockBuilder::<(), (), (), Blocking>::new();
// Walk the noop walker so there isn't an error.
assert_eq!(
walker.walk(DynVisitor(&mut visitor)).into_value(),
Flow::Done
);
// We are done, the walker should now stop early.
VisitResult::Control(Flow::Done)
});
Some(Box::new(DynamicShim(visitor)))
});
// Walk the struct.
assert_eq!(
walker
.walk(AsVisitor::as_visitor(&mut builder))
.into_value(),
Ok(())
);
}
#[test]
fn has_map_backup_tag() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, Blocking>::new(&value);
let mut builder = MockBuilder::<(), (), EmptyError, Blocking>::new();
let mut seq = mockall::Sequence::new();
// Skip the request hint, borrowed value type id, and struct tag.
builder
.expect_traits_mut()
.times(6)
.in_sequence(&mut seq)
.return_var(None);
// Expect that the tag for a map is emmited.
//
// This only happens if the struct tag isn't supported by the visitor.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.with(eq(
TypeNameId::of::<TagProto<tags::Map, Blocking>, Blocking>(),
))
.returning(|_| {
let mut visitor = MockTagVisitor::<tags::Map, Blocking>::new();
visitor.expect_visit().once().returning(|TagConst, walker| {
let mut visitor = MockBuilder::<(), (), (), Blocking>::new();
// Walk the noop walker so there isn't an error.
assert_eq!(
walker.walk(DynVisitor(&mut visitor)).into_value(),
Flow::Done
);
// We are done, the walker should now stop early.
VisitResult::Control(Flow::Done)
});
Some(Box::new(DynamicShim(visitor)))
});
// Walk the struct.
assert_eq!(
walker
.walk(AsVisitor::as_visitor(&mut builder))
.into_value(),
Ok(())
);
}
#[test]
fn borrowed_value_directly() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, Blocking>::new(&value);
let mut builder = MockBuilder::<(), (), EmptyError, Blocking>::new();
let mut seq = mockall::Sequence::new();
// Skip the request hint.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.return_var(None);
// Get the struct value directly from the walker.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.with(eq(TypeNameId::of::<
ValueProto<BorrowedStaticHrt<X>, Blocking>,
Blocking,
>()))
.returning(|_| {
let mut visitor = MockValueVisitor::<BorrowedStaticHrt<X>, Blocking>::new();
// Expect the struct value.
visitor
.expect_visit()
.once()
.returning(|BorrowedStatic(value)| {
// Check the value to be the same.
assert!(value.a);
assert_eq!(value.b, 42);
// We are done, the walker should now stop early.
VisitResult::Control(Flow::Done)
});
Some(Box::new(DynamicShim(visitor)))
});
// Walk the struct.
assert_eq!(
walker
.walk(AsVisitor::as_visitor(&mut builder))
.into_value(),
Ok(())
);
}