From 5acfd4551267a79eaf99fd5191e352246a65e4c0 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 1 Jul 2021 01:39:22 +0900 Subject: [PATCH 01/10] Implement valuable value pointer --- valuable-derive/Cargo.toml | 2 +- valuable-derive/src/{expand.rs => derive.rs} | 2 +- valuable-derive/src/lib.rs | 16 +- valuable-derive/src/pointer.rs | 94 +++++++++++ valuable/examples/pointer.rs | 53 +++++++ valuable/src/lib.rs | 3 + valuable/src/pointer.rs | 159 +++++++++++++++++++ valuable/src/slice.rs | 11 ++ valuable/src/valuable.rs | 6 + 9 files changed, 340 insertions(+), 6 deletions(-) rename valuable-derive/src/{expand.rs => derive.rs} (99%) create mode 100644 valuable-derive/src/pointer.rs create mode 100644 valuable/examples/pointer.rs create mode 100644 valuable/src/pointer.rs diff --git a/valuable-derive/Cargo.toml b/valuable-derive/Cargo.toml index 63077c6..0bf5694 100644 --- a/valuable-derive/Cargo.toml +++ b/valuable-derive/Cargo.toml @@ -12,7 +12,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0.60", features = ["extra-traits"] } +syn = { version = "1.0.60", features = ["full", "extra-traits"] } [dev-dependencies] valuable = { path = "../valuable", features = ["derive"] } diff --git a/valuable-derive/src/expand.rs b/valuable-derive/src/derive.rs similarity index 99% rename from valuable-derive/src/expand.rs rename to valuable-derive/src/derive.rs index 45cfd40..d74210c 100644 --- a/valuable-derive/src/expand.rs +++ b/valuable-derive/src/derive.rs @@ -312,7 +312,7 @@ fn allowed_lints() -> TokenStream { } } -fn respan(tokens: TokenStream, span: &impl ToTokens) -> TokenStream { +pub(crate) fn respan(tokens: TokenStream, span: &impl ToTokens) -> TokenStream { let mut iter = span.to_token_stream().into_iter(); // `Span` on stable Rust has a limitation that only points to the first // token, not the whole tokens. We can work around this limitation by diff --git a/valuable-derive/src/lib.rs b/valuable-derive/src/lib.rs index 6021cb6..b593886 100644 --- a/valuable-derive/src/lib.rs +++ b/valuable-derive/src/lib.rs @@ -1,9 +1,10 @@ -extern crate proc_macro; +#![warn(rust_2018_idioms, unreachable_pub)] -mod expand; +mod derive; +mod pointer; use proc_macro::TokenStream; -use syn::parse_macro_input; +use syn::{parse_macro_input, Error}; /// Derive a `Valuable` implementation for a struct or enum. /// @@ -26,5 +27,12 @@ use syn::parse_macro_input; #[proc_macro_derive(Valuable, attributes(valuable))] pub fn derive_valuable(input: TokenStream) -> TokenStream { let mut input = parse_macro_input!(input as syn::DeriveInput); - expand::derive_valuable(&mut input).into() + derive::derive_valuable(&mut input).into() +} + +#[proc_macro] +pub fn visit_pointer(input: TokenStream) -> TokenStream { + pointer::visit_pointer(input.into()) + .unwrap_or_else(Error::into_compile_error) + .into() } diff --git a/valuable-derive/src/pointer.rs b/valuable-derive/src/pointer.rs new file mode 100644 index 0000000..0faf753 --- /dev/null +++ b/valuable-derive/src/pointer.rs @@ -0,0 +1,94 @@ +use std::collections::VecDeque; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{Expr, Result, Token}; + +use crate::derive::respan; + +pub(crate) fn visit_pointer(input: TokenStream) -> Result { + let Input { + expr, + segments, + visit, + } = syn::parse2(input)?; + + let segments = segments.iter().map(|segment| match segment { + Segment::Member(syn::Member::Named(ident)) => { + let literal = ident.to_string(); + quote! { + ::valuable::PointerSegment::Field(#literal), + } + } + Segment::Member(syn::Member::Unnamed(index)) => { + quote! { + ::valuable::PointerSegment::TupleIndex(#index), + } + } + Segment::Index(expr) => { + let expr = respan(quote! { &#expr }, expr); + quote! { + ::valuable::PointerSegment::Index( + ::valuable::Valuable::as_value(#expr) + ), + } + } + }); + + let visit_pointer = respan(quote! { ::valuable::Valuable::visit_pointer }, &expr); + Ok(quote! { + #visit_pointer( + &#expr, + ::valuable::Pointer::new(&[ + #(#segments)* + ]), + &mut #visit, + ) + }) +} + +struct Input { + expr: Expr, + segments: VecDeque, + visit: Expr, +} + +enum Segment { + Member(syn::Member), + Index(Box), +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> Result { + let mut chain = input.parse()?; + let _: Token![,] = input.parse()?; + let visit = input.parse()?; + let _: Option = input.parse()?; + + let mut segments = VecDeque::new(); + let expr; + loop { + match chain { + Expr::Field(e) => { + chain = *e.base; + segments.push_front(Segment::Member(e.member)) + } + Expr::Index(e) => { + chain = *e.expr; + segments.push_front(Segment::Index(e.index)) + } + e => { + expr = e; + break; + } + } + } + + Ok(Self { + expr, + segments, + visit, + }) + } +} diff --git a/valuable/examples/pointer.rs b/valuable/examples/pointer.rs new file mode 100644 index 0000000..9509035 --- /dev/null +++ b/valuable/examples/pointer.rs @@ -0,0 +1,53 @@ +// TODO: Change this example to doc example, and add tests. + +use valuable::{visit_pointer, Pointer, PointerSegment, Valuable, Value, Visit}; + +#[derive(Valuable)] +struct Struct1 { + field1: String, + field2: Struct2, +} + +#[derive(Valuable)] +struct Struct2 { + field: String, +} + +struct Visitor; + +impl Visit for Visitor { + fn visit_value(&mut self, value: Value<'_>) { + println!("{:?}", value); + } +} + +fn main() { + let value = Struct1 { + field1: "a".to_owned(), + field2: Struct2 { + field: "b".to_owned(), + }, + }; + + let mut visitor = Visitor; + + value.visit_pointer( + Pointer::new(&[PointerSegment::Field("field1")]), + &mut visitor, + ); + value.visit_pointer( + Pointer::new(&[PointerSegment::Field("field2")]), + &mut visitor, + ); + value.visit_pointer( + Pointer::new(&[ + PointerSegment::Field("field2"), + PointerSegment::Field("field"), + ]), + &mut visitor, + ); + + visit_pointer!(value.field1, visitor); // output: "a" + visit_pointer!(value.field2, visitor); // output: Struct2 { field: "b" } + visit_pointer!(value.field2.field, visitor); // output: "b" +} diff --git a/valuable/src/lib.rs b/valuable/src/lib.rs index 7e61b5a..623b6de 100644 --- a/valuable/src/lib.rs +++ b/valuable/src/lib.rs @@ -115,6 +115,9 @@ pub use mappable::Mappable; mod named_values; pub use named_values::NamedValues; +mod pointer; +pub use pointer::{Pointer, PointerSegment}; + mod slice; pub use slice::Slice; diff --git a/valuable/src/pointer.rs b/valuable/src/pointer.rs new file mode 100644 index 0000000..a36b2d8 --- /dev/null +++ b/valuable/src/pointer.rs @@ -0,0 +1,159 @@ +//! Valuable value pointer. + +use crate::*; + +/// A pointer to the value. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub struct Pointer<'a> { + path: &'a [PointerSegment<'a>], +} + +impl<'a> Pointer<'a> { + /// Creates a new `Pointer`. + pub const fn new(path: &'a [PointerSegment<'a>]) -> Self { + Self { path } + } + + /// Returns a path pointed by this pointer. + /// + /// If this pointer points to the current value, this method returns an empty slice. + pub fn path(self) -> &'a [PointerSegment<'a>] { + self.path + } + + #[must_use] + pub fn step(self) -> Self { + Self::new(&self.path[1..]) + } +} + +impl<'a> From<&'a [PointerSegment<'a>]> for Pointer<'a> { + fn from(path: &'a [PointerSegment<'a>]) -> Self { + Self::new(path) + } +} + +/// A segment of a pointer. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum PointerSegment<'a> { + /// Access of a named struct field. + Field(&'a str), + /// Access of an unnamed struct or a tuple field. + TupleIndex(usize), + /// Indexing of a list or a map. + Index(Value<'a>), +} + +pub(crate) fn visit_pointer(value: &V, pointer: Pointer<'_>, visit: &mut dyn Visit) +where + V: ?Sized + Valuable, +{ + let value = value.as_value(); + if pointer.path.is_empty() { + visit.visit_value(value); + return; + } + + let visitor = &mut Visitor { + pointer, + visit, + index: 0, + }; + match (value, pointer.path[0]) { + (Value::Listable(l), PointerSegment::Index(..)) => { + l.visit(visitor); + } + (Value::Mappable(m), PointerSegment::Index(..)) => { + m.visit(visitor); + } + (Value::Tuplable(t), PointerSegment::TupleIndex(..)) => { + t.visit(visitor); + } + (Value::Structable(s), PointerSegment::TupleIndex(..)) + if s.definition().fields().is_unnamed() => + { + s.visit(visitor); + } + (Value::Structable(s), PointerSegment::Field(..)) if s.definition().fields().is_named() => { + s.visit(visitor); + } + (_, p) => { + panic!("invalid pointer: {:?},", p) + } + } +} + +struct Visitor<'a> { + pointer: Pointer<'a>, + visit: &'a mut dyn Visit, + index: usize, +} + +impl Visit for Visitor<'_> { + fn visit_value(&mut self, value: Value<'_>) { + if let PointerSegment::Index(index) = self.pointer.path[0] { + if index.as_usize() == Some(self.index) { + value.visit_pointer(self.pointer.step(), self.visit); + } + } + self.index += 1; + } + + fn visit_named_fields(&mut self, named_values: &NamedValues<'_>) { + if let PointerSegment::Field(name) = self.pointer.path[0] { + if let Some(value) = named_values.get_by_name(name) { + value.visit_pointer(self.pointer.step(), self.visit); + } + } + } + + fn visit_unnamed_fields(&mut self, values: &[Value<'_>]) { + if let PointerSegment::TupleIndex(index) = self.pointer.path[0] { + if let Some(value) = values.get(index - self.index) { + value.visit_pointer(self.pointer.step(), self.visit); + } + } + } + + fn visit_primitive_slice(&mut self, slice: Slice<'_>) { + if let PointerSegment::Index(index) = self.pointer.path[0] { + if let Some(index) = index.as_usize() { + if let Some(value) = slice.get(index - self.index) { + value.visit_pointer(self.pointer.step(), self.visit); + } + } + } + self.index += slice.len(); + } + + fn visit_entry(&mut self, key: Value<'_>, value: Value<'_>) { + if let PointerSegment::Index(index) = self.pointer.path[0] { + let matched = match key { + Value::Bool(k) => index.as_bool() == Some(k), + Value::Char(k) => index.as_char() == Some(k), + Value::I8(k) => index.as_i8() == Some(k), + Value::I16(k) => index.as_i16() == Some(k), + Value::I32(k) => index.as_i32() == Some(k), + Value::I64(k) => index.as_i64() == Some(k), + Value::I128(k) => index.as_i128() == Some(k), + Value::Isize(k) => index.as_isize() == Some(k), + Value::U8(k) => index.as_u8() == Some(k), + Value::U16(k) => index.as_u16() == Some(k), + Value::U32(k) => index.as_u32() == Some(k), + Value::U64(k) => index.as_u64() == Some(k), + Value::U128(k) => index.as_u128() == Some(k), + Value::Usize(k) => index.as_usize() == Some(k), + Value::String(k) => index.as_str() == Some(k), + #[cfg(feature = "std")] + Value::Path(k) => index.as_path() == Some(k), + // f32 and f64 are not included because they do not implement `Eq`. + _ => false, + }; + if matched { + value.visit_pointer(self.pointer.step(), self.visit); + } + } + } +} diff --git a/valuable/src/slice.rs b/valuable/src/slice.rs index 1bd6960..f831f4d 100644 --- a/valuable/src/slice.rs +++ b/valuable/src/slice.rs @@ -75,6 +75,17 @@ macro_rules! slice { } } + /// Returns a reference to an element of index. + pub fn get(&self, index: usize) -> Option> { + #[allow(unused_doc_comments)] + match self { + $( + $(#[$attrs])* + Slice::$variant(s) => s.get(index).map(Valuable::as_value), + )* + } + } + /// Returns an iterator over the slice. /// /// # Examples diff --git a/valuable/src/valuable.rs b/valuable/src/valuable.rs index e1f8e1d..8638d32 100644 --- a/valuable/src/valuable.rs +++ b/valuable/src/valuable.rs @@ -1,3 +1,4 @@ +use crate::pointer::{self, Pointer}; use crate::{Slice, Value, Visit}; use core::fmt; @@ -70,6 +71,11 @@ pub trait Valuable { visit.visit_value(item.as_value()); } } + + /// Visit the value pointed by `pointer`. + fn visit_pointer(&self, pointer: Pointer<'_>, visit: &mut dyn Visit) { + pointer::visit_pointer(self, pointer, visit) + } } macro_rules! deref { From 98efc04833a25ba5926e0b1605ec7d4a97fcfa63 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 1 Jul 2021 01:44:08 +0900 Subject: [PATCH 02/10] temporarily hide Pointer::step from doc --- valuable/src/pointer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/valuable/src/pointer.rs b/valuable/src/pointer.rs index a36b2d8..aa5cbfc 100644 --- a/valuable/src/pointer.rs +++ b/valuable/src/pointer.rs @@ -22,6 +22,7 @@ impl<'a> Pointer<'a> { self.path } + #[doc(hidden)] #[must_use] pub fn step(self) -> Self { Self::new(&self.path[1..]) From 2ae881c37cd35adeaa1dfb229b6306b66d0b036c Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sat, 3 Jul 2021 17:24:00 +0900 Subject: [PATCH 03/10] cleanup --- tests/tests/pointer.rs | 79 ++++++++++++++++++++++++++++++++++ valuable-derive/src/pointer.rs | 8 ++-- valuable/examples/pointer.rs | 53 ----------------------- valuable/src/lib.rs | 3 +- valuable/src/pointer.rs | 76 +++++++++++++++++++++++--------- valuable/src/valuable.rs | 2 +- 6 files changed, 141 insertions(+), 80 deletions(-) create mode 100644 tests/tests/pointer.rs delete mode 100644 valuable/examples/pointer.rs diff --git a/tests/tests/pointer.rs b/tests/tests/pointer.rs new file mode 100644 index 0000000..94b30f1 --- /dev/null +++ b/tests/tests/pointer.rs @@ -0,0 +1,79 @@ +use valuable::{pointer, Valuable, Value, Visit}; + +#[derive(Valuable)] +struct Struct1 { + x: String, + y: Struct2, +} + +#[derive(Valuable)] +struct Struct2 { + z: String, +} + +#[derive(Default)] +struct CollectValues(Vec); + +impl Visit for CollectValues { + fn visit_value(&mut self, value: Value<'_>) { + self.0.push(format!("{:?}", value)); + } +} + +#[test] +fn basic() { + let value = Struct1 { + x: "a".to_owned(), + y: Struct2 { z: "b".to_owned() }, + }; + + let mut visitor = CollectValues::default(); + value.visit_pointer( + pointer::Pointer::new(&[pointer::Segment::Field("x")]), + &mut visitor, + ); + assert_eq!(visitor.0.len(), 1); + assert_eq!(visitor.0[0], r#""a""#); + + let mut visitor = CollectValues::default(); + value.visit_pointer( + pointer::Pointer::new(&[pointer::Segment::Field("y")]), + &mut visitor, + ); + assert_eq!(visitor.0.len(), 1); + assert_eq!(visitor.0[0], r#"Struct2 { z: "b" }"#); + + let mut visitor = CollectValues::default(); + value.visit_pointer( + pointer::Pointer::new(&[pointer::Segment::Field("y"), pointer::Segment::Field("z")]), + &mut visitor, + ); + assert_eq!(visitor.0.len(), 1); + assert_eq!(visitor.0[0], r#""b""#); +} + +#[cfg(feature = "derive")] +#[test] +fn visit_pointer_macro() { + use valuable::visit_pointer; + + let value = Struct1 { + x: "a".to_owned(), + y: Struct2 { z: "b".to_owned() }, + }; + + let mut visitor = CollectValues::default(); + visit_pointer!(value.x, visitor); + assert_eq!(visitor.0.len(), 1); + assert_eq!(visitor.0[0], r#""a""#); + + let mut visitor = CollectValues::default(); + visit_pointer!(value.y, visitor); + assert_eq!(visitor.0.len(), 1); + assert_eq!(visitor.0[0], r#"Struct2 { z: "b" }"#); + + let mut visitor = CollectValues::default(); + visit_pointer!(value.y.z, visitor); + assert_eq!(visitor.0.len(), 1); + assert_eq!(visitor.0[0], r#""b""#); +} diff --git a/valuable-derive/src/pointer.rs b/valuable-derive/src/pointer.rs index 0faf753..68ece19 100644 --- a/valuable-derive/src/pointer.rs +++ b/valuable-derive/src/pointer.rs @@ -18,18 +18,18 @@ pub(crate) fn visit_pointer(input: TokenStream) -> Result { Segment::Member(syn::Member::Named(ident)) => { let literal = ident.to_string(); quote! { - ::valuable::PointerSegment::Field(#literal), + ::valuable::pointer::Segment::Field(#literal), } } Segment::Member(syn::Member::Unnamed(index)) => { quote! { - ::valuable::PointerSegment::TupleIndex(#index), + ::valuable::pointer::Segment::TupleIndex(#index), } } Segment::Index(expr) => { let expr = respan(quote! { &#expr }, expr); quote! { - ::valuable::PointerSegment::Index( + ::valuable::pointer::Segment::Index( ::valuable::Valuable::as_value(#expr) ), } @@ -40,7 +40,7 @@ pub(crate) fn visit_pointer(input: TokenStream) -> Result { Ok(quote! { #visit_pointer( &#expr, - ::valuable::Pointer::new(&[ + ::valuable::pointer::Pointer::new(&[ #(#segments)* ]), &mut #visit, diff --git a/valuable/examples/pointer.rs b/valuable/examples/pointer.rs deleted file mode 100644 index 9509035..0000000 --- a/valuable/examples/pointer.rs +++ /dev/null @@ -1,53 +0,0 @@ -// TODO: Change this example to doc example, and add tests. - -use valuable::{visit_pointer, Pointer, PointerSegment, Valuable, Value, Visit}; - -#[derive(Valuable)] -struct Struct1 { - field1: String, - field2: Struct2, -} - -#[derive(Valuable)] -struct Struct2 { - field: String, -} - -struct Visitor; - -impl Visit for Visitor { - fn visit_value(&mut self, value: Value<'_>) { - println!("{:?}", value); - } -} - -fn main() { - let value = Struct1 { - field1: "a".to_owned(), - field2: Struct2 { - field: "b".to_owned(), - }, - }; - - let mut visitor = Visitor; - - value.visit_pointer( - Pointer::new(&[PointerSegment::Field("field1")]), - &mut visitor, - ); - value.visit_pointer( - Pointer::new(&[PointerSegment::Field("field2")]), - &mut visitor, - ); - value.visit_pointer( - Pointer::new(&[ - PointerSegment::Field("field2"), - PointerSegment::Field("field"), - ]), - &mut visitor, - ); - - visit_pointer!(value.field1, visitor); // output: "a" - visit_pointer!(value.field2, visitor); // output: Struct2 { field: "b" } - visit_pointer!(value.field2.field, visitor); // output: "b" -} diff --git a/valuable/src/lib.rs b/valuable/src/lib.rs index 623b6de..18c88a1 100644 --- a/valuable/src/lib.rs +++ b/valuable/src/lib.rs @@ -115,8 +115,7 @@ pub use mappable::Mappable; mod named_values; pub use named_values::NamedValues; -mod pointer; -pub use pointer::{Pointer, PointerSegment}; +pub mod pointer; mod slice; pub use slice::Slice; diff --git a/valuable/src/pointer.rs b/valuable/src/pointer.rs index aa5cbfc..12a4a98 100644 --- a/valuable/src/pointer.rs +++ b/valuable/src/pointer.rs @@ -1,24 +1,62 @@ //! Valuable value pointer. +//! +//! # Examples +//! +//! ```rust +//! use valuable::*; +//! +//! #[derive(Valuable)] +//! struct Struct1 { +//! x: String, +//! y: Struct2, +//! } +//! +//! #[derive(Valuable)] +//! struct Struct2 { +//! z: String, +//! } +//! +//! struct Visitor; +//! +//! impl Visit for Visitor { +//! fn visit_value(&mut self, value: Value<'_>) { +//! println!("{:?}", value); +//! } +//! } +//! +//! let value = Struct1 { +//! x: "a".to_owned(), +//! y: Struct2 { +//! z: "b".to_owned(), +//! }, +//! }; +//! +//! let mut visitor = Visitor; +//! +//! visit_pointer!(value.x, visitor); // "a" +//! visit_pointer!(value.y, visitor); // Struct2 { field: "b" } +//! visit_pointer!(value.y.z, visitor); // "b" +//! ``` -use crate::*; +use crate::{NamedValues, Slice, Valuable, Value, Visit}; /// A pointer to the value. #[derive(Debug, Clone, Copy)] #[non_exhaustive] pub struct Pointer<'a> { - path: &'a [PointerSegment<'a>], + path: &'a [Segment<'a>], } impl<'a> Pointer<'a> { /// Creates a new `Pointer`. - pub const fn new(path: &'a [PointerSegment<'a>]) -> Self { + pub const fn new(path: &'a [Segment<'a>]) -> Self { Self { path } } /// Returns a path pointed by this pointer. /// /// If this pointer points to the current value, this method returns an empty slice. - pub fn path(self) -> &'a [PointerSegment<'a>] { + pub fn path(self) -> &'a [Segment<'a>] { self.path } @@ -29,16 +67,16 @@ impl<'a> Pointer<'a> { } } -impl<'a> From<&'a [PointerSegment<'a>]> for Pointer<'a> { - fn from(path: &'a [PointerSegment<'a>]) -> Self { +impl<'a> From<&'a [Segment<'a>]> for Pointer<'a> { + fn from(path: &'a [Segment<'a>]) -> Self { Self::new(path) } } -/// A segment of a pointer. +/// A segment of a path. #[derive(Debug, Clone, Copy)] #[non_exhaustive] -pub enum PointerSegment<'a> { +pub enum Segment<'a> { /// Access of a named struct field. Field(&'a str), /// Access of an unnamed struct or a tuple field. @@ -63,21 +101,19 @@ where index: 0, }; match (value, pointer.path[0]) { - (Value::Listable(l), PointerSegment::Index(..)) => { + (Value::Listable(l), Segment::Index(..)) => { l.visit(visitor); } - (Value::Mappable(m), PointerSegment::Index(..)) => { + (Value::Mappable(m), Segment::Index(..)) => { m.visit(visitor); } - (Value::Tuplable(t), PointerSegment::TupleIndex(..)) => { + (Value::Tuplable(t), Segment::TupleIndex(..)) => { t.visit(visitor); } - (Value::Structable(s), PointerSegment::TupleIndex(..)) - if s.definition().fields().is_unnamed() => - { + (Value::Structable(s), Segment::TupleIndex(..)) if s.definition().fields().is_unnamed() => { s.visit(visitor); } - (Value::Structable(s), PointerSegment::Field(..)) if s.definition().fields().is_named() => { + (Value::Structable(s), Segment::Field(..)) if s.definition().fields().is_named() => { s.visit(visitor); } (_, p) => { @@ -94,7 +130,7 @@ struct Visitor<'a> { impl Visit for Visitor<'_> { fn visit_value(&mut self, value: Value<'_>) { - if let PointerSegment::Index(index) = self.pointer.path[0] { + if let Segment::Index(index) = self.pointer.path[0] { if index.as_usize() == Some(self.index) { value.visit_pointer(self.pointer.step(), self.visit); } @@ -103,7 +139,7 @@ impl Visit for Visitor<'_> { } fn visit_named_fields(&mut self, named_values: &NamedValues<'_>) { - if let PointerSegment::Field(name) = self.pointer.path[0] { + if let Segment::Field(name) = self.pointer.path[0] { if let Some(value) = named_values.get_by_name(name) { value.visit_pointer(self.pointer.step(), self.visit); } @@ -111,7 +147,7 @@ impl Visit for Visitor<'_> { } fn visit_unnamed_fields(&mut self, values: &[Value<'_>]) { - if let PointerSegment::TupleIndex(index) = self.pointer.path[0] { + if let Segment::TupleIndex(index) = self.pointer.path[0] { if let Some(value) = values.get(index - self.index) { value.visit_pointer(self.pointer.step(), self.visit); } @@ -119,7 +155,7 @@ impl Visit for Visitor<'_> { } fn visit_primitive_slice(&mut self, slice: Slice<'_>) { - if let PointerSegment::Index(index) = self.pointer.path[0] { + if let Segment::Index(index) = self.pointer.path[0] { if let Some(index) = index.as_usize() { if let Some(value) = slice.get(index - self.index) { value.visit_pointer(self.pointer.step(), self.visit); @@ -130,7 +166,7 @@ impl Visit for Visitor<'_> { } fn visit_entry(&mut self, key: Value<'_>, value: Value<'_>) { - if let PointerSegment::Index(index) = self.pointer.path[0] { + if let Segment::Index(index) = self.pointer.path[0] { let matched = match key { Value::Bool(k) => index.as_bool() == Some(k), Value::Char(k) => index.as_char() == Some(k), diff --git a/valuable/src/valuable.rs b/valuable/src/valuable.rs index 8638d32..d1559ed 100644 --- a/valuable/src/valuable.rs +++ b/valuable/src/valuable.rs @@ -74,7 +74,7 @@ pub trait Valuable { /// Visit the value pointed by `pointer`. fn visit_pointer(&self, pointer: Pointer<'_>, visit: &mut dyn Visit) { - pointer::visit_pointer(self, pointer, visit) + pointer::visit_pointer(self, pointer, visit); } } From 32df15fd06df95c7a2e026ce5ee91f9408f999dd Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Sun, 25 Jul 2021 14:59:26 +0900 Subject: [PATCH 04/10] Override visit_pointer on slice, Vec, VecDeque --- valuable/src/listable.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/valuable/src/listable.rs b/valuable/src/listable.rs index b79317f..ea7901a 100644 --- a/valuable/src/listable.rs +++ b/valuable/src/listable.rs @@ -147,6 +147,20 @@ macro_rules! slice { fn visit(&self, visit: &mut dyn Visit) { T::visit_slice(self, visit); } + + fn visit_pointer(&self, pointer: pointer::Pointer<'_>, visit: &mut dyn Visit) { + if pointer.path().is_empty() { + visit.visit_value(self.as_value()); + return; + } + if let pointer::Segment::Index(i) = pointer.path()[0] { + if let Some(i) = i.as_usize() { + if let Some(value) = self.get(i) { + value.visit_pointer(pointer.step(), visit); + } + } + } + } } $(#[$attrs])* @@ -226,6 +240,20 @@ impl Valuable for alloc::collections::VecDeque { T::visit_slice(first, visit); T::visit_slice(second, visit); } + + fn visit_pointer(&self, pointer: pointer::Pointer<'_>, visit: &mut dyn Visit) { + if pointer.path().is_empty() { + visit.visit_value(self.as_value()); + return; + } + if let pointer::Segment::Index(i) = pointer.path()[0] { + if let Some(i) = i.as_usize() { + if let Some(value) = self.get(i) { + value.visit_pointer(pointer.step(), visit); + } + } + } + } } #[cfg(feature = "alloc")] From 7ed1114c96886164dd06032fcc400bf24f185f6a Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Tue, 23 Nov 2021 16:37:36 +0900 Subject: [PATCH 05/10] Add explanation to pointer module Co-authored-by: Eliza Weisman --- valuable/src/pointer.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/valuable/src/pointer.rs b/valuable/src/pointer.rs index 12a4a98..08998ca 100644 --- a/valuable/src/pointer.rs +++ b/valuable/src/pointer.rs @@ -1,4 +1,10 @@ -//! Valuable value pointer. +//! References to fields of [`Valuable`] values. +//! +//! A [`Pointer`] stores a path traversal to a particular value in a nested +//! [`Valuable`] structure. For example, a [`Pointer`] might refer to the field `z` +//! of the field `y` of the value `x`. [`Pointer`] paths can also include indices into +//! [`Listable`] and [`Tupleable`] values, in order to represent expressions like +//! `x.y[3].0`. //! //! # Examples //! From acf64f7bebc9c22028115a2bcaf9c319f37d9bd1 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Tue, 23 Nov 2021 21:18:01 +0900 Subject: [PATCH 06/10] Override visit_pointer on struct --- valuable-derive/src/derive.rs | 82 ++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/valuable-derive/src/derive.rs b/valuable-derive/src/derive.rs index d74210c..74581dc 100644 --- a/valuable-derive/src/derive.rs +++ b/valuable-derive/src/derive.rs @@ -16,6 +16,7 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea let name = &input.ident; let name_literal = name.to_string(); let visit_fields; + let visit_pointer; let struct_def; let mut named_fields_statics = None; @@ -33,13 +34,17 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea ) }; - let fields = data.fields.iter().map(|field| { - let f = field.ident.as_ref(); - let tokens = quote! { - &self.#f - }; - respan(tokens, &field.ty) - }); + let fields: Vec<_> = data + .fields + .iter() + .map(|field| { + let f = field.ident.as_ref(); + let tokens = quote! { + &self.#f + }; + respan(tokens, &field.ty) + }) + .collect(); visit_fields = quote! { visitor.visit_named_fields(&::valuable::NamedValues::new( #named_fields_static_name, @@ -47,7 +52,21 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea #(::valuable::Valuable::as_value(#fields),)* ], )); - } + }; + let field_name_literals = data + .fields + .iter() + .map(|field| field.ident.as_ref().unwrap().to_string()); + visit_pointer = quote! { + if let ::valuable::pointer::Segment::Field(f) = pointer.path()[0] { + match f { + #(#field_name_literals => { + ::valuable::Valuable::visit_pointer(#fields, pointer.step(), visit) + })* + _ => {} + } + } + }; } syn::Fields::Unnamed(_) | syn::Fields::Unit => { struct_def = quote! { @@ -57,13 +76,18 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea ) }; - let indices = data.fields.iter().enumerate().map(|(i, field)| { - let index = syn::Index::from(i); - let tokens = quote! { - &self.#index - }; - respan(tokens, &field.ty) - }); + let indices: Vec<_> = data + .fields + .iter() + .enumerate() + .map(|(i, field)| { + let index = syn::Index::from(i); + let tokens = quote! { + &self.#index + }; + respan(tokens, &field.ty) + }) + .collect(); visit_fields = quote! { visitor.visit_unnamed_fields( &[ @@ -71,6 +95,21 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea ], ); }; + let field_indices = data + .fields + .iter() + .enumerate() + .map(|(i, _)| syn::Index::from(i)); + visit_pointer = quote! { + if let ::valuable::pointer::Segment::TupleIndex(i) = pointer.path()[0] { + match i { + #(#field_indices => { + ::valuable::Valuable::visit_pointer(#indices, pointer.step(), visit) + })* + _ => {} + } + } + }; } } @@ -93,6 +132,18 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea fn visit(&self, visitor: &mut dyn ::valuable::Visit) { #visit_fields } + + fn visit_pointer( + &self, + pointer: ::valuable::pointer::Pointer<'_>, + visit: &mut dyn ::valuable::Visit, + ) { + if pointer.path().is_empty() { + visit.visit_value(self.as_value()); + return; + } + #visit_pointer + } } }; @@ -307,6 +358,7 @@ fn named_fields_static(name: &Ident, fields: &syn::Fields) -> TokenStream { fn allowed_lints() -> TokenStream { quote! { #[allow(non_upper_case_globals)] + #[allow(clippy::collapsible_match)] #[allow(clippy::unknown_clippy_lints)] #[allow(clippy::used_underscore_binding)] } From b84bc52ded95c75d022485c8de13c2f48aabea03 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Tue, 23 Nov 2021 21:20:46 +0900 Subject: [PATCH 07/10] fix doc links --- valuable/src/pointer.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/valuable/src/pointer.rs b/valuable/src/pointer.rs index 08998ca..2835b0d 100644 --- a/valuable/src/pointer.rs +++ b/valuable/src/pointer.rs @@ -3,7 +3,7 @@ //! A [`Pointer`] stores a path traversal to a particular value in a nested //! [`Valuable`] structure. For example, a [`Pointer`] might refer to the field `z` //! of the field `y` of the value `x`. [`Pointer`] paths can also include indices into -//! [`Listable`] and [`Tupleable`] values, in order to represent expressions like +//! [`Listable`] and [`Tuplable`] values, in order to represent expressions like //! `x.y[3].0`. //! //! # Examples @@ -43,6 +43,9 @@ //! visit_pointer!(value.y, visitor); // Struct2 { field: "b" } //! visit_pointer!(value.y.z, visitor); // "b" //! ``` +//! +//! [`Listable`]: crate::Listable +//! [`Tuplable`]: crate::Tuplable use crate::{NamedValues, Slice, Valuable, Value, Visit}; From 216fda549450413690fb197f5a280e6be54b390c Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Tue, 23 Nov 2021 21:29:41 +0900 Subject: [PATCH 08/10] fix ui test --- tests/tests/ui/not_valuable.stderr | 28 ++++++++++++++++++++++++++++ valuable-derive/src/derive.rs | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/tests/ui/not_valuable.stderr b/tests/tests/ui/not_valuable.stderr index 46982d2..5a50640 100644 --- a/tests/tests/ui/not_valuable.stderr +++ b/tests/tests/ui/not_valuable.stderr @@ -11,6 +11,20 @@ note: required by `as_value` | fn as_value(&self) -> Value<'_>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error[E0277]: the trait bound `S: Valuable` is not satisfied + --> tests/ui/not_valuable.rs:5:10 + | +5 | #[derive(Valuable)] + | ^^^^^^^^ the trait `Valuable` is not implemented for `S` + | + = note: required because of the requirements on the impl of `Valuable` for `Option` +note: required by `visit_pointer` + --> $WORKSPACE/valuable/src/valuable.rs + | + | fn visit_pointer(&self, pointer: Pointer<'_>, visit: &mut dyn Visit) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `Valuable` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `S: Valuable` is not satisfied --> tests/ui/not_valuable.rs:11:14 | @@ -24,6 +38,20 @@ note: required by `as_value` | fn as_value(&self) -> Value<'_>; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error[E0277]: the trait bound `S: Valuable` is not satisfied + --> tests/ui/not_valuable.rs:10:10 + | +10 | #[derive(Valuable)] + | ^^^^^^^^ the trait `Valuable` is not implemented for `S` + | + = note: required because of the requirements on the impl of `Valuable` for `Option` +note: required by `visit_pointer` + --> $WORKSPACE/valuable/src/valuable.rs + | + | fn visit_pointer(&self, pointer: Pointer<'_>, visit: &mut dyn Visit) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `Valuable` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `S: Valuable` is not satisfied --> tests/ui/not_valuable.rs:15:17 | diff --git a/valuable-derive/src/derive.rs b/valuable-derive/src/derive.rs index 74581dc..9148255 100644 --- a/valuable-derive/src/derive.rs +++ b/valuable-derive/src/derive.rs @@ -139,7 +139,7 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea visit: &mut dyn ::valuable::Visit, ) { if pointer.path().is_empty() { - visit.visit_value(self.as_value()); + visit.visit_value(::valuable::Valuable::as_value(self)); return; } #visit_pointer From 6e9371162909cc5408184b76b9ab87b70e599c5e Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 9 Mar 2022 00:25:41 +0900 Subject: [PATCH 09/10] remove needless #[non_exhaustive] --- valuable/src/pointer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/valuable/src/pointer.rs b/valuable/src/pointer.rs index 2835b0d..c254e36 100644 --- a/valuable/src/pointer.rs +++ b/valuable/src/pointer.rs @@ -51,7 +51,6 @@ use crate::{NamedValues, Slice, Valuable, Value, Visit}; /// A pointer to the value. #[derive(Debug, Clone, Copy)] -#[non_exhaustive] pub struct Pointer<'a> { path: &'a [Segment<'a>], } From 382674d8672c1fddc955c8e529edd65ee2134f9e Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Wed, 9 Mar 2022 00:31:41 +0900 Subject: [PATCH 10/10] Merge remote-tracking branch 'origin/master' into taiki-e/pointer --- .github/workflows/ci.yml | 43 +++++++++++++-- CHANGELOG.md | 3 ++ tests/tests/enumerable.rs | 12 ++--- tests/tests/structable.rs | 6 +-- tests/tests/ui/not_valuable.stderr | 71 +++++++++---------------- valuable-derive/Cargo.toml | 16 ++++-- valuable-derive/src/derive.rs | 8 +-- valuable-serde/CHANGELOG.md | 3 ++ valuable-serde/Cargo.toml | 13 +++++ valuable-serde/README.md | 25 +++++++++ valuable-serde/src/lib.rs | 15 +++++- valuable-serde/tests/test.rs | 4 +- valuable/Cargo.toml | 22 +++++++- valuable/no_atomic.rs | 1 - valuable/src/enumerable.rs | 24 ++++----- valuable/src/field.rs | 85 +++++++++++++++++++++++++++--- valuable/src/lib.rs | 24 ++++++++- valuable/src/structable.rs | 32 +++++------ 18 files changed, 298 insertions(+), 109 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 valuable-serde/CHANGELOG.md create mode 100644 valuable-serde/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5728b3..9264655 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ on: env: RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 + minrust: 1.51 jobs: test: @@ -24,6 +25,18 @@ jobs: - run: cargo test --all-features --workspace - run: cargo build --all-features --all-targets --workspace + minrust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ env.minrust }} + override: true + # - uses: Swatinem/rust-cache@v1 + - name: "check --workspace --all-features" + run: cargo check --workspace --all-features --all-targets + no-std: strategy: fail-fast: false @@ -59,9 +72,6 @@ jobs: - run: cargo hack build --workspace --feature-powerset # When this job failed, run ci/no_atomic.sh and commit result changes. - # TODO(taiki-e): Ideally, this should be automated using a bot that creates - # PR when failed, but there is no bandwidth to implement it - # right now... codegen: runs-on: ubuntu-latest steps: @@ -69,7 +79,30 @@ jobs: - name: Install Rust run: rustup update nightly && rustup default nightly - run: ci/no_atomic.sh - - run: git diff --exit-code + - run: git add -N . && git diff --exit-code + if: github.repository_owner != 'tokio-rs' || github.event_name != 'schedule' + - id: diff + run: | + git config user.name "Taiki Endo" + git config user.email "te316e89@gmail.com" + git add -N . + if ! git diff --exit-code; then + git add . + git commit -m "Update no_atomic.rs" + echo "::set-output name=success::false" + fi + if: github.repository_owner == 'tokio-rs' && github.event_name == 'schedule' + - uses: peter-evans/create-pull-request@v3 + with: + title: Update no_atomic.rs + body: | + Auto-generated by [create-pull-request][1] + [Please close and immediately reopen this pull request to run CI.][2] + + [1]: https://github.com/peter-evans/create-pull-request + [2]: https://github.com/peter-evans/create-pull-request/blob/HEAD/docs/concepts-guidelines.md#workarounds-to-trigger-further-workflow-runs + branch: update-no-atomic-rs + if: github.repository_owner == 'tokio-rs' && github.event_name == 'schedule' && steps.diff.outputs.success == 'false' fmt: runs-on: ubuntu-latest @@ -87,4 +120,4 @@ jobs: run: rustup update nightly && rustup default nightly - run: cargo doc --no-deps --all-features env: - RUSTDOCFLAGS: -Dwarnings + RUSTDOCFLAGS: -Dwarnings --cfg docsrs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f9b348b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.1.0 (2022-01-03) + +- Initial release \ No newline at end of file diff --git a/tests/tests/enumerable.rs b/tests/tests/enumerable.rs index 7ccfd0f..66d54cd 100644 --- a/tests/tests/enumerable.rs +++ b/tests/tests/enumerable.rs @@ -11,8 +11,8 @@ fn test_manual_static_impl() { static ENUM_STRUCT_FIELDS: &[NamedField<'static>] = &[NamedField::new("x")]; static ENUM_VARIANTS: &[VariantDef<'static>] = &[ VariantDef::new("Struct", Fields::Named(ENUM_STRUCT_FIELDS)), - VariantDef::new("Tuple", Fields::Unnamed), - VariantDef::new("Unit", Fields::Unnamed), + VariantDef::new("Tuple", Fields::Unnamed(1)), + VariantDef::new("Unit", Fields::Unnamed(0)), ]; impl Enumerable for Enum { @@ -80,7 +80,7 @@ fn test_manual_dyn_impl() { } fn variant(&self) -> Variant<'_> { - Variant::Dynamic(VariantDef::new("MyVariant", Fields::Unnamed)) + Variant::Dynamic(VariantDef::new("MyVariant", Fields::Unnamed(0))) } } @@ -109,17 +109,17 @@ fn test_variant_named_field() { #[test] fn test_variant_unnamed_field() { - let variant = VariantDef::new("Hello", Fields::Unnamed); + let variant = VariantDef::new("Hello", Fields::Unnamed(1)); assert_eq!(variant.name(), "Hello"); - assert!(matches!(variant.fields(), Fields::Unnamed)); + assert!(matches!(variant.fields(), Fields::Unnamed(1))); } #[test] fn test_enum_def() { let fields = [NamedField::new("foo")]; let a = VariantDef::new("A", Fields::Named(&fields[..])); - let b = VariantDef::new("B", Fields::Unnamed); + let b = VariantDef::new("B", Fields::Unnamed(1)); let variants = [a, b]; let def = EnumDef::new_dynamic("Foo", &variants); diff --git a/tests/tests/structable.rs b/tests/tests/structable.rs index a59bfe8..4e005cb 100644 --- a/tests/tests/structable.rs +++ b/tests/tests/structable.rs @@ -148,17 +148,17 @@ fn test_named_field() { #[test] fn test_fields_unnamed() { - let fields = Fields::Unnamed; + let fields = Fields::Unnamed(1); assert!(fields.is_unnamed()); assert!(!fields.is_named()); } #[test] fn test_struct_def() { - let def = StructDef::new_static("hello", Fields::Unnamed); + let def = StructDef::new_static("hello", Fields::Unnamed(1)); assert_eq!(def.name(), "hello"); - assert!(matches!(def.fields(), Fields::Unnamed)); + assert!(matches!(def.fields(), Fields::Unnamed(1))); assert!(!def.is_dynamic()); } diff --git a/tests/tests/ui/not_valuable.stderr b/tests/tests/ui/not_valuable.stderr index 5a50640..66fdb1e 100644 --- a/tests/tests/ui/not_valuable.stderr +++ b/tests/tests/ui/not_valuable.stderr @@ -1,42 +1,32 @@ error[E0277]: the trait bound `S: Valuable` is not satisfied - --> tests/ui/not_valuable.rs:7:8 - | -7 | f: Option, - | ^^^^^^^^^ the trait `Valuable` is not implemented for `S` - | - = note: required because of the requirements on the impl of `Valuable` for `Option` -note: required by `as_value` - --> $WORKSPACE/valuable/src/valuable.rs - | - | fn as_value(&self) -> Value<'_>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui/not_valuable.rs:7:8 + | +5 | #[derive(Valuable)] + | -------- required by a bound introduced by this call +6 | struct Struct { +7 | f: Option, + | ^^^^^^^^^ the trait `Valuable` is not implemented for `S` + | + = note: required because of the requirements on the impl of `Valuable` for `Option` error[E0277]: the trait bound `S: Valuable` is not satisfied - --> tests/ui/not_valuable.rs:5:10 - | -5 | #[derive(Valuable)] - | ^^^^^^^^ the trait `Valuable` is not implemented for `S` - | - = note: required because of the requirements on the impl of `Valuable` for `Option` -note: required by `visit_pointer` - --> $WORKSPACE/valuable/src/valuable.rs - | - | fn visit_pointer(&self, pointer: Pointer<'_>, visit: &mut dyn Visit) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the derive macro `Valuable` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/ui/not_valuable.rs:5:10 + | +5 | #[derive(Valuable)] + | ^^^^^^^^ the trait `Valuable` is not implemented for `S` + | + = note: required because of the requirements on the impl of `Valuable` for `Option` + = note: this error originates in the derive macro `Valuable` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `S: Valuable` is not satisfied --> tests/ui/not_valuable.rs:11:14 | +10 | #[derive(Valuable)] + | -------- required by a bound introduced by this call 11 | struct Tuple(Option); | ^^^^^^^^^ the trait `Valuable` is not implemented for `S` | = note: required because of the requirements on the impl of `Valuable` for `Option` -note: required by `as_value` - --> $WORKSPACE/valuable/src/valuable.rs - | - | fn as_value(&self) -> Value<'_>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `S: Valuable` is not satisfied --> tests/ui/not_valuable.rs:10:10 @@ -45,39 +35,30 @@ error[E0277]: the trait bound `S: Valuable` is not satisfied | ^^^^^^^^ the trait `Valuable` is not implemented for `S` | = note: required because of the requirements on the impl of `Valuable` for `Option` -note: required by `visit_pointer` - --> $WORKSPACE/valuable/src/valuable.rs - | - | fn visit_pointer(&self, pointer: Pointer<'_>, visit: &mut dyn Visit) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `Valuable` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `S: Valuable` is not satisfied --> tests/ui/not_valuable.rs:15:17 | +13 | #[derive(Valuable)] + | -------- required by a bound introduced by this call +14 | enum Enum { 15 | Struct { f: Option }, | ^^^^^^^^^ the trait `Valuable` is not implemented for `S` | = note: required because of the requirements on the impl of `Valuable` for `Option` - = note: 1 redundant requirements hidden + = note: 1 redundant requirement hidden = note: required because of the requirements on the impl of `Valuable` for `&Option` -note: required by `as_value` - --> $WORKSPACE/valuable/src/valuable.rs - | - | fn as_value(&self) -> Value<'_>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `S: Valuable` is not satisfied --> tests/ui/not_valuable.rs:16:11 | +13 | #[derive(Valuable)] + | -------- required by a bound introduced by this call +... 16 | Tuple(Option), | ^^^^^^^^^ the trait `Valuable` is not implemented for `S` | = note: required because of the requirements on the impl of `Valuable` for `Option` - = note: 1 redundant requirements hidden + = note: 1 redundant requirement hidden = note: required because of the requirements on the impl of `Valuable` for `&Option` -note: required by `as_value` - --> $WORKSPACE/valuable/src/valuable.rs - | - | fn as_value(&self) -> Value<'_>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/valuable-derive/Cargo.toml b/valuable-derive/Cargo.toml index 0bf5694..40384e2 100644 --- a/valuable-derive/Cargo.toml +++ b/valuable-derive/Cargo.toml @@ -1,11 +1,21 @@ [package] name = "valuable-derive" version = "0.1.0" -authors = ["Carl Lerche ", "Taiki Endo "] edition = "2018" license = "MIT" -description = "Values" - +rust-version = "1.51.0" +description = "Macros for the `valuable` crate." +repository = "https://github.com/tokio-rs/valuable" +categories = [ + "development-tools::debugging", + "encoding", +] +keywords = [ + "valuable", + "serialization", + "debugging", + "no_std", +] [lib] proc-macro = true diff --git a/valuable-derive/src/derive.rs b/valuable-derive/src/derive.rs index 9148255..a1294ff 100644 --- a/valuable-derive/src/derive.rs +++ b/valuable-derive/src/derive.rs @@ -69,10 +69,11 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea }; } syn::Fields::Unnamed(_) | syn::Fields::Unit => { + let len = data.fields.len(); struct_def = quote! { ::valuable::StructDef::new_static( #name_literal, - ::valuable::Fields::Unnamed, + ::valuable::Fields::Unnamed(#len), ) }; @@ -223,10 +224,11 @@ fn derive_enum(input: &syn::DeriveInput, data: &syn::DataEnum) -> TokenStream { syn::Fields::Unnamed(_) => { let variant_name = &variant.ident; let variant_name_literal = variant_name.to_string(); + let len = variant.fields.len(); variant_defs.push(quote! { ::valuable::VariantDef::new( #variant_name_literal, - ::valuable::Fields::Unnamed, + ::valuable::Fields::Unnamed(#len), ), }); @@ -266,7 +268,7 @@ fn derive_enum(input: &syn::DeriveInput, data: &syn::DataEnum) -> TokenStream { variant_defs.push(quote! { ::valuable::VariantDef::new( #variant_name_literal, - ::valuable::Fields::Unnamed, + ::valuable::Fields::Unnamed(0), ), }); diff --git a/valuable-serde/CHANGELOG.md b/valuable-serde/CHANGELOG.md new file mode 100644 index 0000000..e81cb32 --- /dev/null +++ b/valuable-serde/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.1.0 (2022-01-26) + +- Initial release \ No newline at end of file diff --git a/valuable-serde/Cargo.toml b/valuable-serde/Cargo.toml index 34d7765..1b020f5 100644 --- a/valuable-serde/Cargo.toml +++ b/valuable-serde/Cargo.toml @@ -5,6 +5,19 @@ authors = ["Taiki Endo "] edition = "2018" license = "MIT" description = "`serde::Serialize` implementation for `Valuable` types." +rust-version = "1.51.0" +readme = "README.md" +repository = "https://github.com/tokio-rs/valuable" +categories = [ + "development-tools::debugging", + "encoding", +] +keywords = [ + "valuable", + "serialization", + "serde", + "no_std", +] [features] default = ["std"] diff --git a/valuable-serde/README.md b/valuable-serde/README.md new file mode 100644 index 0000000..2d41c14 --- /dev/null +++ b/valuable-serde/README.md @@ -0,0 +1,25 @@ +# valuable-serde + +[Valuable][`valuable`] provides object-safe value inspection. Use cases include passing +structured data to trait objects and object-safe serialization. + +This crate provides a bridge between [`valuable`] and the [`serde`] +serialization ecosystem. Using [`valuable_serde::Serializable`] allows any type +that implements `valuable`'s [`Valuable`] trait to be serialized by any +[`serde::ser::Serializer`]. + +[`valuable`]: https://crates.io/crates/valuable +[`serde`]: https://crates.io/crates/serde +[`valuable_serde::Serializable`]: https://docs.rs/valuable-serde/latest/valuable_serde/struct.Serializable.html +[`Valuable`]: https://docs.rs/valuable/latest/valuable/trait.Valuable.html +[`serde::ser::Serializer`]: https://docs.rs/serde/latest/serde/ser/trait.Serializer.html + +## License + +This project is licensed under the [MIT license](LICENSE). + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Valuable by you, shall be licensed as MIT, without any additional +terms or conditions. \ No newline at end of file diff --git a/valuable-serde/src/lib.rs b/valuable-serde/src/lib.rs index deebae9..73d53d4 100644 --- a/valuable-serde/src/lib.rs +++ b/valuable-serde/src/lib.rs @@ -8,6 +8,17 @@ //! [`serde::Serialize`] implementation for [`Valuable`] types. //! +//! [Valuable][`valuable`] provides object-safe value inspection. Use cases +//! include passing structured data to trait objects and object-safe serialization. +//! +//! This crate provides a bridge between [`valuable`] and the [`serde`] +//! serialization ecosystem. Using [`Serializable`] allows any type +//! that implements `valuable`'s [`Valuable`] trait to be serialized by any +//! [`serde::ser::Serializer`]. +//! +//! [`valuable`]: https://docs.rs/valuable +//! [`serde`]: https://docs.rs/serde +//! //! # Examples //! //! ``` @@ -375,7 +386,7 @@ impl Visit for VisitStaticStruct { let (name, serializer) = match mem::replace(self, Self::Tmp) { Self::Start { name, - fields: Fields::Unnamed, + fields: Fields::Unnamed(_), serializer, } => (name, serializer), mut res @ Self::End(..) => { @@ -470,7 +481,7 @@ impl Visit for VisitStaticEnum { }; let fields = match variant.fields() { Fields::Named(fields) => fields, - Fields::Unnamed => unreachable!(), + Fields::Unnamed(_) => unreachable!(), }; for (i, (_, v)) in named_values.iter().enumerate() { if let Err(e) = ser.serialize_field(fields[i].name(), &Serializable(v)) { diff --git a/valuable-serde/tests/test.rs b/valuable-serde/tests/test.rs index 332ad32..3c9433b 100644 --- a/valuable-serde/tests/test.rs +++ b/valuable-serde/tests/test.rs @@ -424,7 +424,7 @@ fn test_dyn_struct() { impl Structable for Unnamed { fn definition(&self) -> StructDef<'_> { - StructDef::new_dynamic("Unnamed", Fields::Unnamed) + StructDef::new_dynamic("Unnamed", Fields::Unnamed(2)) } } @@ -491,7 +491,7 @@ fn test_dyn_enum() { fn variant(&self) -> Variant<'_> { match self { Self::Named => Variant::Dynamic(VariantDef::new("Named", Fields::Named(&[]))), - Self::Unnamed => Variant::Dynamic(VariantDef::new("Named", Fields::Unnamed)), + Self::Unnamed => Variant::Dynamic(VariantDef::new("Named", Fields::Unnamed(2))), } } } diff --git a/valuable/Cargo.toml b/valuable/Cargo.toml index 4d7818a..621a044 100644 --- a/valuable/Cargo.toml +++ b/valuable/Cargo.toml @@ -1,10 +1,24 @@ [package] name = "valuable" version = "0.1.0" -authors = ["Carl Lerche "] edition = "2018" license = "MIT" -description = "Values" +rust-version = "1.51.0" +readme = "../README.md" +repository = "https://github.com/tokio-rs/valuable" +description = """ +Object-safe value inspection, used to pass un-typed structured data across trait-object boundaries. +""" +categories = [ + "development-tools::debugging", + "encoding", +] +keywords = [ + "valuable", + "serialization", + "debugging", + "no_std", +] [features] default = ["std"] @@ -27,3 +41,7 @@ criterion = "0.3" [[bench]] name = "structable" harness = false + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/valuable/no_atomic.rs b/valuable/no_atomic.rs index 3c25155..6594d62 100644 --- a/valuable/no_atomic.rs +++ b/valuable/no_atomic.rs @@ -43,7 +43,6 @@ const NO_ATOMIC_64: &[&str] = &[ "riscv32gc-unknown-linux-gnu", "riscv32gc-unknown-linux-musl", "riscv32imac-unknown-none-elf", - "riscv32imc-esp-espidf", "thumbv7em-none-eabi", "thumbv7em-none-eabihf", "thumbv7m-none-eabi", diff --git a/valuable/src/enumerable.rs b/valuable/src/enumerable.rs index 73393a7..3161f19 100644 --- a/valuable/src/enumerable.rs +++ b/valuable/src/enumerable.rs @@ -218,7 +218,7 @@ pub enum EnumDef<'a> { /// } /// /// fn variant(&self) -> Variant<'_> { - /// Variant::Dynamic(VariantDef::new(&self.variant, Fields::Unnamed)) + /// Variant::Dynamic(VariantDef::new(&self.variant, Fields::Unnamed(0))) /// } /// } /// ``` @@ -271,7 +271,7 @@ impl<'a> EnumDef<'a> { /// use valuable::{EnumDef, Fields, VariantDef}; /// /// static VARIANTS: &[VariantDef<'static>] = &[ - /// VariantDef::new("Bar", Fields::Unnamed), + /// VariantDef::new("Bar", Fields::Unnamed(1)), /// ]; /// /// let def = EnumDef::new_static( "Foo", VARIANTS); @@ -294,7 +294,7 @@ impl<'a> EnumDef<'a> { /// /// let def = EnumDef::new_dynamic( /// "Foo", - /// &[VariantDef::new("Bar", Fields::Unnamed)] + /// &[VariantDef::new("Bar", Fields::Unnamed(1))] /// ); /// ``` pub const fn new_dynamic(name: &'a str, variants: &'a [VariantDef<'a>]) -> EnumDef<'a> { @@ -421,7 +421,7 @@ impl<'a> VariantDef<'a> { /// ``` /// use valuable::{Fields, VariantDef}; /// - /// let def = VariantDef::new("Foo", Fields::Unnamed); + /// let def = VariantDef::new("Foo", Fields::Unnamed(2)); /// ``` pub const fn new(name: &'a str, fields: Fields<'a>) -> VariantDef<'a> { VariantDef { name, fields } @@ -434,7 +434,7 @@ impl<'a> VariantDef<'a> { /// ``` /// use valuable::{Fields, VariantDef}; /// - /// let def = VariantDef::new("Foo", Fields::Unnamed); + /// let def = VariantDef::new("Foo", Fields::Unnamed(2)); /// assert_eq!("Foo", def.name()); /// ``` pub fn name(&self) -> &str { @@ -448,8 +448,8 @@ impl<'a> VariantDef<'a> { /// ``` /// use valuable::{Fields, VariantDef}; /// - /// let def = VariantDef::new("Foo", Fields::Unnamed); - /// assert!(matches!(def.fields(), Fields::Unnamed)); + /// let def = VariantDef::new("Foo", Fields::Unnamed(3)); + /// assert!(matches!(def.fields(), Fields::Unnamed(_))); /// ``` pub fn fields(&self) -> &Fields<'_> { &self.fields @@ -465,7 +465,7 @@ impl Variant<'_> { /// use valuable::{Fields, Variant, VariantDef}; /// /// static VARIANT: &VariantDef<'static> = &VariantDef::new( - /// "Foo", Fields::Unnamed); + /// "Foo", Fields::Unnamed(2)); /// /// let variant = Variant::Static(VARIANT); /// assert_eq!("Foo", variant.name()); @@ -507,7 +507,7 @@ impl Variant<'_> { /// use valuable::{Fields, Variant, VariantDef}; /// /// static VARIANT: &VariantDef<'static> = &VariantDef::new( - /// "Foo", Fields::Unnamed); + /// "Foo", Fields::Unnamed(1)); /// /// let variant = Variant::Static(VARIANT); /// assert!(!variant.is_named_fields()); @@ -538,7 +538,7 @@ impl Variant<'_> { /// use valuable::{Fields, Variant, VariantDef}; /// /// static VARIANT: &VariantDef<'static> = &VariantDef::new( - /// "Foo", Fields::Unnamed); + /// "Foo", Fields::Unnamed(1)); /// /// let variant = Variant::Static(VARIANT); /// assert!(variant.is_unnamed_fields()); @@ -643,8 +643,8 @@ deref! { } static RESULT_VARIANTS: &[VariantDef<'static>] = &[ - VariantDef::new("Ok", Fields::Unnamed), - VariantDef::new("Err", Fields::Unnamed), + VariantDef::new("Ok", Fields::Unnamed(1)), + VariantDef::new("Err", Fields::Unnamed(1)), ]; impl Enumerable for Result diff --git a/valuable/src/field.rs b/valuable/src/field.rs index 915cf75..faa00f4 100644 --- a/valuable/src/field.rs +++ b/valuable/src/field.rs @@ -5,7 +5,9 @@ pub enum Fields<'a> { Named(&'a [NamedField<'a>]), /// Unnamed (positional) fields or unit - Unnamed, + /// + /// The `usize` value represents the number of fields. + Unnamed(usize), } /// A named field @@ -31,10 +33,10 @@ impl Fields<'_> { /// ``` /// use valuable::Fields; /// - /// let fields = Fields::Unnamed; + /// let fields = Fields::Unnamed(2); /// assert!(!fields.is_named()); /// ``` - pub fn is_named(&self) -> bool { + pub const fn is_named(&self) -> bool { matches!(self, Fields::Named(..)) } @@ -56,11 +58,80 @@ impl Fields<'_> { /// ``` /// use valuable::Fields; /// - /// let fields = Fields::Unnamed; + /// let fields = Fields::Unnamed(3); /// assert!(fields.is_unnamed()); /// ``` - pub fn is_unnamed(&self) -> bool { - matches!(self, Fields::Unnamed) + pub const fn is_unnamed(&self) -> bool { + matches!(self, Fields::Unnamed(_)) + } + + /// Returns the number of fields. + /// + /// # Examples + /// + /// Named fields + /// + /// ``` + /// use valuable::{Fields, NamedField}; + /// + /// let fields = &[ + /// NamedField::new("alice"), + /// NamedField::new("bob"), + /// ]; + /// let fields = Fields::Named(fields); + /// + /// assert_eq!(fields.len(), 2); + /// ``` + /// + /// Unnamed fields + /// + /// ``` + /// use valuable::Fields; + /// + /// let fields = Fields::Unnamed(2); + /// assert_eq!(fields.len(), 2); + /// ``` + pub const fn len(&self) -> usize { + match self { + Self::Named(names) => names.len(), + Self::Unnamed(len) => *len, + } + } + + /// Returns `true` if this set of fields defines no fields. + /// + /// # Examples + /// + /// Named fields + /// + /// ``` + /// use valuable::{Fields, NamedField}; + /// + /// let fields = &[ + /// NamedField::new("alice"), + /// NamedField::new("bob"), + /// ]; + /// let non_empty = Fields::Named(fields); + /// + /// let empty = Fields::Named(&[]); + /// + /// assert!(!non_empty.is_empty()); + /// assert!(empty.is_empty()); + /// ``` + /// + /// Unnamed fields + /// + /// ``` + /// use valuable::Fields; + /// + /// let non_empty = Fields::Unnamed(2); + /// let empty = Fields::Unnamed(0); + /// + /// assert!(!non_empty.is_empty()); + /// assert!(empty.is_empty()); + /// ``` + pub const fn is_empty(&self) -> bool { + self.len() == 0 } } @@ -89,7 +160,7 @@ impl<'a> NamedField<'a> { /// let field = NamedField::new("hello"); /// assert_eq!("hello", field.name()); /// ``` - pub fn name(&self) -> &str { + pub const fn name(&self) -> &str { self.0 } } diff --git a/valuable/src/lib.rs b/valuable/src/lib.rs index 18c88a1..74c9e4f 100644 --- a/valuable/src/lib.rs +++ b/valuable/src/lib.rs @@ -94,8 +94,28 @@ //! let hello_world = HelloWorld { message: Message::HelloWorld }; //! hello_world.visit(&mut Print); //! ``` - +//! +//! # Related Crates +//! +//! - [`valuable-serde`] provides a bridge between `valuable` and the [`serde`] +//! serialization ecosystem. Using [`valuable_serde::Serializable`] allows any +//! type that implements [`Valuable`] to be serialized by any +//! [`serde::ser::Serializer`]. +//! +//! [`valuable-serde`]: https://crates.io/crates/valuable-serde +//! [`serde`]: https://crates.io/crates/serde +//! [`valuable_serde::Serializable`]: https://docs.rs/valuable-serde/latest/valuable_serde/struct.Serializable.html +//! [`serde::ser::Serializer`]: https://docs.rs/serde/latest/serde/ser/trait.Serializer.html #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg, doc_cfg_hide))] +#![cfg_attr( + docsrs, + doc(cfg_hide( + not(valuable_no_atomic_cas), + not(valuable_no_atomic), + not(valuable_no_atomic_64) + )) +)] #[cfg(feature = "alloc")] extern crate alloc; @@ -136,4 +156,4 @@ mod visit; pub use visit::{visit, Visit}; #[cfg(feature = "derive")] -pub use valuable_derive::*; +pub use valuable_derive::{visit_pointer, Valuable}; diff --git a/valuable/src/structable.rs b/valuable/src/structable.rs index e267bf8..611a7e5 100644 --- a/valuable/src/structable.rs +++ b/valuable/src/structable.rs @@ -337,7 +337,7 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_static("Foo", Fields::Unnamed); + /// let def = StructDef::new_static("Foo", Fields::Unnamed(2)); /// ``` pub const fn new_static(name: &'static str, fields: Fields<'static>) -> StructDef<'a> { StructDef::Static { name, fields } @@ -352,7 +352,7 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed); + /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed(3)); /// ``` pub const fn new_dynamic(name: &'a str, fields: Fields<'a>) -> StructDef<'a> { StructDef::Dynamic { name, fields } @@ -367,7 +367,7 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_static("Foo", Fields::Unnamed); + /// let def = StructDef::new_static("Foo", Fields::Unnamed(1)); /// assert_eq!("Foo", def.name()); /// ``` /// @@ -376,10 +376,10 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed); + /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed(2)); /// assert_eq!("Foo", def.name()); /// ``` - pub fn name(&self) -> &'a str { + pub const fn name(&self) -> &'a str { match self { StructDef::Static { name, .. } => name, StructDef::Dynamic { name, .. } => name, @@ -395,8 +395,8 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_static("Foo", Fields::Unnamed); - /// assert!(matches!(def.fields(), Fields::Unnamed)); + /// let def = StructDef::new_static("Foo", Fields::Unnamed(3)); + /// assert!(matches!(def.fields(), Fields::Unnamed(_))); /// ``` /// /// With a dynamic struct @@ -404,10 +404,10 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed); - /// assert!(matches!(def.fields(), Fields::Unnamed)); + /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed(1)); + /// assert!(matches!(def.fields(), Fields::Unnamed(_))); /// ``` - pub fn fields(&self) -> &Fields<'a> { + pub const fn fields(&self) -> &Fields<'a> { match self { StructDef::Static { fields, .. } => fields, StructDef::Dynamic { fields, .. } => fields, @@ -423,7 +423,7 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_static("Foo", Fields::Unnamed); + /// let def = StructDef::new_static("Foo", Fields::Unnamed(2)); /// assert!(def.is_static()); /// ``` /// @@ -432,10 +432,10 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed); + /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed(4)); /// assert!(!def.is_static()); /// ``` - pub fn is_static(&self) -> bool { + pub const fn is_static(&self) -> bool { matches!(self, StructDef::Static { .. }) } @@ -448,7 +448,7 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_static("Foo", Fields::Unnamed); + /// let def = StructDef::new_static("Foo", Fields::Unnamed(1)); /// assert!(!def.is_dynamic()); /// ``` /// @@ -457,10 +457,10 @@ impl<'a> StructDef<'a> { /// ``` /// use valuable::{StructDef, Fields}; /// - /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed); + /// let def = StructDef::new_dynamic("Foo", Fields::Unnamed(1)); /// assert!(def.is_dynamic()); /// ``` - pub fn is_dynamic(&self) -> bool { + pub const fn is_dynamic(&self) -> bool { matches!(self, StructDef::Dynamic { .. }) } }