From de2a516de427d97019e1ea693049be125bbb990e Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Sat, 22 May 2021 21:56:20 -0700 Subject: [PATCH 1/6] add Tuplable --- tests/tests/tuplable.rs | 2 + valuable/src/lib.rs | 3 + valuable/src/structable.rs | 2 +- valuable/src/tuplable.rs | 169 +++++++++++++++++++++++++++++++++++++ valuable/src/value.rs | 33 +++++++- 5 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 tests/tests/tuplable.rs create mode 100644 valuable/src/tuplable.rs diff --git a/tests/tests/tuplable.rs b/tests/tests/tuplable.rs new file mode 100644 index 0000000..af6c252 --- /dev/null +++ b/tests/tests/tuplable.rs @@ -0,0 +1,2 @@ +use valuable::*; + diff --git a/valuable/src/lib.rs b/valuable/src/lib.rs index b4b85f3..7e61b5a 100644 --- a/valuable/src/lib.rs +++ b/valuable/src/lib.rs @@ -121,6 +121,9 @@ pub use slice::Slice; mod structable; pub use structable::{StructDef, Structable}; +mod tuplable; +pub use tuplable::{Tuplable, TupleDef}; + mod valuable; pub use crate::valuable::Valuable; diff --git a/valuable/src/structable.rs b/valuable/src/structable.rs index 56bd20f..d9224f8 100644 --- a/valuable/src/structable.rs +++ b/valuable/src/structable.rs @@ -438,7 +438,7 @@ impl<'a> StructDef<'a> { matches!(self, StructDef::Static { .. }) } - /// Returns `true` if the struct is [dynamically defined](StructDef::Static). + /// Returns `true` if the struct is [dynamically defined](StructDef::Dynamic). /// /// # Examples /// diff --git a/valuable/src/tuplable.rs b/valuable/src/tuplable.rs new file mode 100644 index 0000000..a3c62d1 --- /dev/null +++ b/valuable/src/tuplable.rs @@ -0,0 +1,169 @@ +use crate::{Valuable, Value, Visit}; + +use core::fmt; + +/// TODO +pub trait Tuplable: Valuable { + /// TODO + fn definition(&self) -> TupleDef; +} + +/// TODO +#[derive(Debug)] +#[non_exhaustive] +pub enum TupleDef { + /// TODO + #[non_exhaustive] + Static { + /// TODO + fields: usize, + }, + /// TODO + #[non_exhaustive] + Dynamic { + /// TODO + fields: (usize, Option), + }, +} + +macro_rules! tuple_impls { + ( + $( $len:expr => ( $($n:tt $name:ident)+ ) )+ + ) => { + $( + impl<$($name),+> Valuable for ($($name,)+) + where + $($name: Valuable,)+ + { + fn as_value(&self) -> Value<'_> { + unimplemented!() + } + + fn visit(&self, visit: &mut dyn Visit) { + visit.visit_unnamed_fields(&[ + $( + self.$n.as_value(), + )+ + ]); + } + } + + impl<$($name),+> Tuplable for ($($name,)+) + where + $($name: Valuable,)+ + { + fn definition(&self) -> TupleDef { + TupleDef::Static { fields: $len } + } + } + )+ + } +} + +tuple_impls! { + 1 => (0 T0) + 2 => (0 T0 1 T1) + 3 => (0 T0 1 T1 2 T2) + 4 => (0 T0 1 T1 2 T2 3 T3) + 5 => (0 T0 1 T1 2 T2 3 T3 4 T4) + 6 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5) + 7 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6) + 8 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7) + 9 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8) + 10 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9) + 11 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10) + 12 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11) + 13 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12) + 14 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13) + 15 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14) + 16 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15) +} + +impl fmt::Debug for dyn Tuplable + '_ { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + struct DebugTuple<'a, 'b> { + fmt: fmt::DebugTuple<'a, 'b>, + } + + impl Visit for DebugTuple<'_, '_> { + fn visit_unnamed_fields(&mut self, values: &[Value<'_>]) { + for value in values { + self.fmt.field(value); + } + } + + fn visit_value(&mut self, _: Value<'_>) { + unimplemented!() + } + } + + let mut debug = DebugTuple { + fmt: fmt.debug_tuple(""), + }; + + self.visit(&mut debug); + debug.fmt.finish() + } +} + +impl TupleDef { + /// TODO + pub const fn new_static(fields: usize) -> TupleDef { + TupleDef::Static { fields } + } + + /// TODO + pub const fn new_dynamic(fields: (usize, Option)) -> TupleDef { + TupleDef::Dynamic { fields } + } + + /// Returns `true` if the tuple is [statically defined](TupleDef::Static). + /// + /// # Examples + /// + /// With a static tuple + /// + /// ``` + /// use valuable::TupleDef; + /// + /// let def = TupleDef::new_static(2); + /// assert!(def.is_static()); + /// ``` + /// + /// With a dynamic tuple + /// + /// ``` + /// use valuable::TupleDef; + /// + /// let def = TupleDef::new_dynamic((2, None)); + /// assert!(!def.is_static()); + /// ``` + pub fn is_static(&self) -> bool { + matches!(self, TupleDef::Static { .. }) + } + + /// Returns `true` if the tuple is [dynamically defined](TupleDef::Dynamic). + /// + /// # Examples + /// + /// With a static tuple + /// + /// ``` + /// use valuable::TupleDef; + /// + /// let def = TupleDef::new_static(2); + /// assert!(!def.is_dynamic()); + /// ``` + /// + /// With a dynamic tuple + /// + /// ``` + /// use valuable::TupleDef; + /// + /// let def = TupleDef::new_dynamic((2, None)); + /// assert!(def.is_dynamic()); + /// ``` + pub fn is_dynamic(&self) -> bool { + matches!(self, TupleDef::Dynamic { .. }) + } +} \ No newline at end of file diff --git a/valuable/src/value.rs b/valuable/src/value.rs index 2a55f07..aca6ae3 100644 --- a/valuable/src/value.rs +++ b/valuable/src/value.rs @@ -1,4 +1,4 @@ -use crate::*; +use crate::{Enumerable, Listable, Mappable, Structable, Tuplable, Valuable, Visit}; use core::fmt; @@ -392,6 +392,18 @@ value! { /// let v = Value::Enumerable(&my_enum); /// ``` Enumerable(&'a dyn Enumerable), + + /// A tuple value + /// + /// # Examples + /// + /// ``` + /// use valuable::{Value, Valuable}; + /// + /// let my_tuple = (123, 456); + /// let v = Value::Tuplable(&my_tuple); + /// ``` + Tuplable(&'a dyn Tuplable), } impl Valuable for Value<'_> { @@ -659,6 +671,25 @@ macro_rules! convert { _ => None, } } + + /// Return a `&dyn Tuplable` representation of `self`, if possible. + /// + /// # Examples + /// + /// ``` + /// use valuable::Value; + /// + /// let my_tuple = (123, 456); + /// + /// assert!(Value::Tuplable(&my_tuple).as_tuplable().is_some()); + /// assert!(Value::Bool(true).as_tuplable().is_none()); + /// ``` + pub fn as_tuplable(&self) -> Option<&dyn Tuplable> { + match *self { + Value::Tuplable(v) => Some(v), + _ => None, + } + } } } } From b91c3e5078c2df01bcb394bb008488c95a12a3f6 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Mon, 24 May 2021 10:51:38 -0700 Subject: [PATCH 2/6] more docs --- tests/tests/tuplable.rs | 1 - valuable/src/tuplable.rs | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/tests/tuplable.rs b/tests/tests/tuplable.rs index af6c252..5927394 100644 --- a/tests/tests/tuplable.rs +++ b/tests/tests/tuplable.rs @@ -1,2 +1 @@ use valuable::*; - diff --git a/valuable/src/tuplable.rs b/valuable/src/tuplable.rs index a3c62d1..e4fd55f 100644 --- a/valuable/src/tuplable.rs +++ b/valuable/src/tuplable.rs @@ -107,11 +107,23 @@ impl fmt::Debug for dyn Tuplable + '_ { } impl TupleDef { - /// TODO + /// Create a new [`TupleDef::Static`] instance + /// + /// This should be used when the tuple's fields are fixed and known ahead of time. + /// + /// # Examples + /// + /// ``` + /// use valuable::TupleDef; + /// + /// let def = TupleDef::new_static(2); + /// ``` pub const fn new_static(fields: usize) -> TupleDef { TupleDef::Static { fields } } + /// Create a new [`TupleDef::Dynamic`] instance. + /// /// TODO pub const fn new_dynamic(fields: (usize, Option)) -> TupleDef { TupleDef::Dynamic { fields } @@ -166,4 +178,4 @@ impl TupleDef { pub fn is_dynamic(&self) -> bool { matches!(self, TupleDef::Dynamic { .. }) } -} \ No newline at end of file +} From c3bf1368930b872861313f632eef41b48044a077 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 27 May 2021 13:34:49 -0700 Subject: [PATCH 3/6] finish work --- tests/tests/tuplable.rs | 88 ++++++++++++++++++ valuable/src/structable.rs | 1 + valuable/src/tuplable.rs | 178 +++++++++++++++++++++++++++++++------ valuable/src/valuable.rs | 11 +-- valuable/src/value.rs | 2 +- 5 files changed, 245 insertions(+), 35 deletions(-) diff --git a/tests/tests/tuplable.rs b/tests/tests/tuplable.rs index 5927394..1b41acd 100644 --- a/tests/tests/tuplable.rs +++ b/tests/tests/tuplable.rs @@ -1 +1,89 @@ use valuable::*; + +macro_rules! test_tuple { + ( + $( + $name:ident => $num:expr, $tuple:expr; + )* + ) => { + $( + #[test] + fn $name() { + let tuple = $tuple; + + assert_eq!(format!("{:?}", tuple.as_value()), format!("{:?}", tuple),); + + let def = tuple.definition(); + + assert_eq!(def.is_unit(), $num == 0); + assert!(def.is_static()); + assert!(matches!(def, TupleDef::Static { fields, .. } if fields == $num)); + + let counts = tests::visit_counts(&tuple); + assert_eq!( + counts, + tests::VisitCount { + visit_unnamed_fields: 1, + ..Default::default() + } + ); + } + )* + }; +} + +#[derive(Valuable, Debug)] +struct Foo { + name: &'static str, +} + +test_tuple! { + test_0 => 0, (); + test_1 => 1, (123,); + test_2 => 2, (123, "foo"); + test_3 => 3, (123, "foo", "bar".to_string()); + test_4 => 4, ("bar".to_string(), 123, "foo", Foo { name: "Foo" }); + test_10 => 10, ( + 1, + "two", + 3_u64, + 4_f32, + "five".to_string(), + 6, + 7, + 8, + Foo { name: "nine" }, + 10, + ); +} + +#[test] +fn test_dyn_impl() { + struct MyTuple; + + impl Valuable for MyTuple { + fn as_value(&self) -> Value<'_> { + Value::Tuplable(self) + } + + fn visit(&self, visit: &mut dyn Visit) { + visit.visit_unnamed_fields(&[Value::I32(123), Value::String("hello world")]); + visit.visit_unnamed_fields(&[Value::String("j/k there is more")]); + } + } + + impl Tuplable for MyTuple { + fn definition(&self) -> TupleDef { + TupleDef::new_dynamic((0, None)) + } + } + + let def = MyTuple.definition(); + assert!(!def.is_unit()); + assert!(def.is_dynamic()); + + assert_eq!( + format!("{:?}", MyTuple.as_value()), + format!("{:?}", (123, "hello world", "j/k there is more")), + ); +} diff --git a/valuable/src/structable.rs b/valuable/src/structable.rs index d9224f8..f35bdb2 100644 --- a/valuable/src/structable.rs +++ b/valuable/src/structable.rs @@ -346,6 +346,7 @@ impl<'a> StructDef<'a> { /// Create a new [`StructDef::Dynamic`] instance. /// /// This is used when the struct's fields may vary at runtime. + /// /// # Examples /// /// ``` diff --git a/valuable/src/tuplable.rs b/valuable/src/tuplable.rs index e4fd55f..e8c7fbe 100644 --- a/valuable/src/tuplable.rs +++ b/valuable/src/tuplable.rs @@ -2,30 +2,118 @@ use crate::{Valuable, Value, Visit}; use core::fmt; -/// TODO +/// A tuple-like [`Valuable`] sub-type. +/// +/// Implemented by [`Valuable`] types that have a tuple-like shape. Fields are +/// always unnamed. Values that implement `Tuplable` must return +/// [`Value::Tuplable`] from their [`Valuable::as_value`] implementation. +/// +/// It is uncommon for users to implement this type as the crate provides +/// implementations of `Tuplable` for Rust tuples. +/// +/// # Inspecting +/// +/// Inspecting fields contained by a `Tuplable` instance is done by visiting the +/// tuple. When visiting a `Tuple`, the `visit_nunnamed_fields()` method is +/// called. When the tuple is statically defined, `visit_unnamed_fields()` is +/// called once with the values of all the fields. A dynamic tuple +/// implementation may call `visit_unnamed_fields()` multiple times. pub trait Tuplable: Valuable { - /// TODO + /// Returns the tuple's definition. + /// + /// See [`TupleDef`] documentation for more details. + /// + /// # Examples + /// + /// ``` + /// use valuable::{Tuplable, TupleDef}; + /// + /// let tuple = (123, "hello"); + /// + /// if let TupleDef::Static { fields, .. } = tuple.definition() { + /// assert_eq!(2, fields); + /// } + /// ``` fn definition(&self) -> TupleDef; } -/// TODO +/// The number of fields and other tuple-level information. +/// +/// Returned by [`Tuplable::definition()`], `TupleDef` provides the caller with +/// information about the tuple's definition. +/// +/// This includes the number of fields contained by the tuple. #[derive(Debug)] #[non_exhaustive] pub enum TupleDef { - /// TODO + /// The tuple is statically-defined, all fields are known ahead of time. + /// + /// Static tuple implementations are provided by the crate. + /// + /// # Examples + /// + /// A statically defined tuple. + /// + /// ``` + /// use valuable::{Tuplable, TupleDef}; + /// + /// let tuple = (123, "hello"); + /// + /// match tuple.definition() { + /// TupleDef::Static { fields, .. } => { + /// assert_eq!(2, fields); + /// } + /// _ => unreachable!(), + /// }; + /// ``` #[non_exhaustive] Static { - /// TODO + /// The number of fields contained by the tuple. fields: usize, }, - /// TODO + /// The tuple is dynamically-defined, not all fields are known ahead of + /// time. + /// + /// # Examples + /// + /// ``` + /// use valuable::{Tuplable, TupleDef, Valuable, Value, Visit}; + /// + /// struct MyTuple; + /// + /// impl Valuable for MyTuple { + /// fn as_value(&self) -> Value<'_> { + /// Value::Tuplable(self) + /// } + /// + /// fn visit(&self, visit: &mut dyn Visit) { + /// visit.visit_unnamed_fields(&[Value::I32(123)]); + /// visit.visit_unnamed_fields(&[Value::String("hello world")]); + /// } + /// } + /// + /// impl Tuplable for MyTuple { + /// fn definition(&self) -> TupleDef { + /// TupleDef::new_dynamic((1, Some(3))) + /// } + /// } + /// ``` #[non_exhaustive] Dynamic { - /// TODO + /// Returns the bounds on the number of tuple fields. + /// + /// Specifically, the first element is the lower bound, and the second + /// element is the upper bound. fields: (usize, Option), }, } +impl Tuplable for () { + fn definition(&self) -> TupleDef { + TupleDef::Static { fields: 0 } + } +} + macro_rules! tuple_impls { ( $( $len:expr => ( $($n:tt $name:ident)+ ) )+ @@ -36,7 +124,7 @@ macro_rules! tuple_impls { $($name: Valuable,)+ { fn as_value(&self) -> Value<'_> { - unimplemented!() + Value::Tuplable(self) } fn visit(&self, visit: &mut dyn Visit) { @@ -81,28 +169,32 @@ tuple_impls! { impl fmt::Debug for dyn Tuplable + '_ { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - struct DebugTuple<'a, 'b> { - fmt: fmt::DebugTuple<'a, 'b>, - } + if self.definition().is_unit() { + ().fmt(fmt) + } else { + struct DebugTuple<'a, 'b> { + fmt: fmt::DebugTuple<'a, 'b>, + } - impl Visit for DebugTuple<'_, '_> { - fn visit_unnamed_fields(&mut self, values: &[Value<'_>]) { - for value in values { - self.fmt.field(value); + impl Visit for DebugTuple<'_, '_> { + fn visit_unnamed_fields(&mut self, values: &[Value<'_>]) { + for value in values { + self.fmt.field(value); + } } - } - fn visit_value(&mut self, _: Value<'_>) { - unimplemented!() + fn visit_value(&mut self, _: Value<'_>) { + unimplemented!() + } } - } - let mut debug = DebugTuple { - fmt: fmt.debug_tuple(""), - }; + let mut debug = DebugTuple { + fmt: fmt.debug_tuple(""), + }; - self.visit(&mut debug); - debug.fmt.finish() + self.visit(&mut debug); + debug.fmt.finish() + } } } @@ -124,11 +216,47 @@ impl TupleDef { /// Create a new [`TupleDef::Dynamic`] instance. /// - /// TODO + /// This is used when the tuple's fields may vary at runtime. + /// + /// # Examples + /// + /// ``` + /// use valuable::TupleDef; + /// + /// let def = TupleDef::new_dynamic((2, Some(10))); + /// ``` pub const fn new_dynamic(fields: (usize, Option)) -> TupleDef { TupleDef::Dynamic { fields } } + /// Returns `true` if `self` represents the [unit][()] tuple. + /// + /// # Examples + /// + /// With the unit tuple + /// + /// ``` + /// use valuable::Tuplable; + /// + /// let tuple: &dyn Tuplable = &(); + /// assert!(tuple.definition().is_unit()); + /// ``` + /// + /// When not the unit tuple. + /// + /// ``` + /// use valuable::Tuplable; + /// + /// let tuple: &dyn Tuplable = &(123,456); + /// assert!(!tuple.definition().is_unit()); + /// ``` + pub fn is_unit(&self) -> bool { + match *self { + TupleDef::Static { fields } => fields == 0, + TupleDef::Dynamic { fields } => fields == (0, Some(0)), + } + } + /// Returns `true` if the tuple is [statically defined](TupleDef::Static). /// /// # Examples diff --git a/valuable/src/valuable.rs b/valuable/src/valuable.rs index 2a95685..3b8dd2e 100644 --- a/valuable/src/valuable.rs +++ b/valuable/src/valuable.rs @@ -262,18 +262,11 @@ impl Valuable for Wrapping { impl Valuable for () { fn as_value(&self) -> Value<'_> { - Value::Unit + Value::Tuplable(self) } fn visit(&self, visit: &mut dyn Visit) { - visit.visit_value(Value::Unit); - } - - fn visit_slice(slice: &[Self], visit: &mut dyn Visit) - where - Self: Sized, - { - visit.visit_primitive_slice(Slice::Unit(slice)); + visit.visit_unnamed_fields(&[]); } } diff --git a/valuable/src/value.rs b/valuable/src/value.rs index aca6ae3..8b78cc3 100644 --- a/valuable/src/value.rs +++ b/valuable/src/value.rs @@ -88,7 +88,7 @@ macro_rules! value { impl<'a> From<()> for Value<'a> { fn from(_: ()) -> Value<'a> { - Value::Unit + Value::Tuplable(&()) } } From c486b693396b9c524c7014461ce2d26ef402a0e8 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 27 May 2021 13:41:19 -0700 Subject: [PATCH 4/6] remove () value test --- tests/tests/value.rs | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tests/tests/value.rs b/tests/tests/value.rs index 6b97060..bc07546 100644 --- a/tests/tests/value.rs +++ b/tests/tests/value.rs @@ -248,40 +248,6 @@ fn test_error() { assert_value!(&'a dyn error::Error: Error, as_error, yes => error); } -#[test] -fn test_unit() { - use Value::Unit; - - struct VisitValue; - - impl Visit for VisitValue { - fn visit_value(&mut self, val: Value<'_>) { - assert!(matches!(val, Unit)); - } - } - - // Visit the raw value once - assert_visit_call!(&()); - let mut visit = VisitValue; - ().visit(&mut visit); - - let val = Value::from(()); - - // Visit the converted value - assert_visit_call!(&()); - let mut visit = VisitValue; - val.visit(&mut visit); - - // Test conversion - assert!(matches!(val, Unit)); - - // Test `as_value()` - assert!(matches!(Valuable::as_value(&()), Unit)); - - // Test clone() - assert!(matches!(val.clone(), Unit)); -} - test_num! { test_u8(as_u8, u8, U8); test_u16(as_u16, u16, U16); From 7c0c7df4f67189cbb2389413b80a3c240f8a1511 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Thu, 27 May 2021 13:47:00 -0700 Subject: [PATCH 5/6] fix valuable-serde --- valuable-serde/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/valuable-serde/src/lib.rs b/valuable-serde/src/lib.rs index d205165..bf08871 100644 --- a/valuable-serde/src/lib.rs +++ b/valuable-serde/src/lib.rs @@ -221,6 +221,13 @@ where } _ => unreachable!(), }, + Value::Tuplable(t) => { + if t.definition().is_unit() { + serializer.serialize_unit() + } else { + unimplemented!() + } + } #[cfg(feature = "std")] Value::Path(p) => Serialize::serialize(p, serializer), #[cfg(feature = "std")] From 6a596c5fe36f927776ece208bb9f549382bc1b48 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Tue, 1 Jun 2021 13:59:49 -0700 Subject: [PATCH 6/6] Update valuable/src/tuplable.rs Co-authored-by: Taiki Endo --- valuable/src/tuplable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/valuable/src/tuplable.rs b/valuable/src/tuplable.rs index e8c7fbe..b1ecc1d 100644 --- a/valuable/src/tuplable.rs +++ b/valuable/src/tuplable.rs @@ -229,7 +229,7 @@ impl TupleDef { TupleDef::Dynamic { fields } } - /// Returns `true` if `self` represents the [unit][()] tuple. + /// Returns `true` if `self` represents the [unit][primitive@unit] tuple. /// /// # Examples ///