use core::fmt::{Debug, Display};
use crate::{
any::{OwnedStatic, TempBorrowedStatic, TempBorrowedStaticHrt, TypeName},
any_trait,
effect::{
Effect, EffectExt as _, Effective, EffectiveExt as _, ErasedEffective, ReadyExt as _, Ss,
},
hkt::Marker,
protocol::{
visitor::{
tags, DynSequenceScope, EffectiveVisitExt as _, RequestHint, RequestHintProto,
Sequence, SequenceHint, SequenceProto, Tag, TagConst, TagHint, TagProto, Value,
ValueProto, VisitResult,
},
walker::hint::hint_protocol,
DynVisitor, DynWalker,
},
tri, Builder, BuilderTypes, DynWalkerObjSafe, Flow,
};
use super::NoopVisitor;
/// A builder for a struct.
pub struct StructBuilder<'ctx, Info, Mode, E: Effect>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
inner: Inner<'ctx, Info, Mode, E>,
}
enum Inner<'ctx, Info, Mode, E: Effect>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
Temp,
Seed(Info::Seed),
Builders {
/// The builders for all the struct's fields.
builders: Info::Builders,
/// The kind of struct the builder is expecting.
kind: StructKind,
},
Value(Info::T),
}
/// Structs are either tuple-like or map-like.
enum StructKind {
/// A tuple-like struct uses the order of the fields.
Tuple,
/// A map-like struct uses field names.
Map,
}
/// Information about how a struct type interacts with the [`StructBuilder`].
///
/// This trait is *not* implemented by the struct type itself to allow many
/// of these to exist per struct.
///
/// The `Mode` generic allows implementations to change depending on the mode the user gives.
/// It is not used by the trait directly.
pub trait StructTypeInfo<'ctx, Mode: 'ctx, E: Effect>: 'static {
/// A struct of builders for each field.
type Builders: Ss;
/// The seed value needed to make the builders.
type Seed: Ss;
/// An enum of the fields.
///
/// These markers act in place of the field names.
type FieldMarker: Ss + Copy + Display;
/// The error type that can be generated while building the struct.
type Error: Ss + Debug + Display;
/// The kind of type for enabling the direct value protocol.
type ValueT: TypeName::MemberType;
/// The struct type this info is for.
type T: Ss;
const FIELD_COUNT: usize;
/// Create a set of builders from a seed value.
fn new_builders<'a>(seed: Self::Seed) -> ErasedEffective<'a, Self::Builders, E>;
/// Finish building the struct value.
fn from_builders<'a>(
builders: Self::Builders,
) -> ErasedEffective<'a, Result<Self::T, Self::Error>, E>;
/// Get the visitor for a field.
///
/// This is how [`StructBuilder`] picks a field builder to use.
fn as_visitor<'a>(
marker: Self::FieldMarker,
builders: &'a mut Self::Builders,
) -> DynVisitor<'a, 'ctx>;
/// Get a field marker from the index of the field.
///
/// This should be the definition order as seen in the Rust source code.
fn marker_from_index(index: usize) -> Option<Self::FieldMarker>;
/// Get a field marker from a field name.
fn marker_from_name(name: &str) -> Option<Self::FieldMarker>;
/// Get the value from the value protocol.
fn from_value<'a>(value: TypeName::T<'a, 'ctx, Self::ValueT>) -> Self::T;
}
/// Error that [`StructBuilder`] returns.
pub struct StructError<'ctx, Info, M, E: Effect>
where
Info: StructTypeInfo<'ctx, M, E>,
{
/// Error from the struct info definition.
error: Info::Error,
}
impl<'ctx, Info, Mode, E: Effect> StructError<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
fn from_field_err(error: Info::Error) -> Self {
Self { error }
}
}
impl<'ctx, Info, Mode, E: Effect> Debug for StructError<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("StructError").field(&self.error).finish()
}
}
impl<'ctx, Info, Mode, E: Effect> Display for StructError<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.error, f)
}
}
impl<'ctx, Info, Mode, E: Effect> BuilderTypes for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
type Seed = Info::Seed;
type Error = StructError<'ctx, Info, Mode, E>;
type Value = Info::T;
}
impl<'ctx, Info, Mode: 'ctx, E: Effect> StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
fn make_builders<'e>(&'e mut self) -> ErasedEffective<'e, (), E>
where
'ctx: 'e,
{
match core::mem::replace(&mut self.inner, Inner::Temp) {
Inner::Seed(seed) => Info::new_builders(seed).map(|builders| {
self.inner = Inner::Builders {
builders,
kind: StructKind::Tuple,
};
}),
inner => {
self.inner = inner;
().ready()
}
}
}
}
impl<'ctx, Info, Mode: 'ctx, E: Effect> Builder<'ctx, E> for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
{
fn from_seed<'a>(seed: Self::Seed) -> ErasedEffective<'a, Self, E>
where
Self: 'a,
{
Self {
inner: Inner::Seed(seed),
}
.ready()
}
fn build<'a>(self) -> ErasedEffective<'a, Result<Self::Value, Self::Error>, E>
where
Self: 'a,
{
match self.inner {
Inner::Temp => unreachable!(),
Inner::Seed(seed) => {
// We may be able to make a value from just the seed.
Info::new_builders(seed)
.then(|builders| Info::from_builders(builders))
.map(|result| result.map_err(StructError::from_field_err))
}
Inner::Builders { builders, .. } => {
// Create the value from the builders.
Info::from_builders(builders)
.map(|result| result.map_err(StructError::from_field_err))
}
// Use the value as is.
Inner::Value(value) => Ok(value).ready(),
}
}
fn as_visitor(&mut self) -> DynVisitor<'_, 'ctx> {
DynVisitor(self)
}
}
any_trait! {
impl['ctx, Info, Mode, E] StructBuilder<'ctx, Info, Mode, E> = [
RequestHintProto<E>,
ValueProto<Info::ValueT, E>,
TagProto<tags::Struct, E>,
TagProto<tags::Map, E>,
SequenceProto<E>
] where
E: Effect,
Info: StructTypeInfo<'ctx, Mode, E>,
Mode: 'ctx,
}
impl<'ctx, Info, Mode: 'ctx, E> RequestHint<'ctx, E> for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
E: Effect,
{
#[inline(always)]
fn request_hint<'a>(
&'a mut self,
walker: DynWalker<'a, 'ctx>,
) -> ErasedEffective<'a, VisitResult<DynWalker<'a, 'ctx>>, E> {
E::as_ctx((self, walker), |(this, walker)| {
// Start with a hint to use the value protocol to directly transfer the
// struct value.
hint_protocol::<ValueProto<Info::ValueT, E>>(walker.cast(), *this, ()).cast()
})
.if_not_finished(|(this, walker)| {
// Next hint that the struct protocol should be used to switch into
// map-like if the walker supports it.
hint_protocol::<TagProto<tags::Struct, E>>(
walker.cast(),
*this,
TagHint { kind: TagConst },
)
.cast()
})
.if_skipped(|(this, walker)| {
// If the struct hint didn't work,
// then hint that the map protocol should be used to switch into
// map-like if the walker supports it.
hint_protocol::<TagProto<tags::Map, E>>(
walker.cast(),
*this,
TagHint { kind: TagConst },
)
.cast()
})
.if_not_finished(|(this, walker)| {
// Lastly hint to use a sequence to get the field values.
// We hint with the exact number of fields we are expecting.
hint_protocol::<SequenceProto<E>>(
walker.cast(),
*this,
SequenceHint {
len: (Info::FIELD_COUNT, Some(Info::FIELD_COUNT)),
},
)
.cast()
})
.map(|((_, walker), result)| match result {
VisitResult::Skipped(()) => VisitResult::Skipped(walker),
VisitResult::Control(flow) => VisitResult::Control(flow),
})
}
}
/// Allows for a walker to directly give the struct value.
///
/// This skips needing to go through each field individually.
impl<'ctx, Info, Mode, E> Value<'ctx, Info::ValueT, E> for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
E: Effect,
{
fn visit<'a>(
&'a mut self,
value: TypeName::T<'a, 'ctx, Info::ValueT>,
) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, Info::ValueT>>, E>
where
TypeName::T<'a, 'ctx, Info::ValueT>: Send + Sync + Sized,
'ctx: 'a,
{
// Get the value from what we got from the walker.
self.inner = Inner::Value(Info::from_value(value));
// Since we have the struct value we are done.
E::ready(Flow::Done.into())
}
}
/// Allows for the walker to use field names.
///
/// By default [`StructBuilder`] expects a tuple-like struct.
impl<'ctx, Info, Mode: 'ctx, E> Tag<'ctx, tags::Struct, E> for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
E: Effect,
{
fn visit<'this: 'e, 'walker: 'e, 'e>(
&'this mut self,
_kind: tags::Struct,
walker: DynWalkerObjSafe<'walker, 'ctx, E>,
) -> ErasedEffective<'e, VisitResult<DynWalkerObjSafe<'walker, 'ctx, E>>, E> {
// If this protocol is used then we need to create the builders.
E::as_ctx(self, |this| this.make_builders().cast()).then(|(this, _)| {
if let Inner::Builders { kind, .. } = &mut this.inner {
// This signals to go into map mode for the sequence.
*kind = StructKind::Map;
}
// Walk the walker so nothing complains.
NoopVisitor::walk_dyn(walker).cast()
})
}
}
/// Allows for the walker to use field names.
///
/// By default [`StructBuilder`] expects a tuple-like struct.
impl<'ctx, Info, Mode: 'ctx, E> Tag<'ctx, tags::Map, E> for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
E: Effect,
{
fn visit<'this: 'e, 'walker: 'e, 'e>(
&'this mut self,
_kind: tags::Map,
walker: DynWalkerObjSafe<'walker, 'ctx, E>,
) -> ErasedEffective<'e, VisitResult<DynWalkerObjSafe<'walker, 'ctx, E>>, E> {
// If this protocol is used then we need to create the builders.
E::as_ctx(self, |this| this.make_builders().cast()).then(|(this, _)| {
if let Inner::Builders { kind, .. } = &mut this.inner {
// This signals to go into map mode for the sequence.
*kind = StructKind::Map;
}
// Walk the walker so nothing complains.
NoopVisitor::walk_dyn(walker).cast()
})
}
}
/// Main protocol allowing a sequence of field values.
///
/// By default this will use each sequence element as a field value.
///
/// If the [`tags::Struct`] or [`tags::Map`] tags are used then this will expect
/// a sequence of key value pairs. Where the key is the field name.
impl<'ctx, Info, Mode: 'ctx, E> Sequence<'ctx, E> for StructBuilder<'ctx, Info, Mode, E>
where
Info: StructTypeInfo<'ctx, Mode, E>,
E: Effect,
{
fn visit<'a: 'c, 'b: 'c, 'c>(
&'a mut self,
scope: DynSequenceScope<'b, 'ctx, E>,
) -> ErasedEffective<'c, VisitResult<DynSequenceScope<'b, 'ctx, E>>, E>
where
'ctx: 'a + 'b + 'c,
{
// If this protocol is used then we need to create the builders.
E::as_ctx(self, |this| this.make_builders().cast())
.as_ctx(|(this, _)| {
match &mut this.inner {
// We should treat the sequence as just values.
Inner::Builders {
builders,
kind: StructKind::Tuple,
} => {
// Tuple-like is based on the index of the field.
let mut index = 0;
// Loop through all the fields getting a value for each one.
E::repeat_map((scope, builders), move |(scope, builders)| {
// Get the marker for the field at this index.
let marker = tri!(Info::marker_from_index(index));
// Move to the next field for the next iteration.
index += 1;
// Select the visitor for this field.
let visitor = Info::as_visitor(marker, builders);
// Visit the next item in the sequence.
scope.next(visitor).map(Flow::to_control_flow).cast()
})
}
// We should treat the sequence as key value pairs.
Inner::Builders {
builders,
kind: StructKind::Map,
} => {
// A visitor that knows how to use field names.
let visitor = FieldVisitor::<Info, Mode, E> {
builders,
marker: None,
_marker: Default::default(),
};
// Loop through all the elements in the sequence.
// Each key value pair will be mapped to a field.
E::repeat_map((visitor, scope), |(visitor, scope)| {
// Visit the next element of the sequence.
// When there are no more items in the sequence then the loop ends.
scope
.next(DynVisitor(visitor))
.map(Flow::to_control_flow)
.cast()
})
}
// If we don't have the builders ... we can't do anything.
// This would only happen if the walker gives the value directly
// then gives a sequence.
_ => Flow::Done.ready(),
}
.map(Into::into)
.cast()
})
.remove_ctx()
}
}
struct FieldVisitor<'a, 'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> {
builders: &'a mut I::Builders,
marker: Option<I::FieldMarker>,
_marker: Marker<E>,
}
any_trait! {
impl['ctx, 'a, I, M, E] FieldVisitor<'a, 'ctx, I, M, E> = [
TagProto<tags::Key, E>,
] else ref {
let (_this, _id);
None
} else mut {
let (this, id);
this.marker.and_then(|marker| {
I::as_visitor(marker, this.builders).0.upcast_to_id_mut(id)
})
} where
E: Effect,
I: StructTypeInfo<'ctx, M, E>,
}
impl<'d, 'ctx, I, M, E> Tag<'ctx, tags::Key, E> for FieldVisitor<'d, 'ctx, I, M, E>
where
E: Effect,
I: StructTypeInfo<'ctx, M, E>,
{
fn visit<'a: 'c, 'b: 'c, 'c>(
&'a mut self,
_key: tags::Key,
walker: DynWalkerObjSafe<'b, 'ctx, E>,
) -> ErasedEffective<'c, VisitResult<DynWalkerObjSafe<'b, 'ctx, E>>, E> {
let visitor = NameVisitor::<I, M, E> {
field_marker: None,
_marker: Default::default(),
};
E::ready((self, visitor, walker))
.as_ctx(|(_, visitor, walker)| walker.walk(DynVisitor(visitor)).cast())
.map(|((this, visitor, _), flow)| {
this.marker = visitor.field_marker;
flow.to_continue().into()
})
}
}
struct NameVisitor<'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> {
field_marker: Option<I::FieldMarker>,
_marker: Marker<E>,
}
any_trait! {
impl['ctx, I, M, E] NameVisitor<'ctx, I, M, E> = [
ValueProto<OwnedStatic<usize>, E>,
ValueProto<TempBorrowedStaticHrt<str>, E>,
ValueProto<OwnedStatic<&'static str>, E>,
] where
E: Effect,
I: StructTypeInfo<'ctx, M, E>,
}
impl<'ctx, I, M, E> Value<'ctx, OwnedStatic<usize>, E> for NameVisitor<'ctx, I, M, E>
where
E: Effect,
I: StructTypeInfo<'ctx, M, E>,
{
fn visit<'a>(
&'a mut self,
OwnedStatic(index): TypeName::T<'a, 'ctx, OwnedStatic<usize>>,
) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, OwnedStatic<usize>>>, E>
where
TypeName::T<'a, 'ctx, OwnedStatic<usize>>: Send + Sized,
'ctx: 'a,
{
self.field_marker = I::marker_from_index(index);
E::ready(VisitResult::Control(Flow::Done))
}
}
impl<'ctx, I, M, E> Value<'ctx, TempBorrowedStaticHrt<str>, E> for NameVisitor<'ctx, I, M, E>
where
E: Effect,
I: StructTypeInfo<'ctx, M, E>,
{
fn visit<'a>(
&'a mut self,
TempBorrowedStatic(name): TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>,
) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>>, E>
where
TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>: Send + Sized,
'ctx: 'a,
{
self.field_marker = I::marker_from_name(name);
E::ready(VisitResult::Control(Flow::Done))
}
}
impl<'ctx, I, M, E> Value<'ctx, OwnedStatic<&'static str>, E> for NameVisitor<'ctx, I, M, E>
where
E: Effect,
I: StructTypeInfo<'ctx, M, E>,
{
fn visit<'a>(
&'a mut self,
OwnedStatic(name): TypeName::T<'a, 'ctx, OwnedStatic<&'static str>>,
) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, OwnedStatic<&'static str>>>, E>
where
TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>: Send + Sized,
'ctx: 'a,
{
self.field_marker = I::marker_from_name(name);
E::ready(VisitResult::Control(Flow::Done))
}
}