Diffstat (limited to 'src/build/builders/core/struct.rs')
| -rw-r--r-- | src/build/builders/core/struct.rs | 488 |
1 files changed, 365 insertions, 123 deletions
diff --git a/src/build/builders/core/struct.rs b/src/build/builders/core/struct.rs index b8ca483..aa5215e 100644 --- a/src/build/builders/core/struct.rs +++ b/src/build/builders/core/struct.rs @@ -1,115 +1,222 @@ -use core::ops::ControlFlow; +use core::fmt::{Debug, Display}; use crate::{ - any::{OwnedStatic, TempBorrowedStatic, TempBorrowedStaticHrt, TypeName}, + any::{AnyTrait, OwnedStatic, StaticType, TempBorrowedStatic, TempBorrowedStaticHrt, TypeName}, any_trait, effect::{ - Effect, EffectExt as _, Effective, EffectiveExt as _, ErasedEffective, ReadyExt as _, + Effect, EffectExt as _, Effective, EffectiveExt as _, ErasedEffective, ReadyExt as _, Ss, }, hkt::Marker, protocol::{ visitor::{ - tags, DynSequenceScope, Sequence, SequenceProto, Tag, TagProto, Value, ValueProto, - VisitResult, + tags, DynSequenceScope, EffectiveVisitExt as _, RequestHint, RequestHintProto, + Sequence, SequenceHint, SequenceProto, Tag, TagConst, TagHint, TagProto, Value, + ValueProto, VisitResult, }, - DynVisitor, + walker::{self, hint::hint_protocol}, + DynVisitor, DynWalker, }, tri, Builder, BuilderTypes, DynWalkerObjSafe, Flow, }; use super::NoopVisitor; -enum StructMode { - /// A tuple-like struct uses the order of the sequence. +/// A builder for a struct. +pub struct StructBuilder<'ctx, Info, Mode, E: Effect> +where + Info: StructTypeInfo<'ctx, Mode>, +{ + inner: Inner<'ctx, Info, Mode, E>, +} + +enum Inner<'ctx, Info, Mode, E: Effect> +where + Info: StructTypeInfo<'ctx, Mode>, +{ + Temp, + Seed(Info::Seed), + Builders { + /// The builders for all the struct's fields. + builders: Info::Builders<E>, + + /// 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, } -pub struct StructBuilder<'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> { - builders: I::Builders, - mode: StructMode, - _generics: Marker<E>, -} +/// 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>: 'static { + /// A struct of builders for each field. + type Builders<E: Effect>: Ss; -pub trait StructTypeInfo<'ctx, M, E: Effect>: 'static { - type Builders: Send + Sync; + /// The seed value needed to make the builders. + type Seed: Ss; - type Seed: Send + Sync; + /// An enum of the fields. + /// + /// These markers act in place of the field names. + type FieldMarker: Ss + Copy + Display; - type FieldMarker: Send + Sync + Copy + core::fmt::Debug; + /// The error type that can be generated while building the struct. + type Error: Ss + Debug + Display; - type Error: Send + Sync + core::fmt::Debug; + /// The kind of type for enabling the direct value protocol. + type ValueT: TypeName::MemberType; - type T: Send + Sync; + /// The struct type this info is for. + type T: Ss; - fn new_builders<'a>(seed: Self::Seed) -> ErasedEffective<'a, Self::Builders, E>; + const FIELD_COUNT: usize; - fn from_builders<'a>( - builders: Self::Builders, + /// Create a set of builders from a seed value. + fn new_builders<'a, E: Effect>(seed: Self::Seed) -> ErasedEffective<'a, Self::Builders<E>, E>; + + /// Finish building the struct value. + fn from_builders<'a, E: Effect>( + builders: Self::Builders<E>, ) -> ErasedEffective<'a, Result<Self::T, Self::Error>, E>; - fn as_visitor<'a>( + /// Get the visitor for a field. + /// + /// This is how [`StructBuilder`] picks a field builder to use. + fn as_visitor<'a, E: Effect>( marker: Self::FieldMarker, - builders: &'a mut Self::Builders, + builders: &'a mut Self::Builders<E>, ) -> 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; } -pub struct StructError<'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> { - error: I::Error, +/// Error that [`StructBuilder`] returns. +pub struct StructError<'ctx, Info, M> +where + Info: StructTypeInfo<'ctx, M>, +{ + /// Error from the struct info definition. + error: Info::Error, +} + +impl<'ctx, Info, Mode> StructError<'ctx, Info, Mode> +where + Info: StructTypeInfo<'ctx, Mode>, +{ + fn from_field_err(error: Info::Error) -> Self { + Self { error } + } } -impl<'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> core::fmt::Debug - for StructError<'ctx, I, M, E> +impl<'ctx, Info, Mode> Debug for StructError<'ctx, Info, Mode> +where + Info: StructTypeInfo<'ctx, Mode>, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("StructError") - .field("error", &self.error) - .finish() + f.debug_tuple("StructError").field(&self.error).finish() } } -impl<'ctx, I, M, E: Effect> BuilderTypes for StructBuilder<'ctx, I, M, E> +impl<'ctx, Info, Mode> Display for StructError<'ctx, Info, Mode> where - I: StructTypeInfo<'ctx, M, E>, + Info: StructTypeInfo<'ctx, Mode>, { - type Seed = I::Seed; + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Display::fmt(&self.error, f) + } +} - type Error = StructError<'ctx, I, M, E>; +impl<'ctx, Info, Mode, E: Effect> BuilderTypes for StructBuilder<'ctx, Info, Mode, E> +where + Info: StructTypeInfo<'ctx, Mode>, +{ + type Seed = Info::Seed; + + type Error = StructError<'ctx, Info, Mode>; - type Value = I::T; + type Value = Info::T; } -impl<'ctx, I, M, E> Builder<'ctx, E> for StructBuilder<'ctx, I, M, E> +impl<'ctx, Info, Mode: 'ctx, E: Effect> StructBuilder<'ctx, Info, Mode, E> where - I: StructTypeInfo<'ctx, M, E>, - E: Effect, + Info: StructTypeInfo<'ctx, Mode>, +{ + fn into_builders<'e>(&mut self) -> ErasedEffective<'e, (), E> + where + 'ctx: 'e, + { + match core::mem::replace(&mut self.inner, Inner::Temp) { + Inner::Seed(seed) => Info::new_builders::<E>(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>, { - #[inline(always)] fn from_seed<'a>(seed: Self::Seed) -> ErasedEffective<'a, Self, E> where Self: 'a, { - I::new_builders(seed).map(|builders| { - Self { - builders, - // Start in tuple mode until a struct or map tag is visited. - mode: StructMode::Tuple, - _generics: Default::default(), - } - }) + Self { + inner: Inner::Seed(seed), + } + .ready() } fn build<'a>(self) -> ErasedEffective<'a, Result<Self::Value, Self::Error>, E> where Self: 'a, { - I::from_builders(self.builders) - .map(|builders| builders.map_err(|err| StructError { error: err })) + match self.inner { + Inner::Temp => unreachable!(), + Inner::Seed(seed) => { + // We may be able to make a value from just the seed. + Info::new_builders::<E>(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> { @@ -118,98 +225,237 @@ where } any_trait! { - impl['ctx, I, M, E] StructBuilder<'ctx, I, M, E> = [ + 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, - I: StructTypeInfo<'ctx, M, E>, + Info: StructTypeInfo<'ctx, Mode>, + Mode: 'ctx, } -impl<'ctx, I, M, E> Tag<'ctx, tags::Map, E> for StructBuilder<'ctx, I, M, E> +impl<'ctx, Info, Mode: 'ctx, E> RequestHint<'ctx, E> for StructBuilder<'ctx, Info, Mode, E> where - I: StructTypeInfo<'ctx, M, E>, + Info: StructTypeInfo<'ctx, Mode>, E: Effect, { #[inline(always)] - fn visit<'a: 'c, 'b: 'c, 'c>( + fn request_hint<'a>( &'a mut self, - _kind: tags::Map, - walker: DynWalkerObjSafe<'b, 'ctx, E>, - ) -> ErasedEffective<'c, VisitResult<DynWalkerObjSafe<'b, 'ctx, E>>, E> { - // This signals to go into map mode for the sequence. - self.mode = StructMode::Map; - - E::ready(NoopVisitor::new()).as_ctx_map(|noop| { - walker - .walk(DynVisitor(noop)) - .map(|x| x.to_continue().into()) - .cast() + 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), }) } } -// A struct is a sequence of field values. -impl<'ctx, I, M, E> Sequence<'ctx, E> for StructBuilder<'ctx, I, M, E> +/// 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 - I: StructTypeInfo<'ctx, M, E>, + Info: StructTypeInfo<'ctx, Mode>, E: Effect, { - #[inline(always)] - fn visit<'a: 'c, 'b: 'c, 'c>( + fn visit<'a>( &'a mut self, - scope: DynSequenceScope<'b, 'ctx, E>, - ) -> ErasedEffective<'c, VisitResult<DynSequenceScope<'b, 'ctx, E>>, E> + value: TypeName::T<'a, 'ctx, Info::ValueT>, + ) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, Info::ValueT>>, E> where - 'ctx: 'c, + TypeName::T<'a, 'ctx, Info::ValueT>: Send + Sized, + 'ctx: 'a, { - match self.mode { - StructMode::Tuple => { - // Tuple-like is based on the index of the field. - let mut index = 0; + // Get the value from what we got from the walker. + self.inner = Inner::Value(Info::from_value(value)); - // Loop through all the fields getting a value for each one. - E::repeat_map((self, scope), |(this, scope)| { - // Get the marker for the field at this index. - let marker = tri!(I::marker_from_index(index)); + // Since we have the struct value we are done. + E::ready(Flow::Done.into()) + } +} - // Move to the next field for the next iteration. - index += 1; +/// 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: 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.into_builders().cast()).then(|(this, _)| { + match &mut this.inner { + Inner::Builders { kind, .. } => { + // This signals to go into map mode for the sequence. + *kind = StructKind::Map; + } + _ => {} + } - // Select the visitor for this field. - let visitor = I::as_visitor(marker, &mut this.builders); + // Walk the walker so nothing complains. + NoopVisitor::walk_dyn(walker).cast() + }) + } +} - // Visit the next item in the sequence. - scope.next(visitor).map(Flow::to_control_flow).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: 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.into_builders().cast()).then(|(this, _)| { + match &mut this.inner { + Inner::Builders { kind, .. } => { + // This signals to go into map mode for the sequence. + *kind = StructKind::Map; + } + _ => {} } - StructMode::Map => { - // A visitor that knows how to use field names. - let visitor = FieldVisitor::<I, M, E> { - builders: &mut self.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)| { - E::as_ctx_map((scope, visitor), |(scope, visitor)| { - // Visit the next element of the sequence. - // When there are no more items in the sequence then the loop ends. - scope.next(DynVisitor(*visitor)).cast() - }) - .map(Flow::to_control_flow) - .cast() - }) - } - } - .map(Into::into) + // 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: 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.into_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), |(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, +struct FieldVisitor<'a, 'ctx, I: StructTypeInfo<'ctx, M>, M, E: Effect> { + builders: &'a mut I::Builders<E>, marker: Option<I::FieldMarker>, _marker: Marker<E>, } @@ -228,15 +474,14 @@ any_trait! { }) } where E: Effect, - I: StructTypeInfo<'ctx, M, E>, + I: StructTypeInfo<'ctx, M>, } 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>, + I: StructTypeInfo<'ctx, M>, { - #[inline(always)] fn visit<'a: 'c, 'b: 'c, 'c>( &'a mut self, _key: tags::Key, @@ -256,7 +501,7 @@ where } } -struct NameVisitor<'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> { +struct NameVisitor<'ctx, I: StructTypeInfo<'ctx, M>, M, E: Effect> { field_marker: Option<I::FieldMarker>, _marker: Marker<E>, } @@ -268,15 +513,14 @@ any_trait! { ValueProto<OwnedStatic<&'static str>, E>, ] where E: Effect, - I: StructTypeInfo<'ctx, M, E>, + I: StructTypeInfo<'ctx, M>, } impl<'ctx, I, M, E> Value<'ctx, OwnedStatic<usize>, E> for NameVisitor<'ctx, I, M, E> where E: Effect, - I: StructTypeInfo<'ctx, M, E>, + I: StructTypeInfo<'ctx, M>, { - #[inline(always)] fn visit<'a>( &'a mut self, OwnedStatic(index): TypeName::T<'a, 'ctx, OwnedStatic<usize>>, @@ -294,9 +538,8 @@ where impl<'ctx, I, M, E> Value<'ctx, TempBorrowedStaticHrt<str>, E> for NameVisitor<'ctx, I, M, E> where E: Effect, - I: StructTypeInfo<'ctx, M, E>, + I: StructTypeInfo<'ctx, M>, { - #[inline(always)] fn visit<'a>( &'a mut self, TempBorrowedStatic(name): TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>, @@ -314,9 +557,8 @@ where 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>, + I: StructTypeInfo<'ctx, M>, { - #[inline(always)] fn visit<'a>( &'a mut self, OwnedStatic(name): TypeName::T<'a, 'ctx, OwnedStatic<&'static str>>, |