From 255ae393c955fdeae8ec3b464f36b56efc3cfb1b Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 30 Aug 2022 18:32:37 +0000 Subject: [PATCH] Add `Structable::get` for accessing fields-by-name. --- tests/src/hello_world.rs | 9 +++++++++ tests/tests/structable.rs | 24 ++++++++++++++++++++++++ valuable-derive/src/expand.rs | 29 +++++++++++++++++++++++++++++ valuable-serde/tests/test.rs | 16 ++++++++++++++++ valuable/examples/hello_world.rs | 15 +++++++++++++++ valuable/examples/indexing.rs | 12 ++++++++++++ valuable/examples/print.rs | 2 +- valuable/src/field.rs | 10 ++++++++++ valuable/src/lib.rs | 2 +- valuable/src/structable.rs | 29 +++++++++++++++++++++++++++-- 10 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 valuable/examples/indexing.rs diff --git a/tests/src/hello_world.rs b/tests/src/hello_world.rs index f14ea77..d1d3615 100644 --- a/tests/src/hello_world.rs +++ b/tests/src/hello_world.rs @@ -24,4 +24,13 @@ impl Structable for HelloWorld { fn definition(&self) -> StructDef<'_> { StructDef::new_static("HelloWorld", Fields::Named(HELLO_WORLD_FIELDS)) } + + fn get(&self, field: Field<'_>) -> Option> { + if let Field::Named(field) = field { + if field.name() == HELLO_WORLD_FIELDS[0].name() { + return Some(Value::U32(self.id)); + } + } + unimplemented!() + } } diff --git a/tests/tests/structable.rs b/tests/tests/structable.rs index 4e005cb..5a63fc4 100644 --- a/tests/tests/structable.rs +++ b/tests/tests/structable.rs @@ -41,6 +41,15 @@ fn test_manual_static_impl() { fn definition(&self) -> StructDef<'_> { StructDef::new_static("MyStruct", Fields::Named(MY_STRUCT_FIELDS)) } + + fn get(&self, field: Field<'_>) -> Option> { + match field { + Field::Named(field) if field.name() == "num" => Some(Value::U32(self.num)), + Field::Named(field) if field.name() == "list" => Some(Value::Listable(&self.list)), + Field::Named(field) if field.name() == "sub" => Some(Value::Structable(&self.sub)), + _ => None, + } + } } impl Valuable for SubStruct { @@ -60,6 +69,13 @@ fn test_manual_static_impl() { fn definition(&self) -> StructDef<'_> { StructDef::new_static("SubStruct", Fields::Named(SUB_STRUCT_FIELDS)) } + + fn get(&self, field: Field<'_>) -> Option> { + match field { + Field::Named(field) if field.name() == "sub" => Some(Value::String(&self.message)), + _ => None, + } + } } let my_struct = MyStruct { @@ -118,6 +134,14 @@ fn test_manual_dyn_impl() { fn definition(&self) -> StructDef<'_> { StructDef::new_dynamic("MyStruct", Fields::Named(&[])) } + + fn get(&self, field: Field) -> Option> { + match field { + Field::Named(field) if field.name() == "foo" => Some(Value::U32(1)), + Field::Named(field) if field.name() == "bar" => Some(Value::String("two")), + _ => None, + } + } } let my_struct = MyStruct; diff --git a/valuable-derive/src/expand.rs b/valuable-derive/src/expand.rs index 2832414..5152ff3 100644 --- a/valuable-derive/src/expand.rs +++ b/valuable-derive/src/expand.rs @@ -17,6 +17,7 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea let name_literal = name.to_string(); let visit_fields; let struct_def; + let get_branches: TokenStream; let mut named_fields_statics = None; match &data.fields { @@ -33,6 +34,19 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea ) }; + get_branches = + data.fields.iter().map(|field| { + let field_name_ident = field.ident.as_ref(); + let field_name_str = field_name_ident.unwrap().to_string(); + quote! { + ::valuable::Field::Named(field) if field.name() == #field_name_str => Some(::valuable::Valuable::as_value(&self.#field_name_ident)), + } + }).collect(); + + quote! { + ::valuable::Field::Named(field) if field.name() == "" + }; + let fields = data.fields.iter().map(|field| { let f = field.ident.as_ref(); let tokens = quote! { @@ -58,6 +72,14 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea ) }; + get_branches = + data.fields.iter().enumerate().map(|(i, _)| { + let i = syn::Index::from(i); + quote! { + ::valuable::Field::Unnamed(f) if f == #i => Some(::valuable::Valuable::as_value(&self.#i)), + } + }).collect(); + let indices = data.fields.iter().enumerate().map(|(i, field)| { let index = syn::Index::from(i); let tokens = quote! { @@ -82,6 +104,13 @@ fn derive_struct(input: &syn::DeriveInput, data: &syn::DataStruct) -> TokenStrea fn definition(&self) -> ::valuable::StructDef<'_> { #struct_def } + + fn get(&self, field: ::valuable::Field<'_>) -> Option<::valuable::Value<'_>> { + match field { + #get_branches + _ => None, + } + } } }; diff --git a/valuable-serde/tests/test.rs b/valuable-serde/tests/test.rs index 4e73103..7217c47 100644 --- a/valuable-serde/tests/test.rs +++ b/valuable-serde/tests/test.rs @@ -407,6 +407,14 @@ fn test_dyn_struct() { fn definition(&self) -> StructDef<'_> { StructDef::new_dynamic("Named", Fields::Named(&[])) } + + fn get(&self, field: Field<'_>) -> Option> { + match field { + Field::Unnamed(0) => Some(Value::U32(1)), + Field::Unnamed(1) => Some(Value::I32(-1)), + _ => None, + } + } } struct Unnamed; @@ -426,6 +434,14 @@ fn test_dyn_struct() { fn definition(&self) -> StructDef<'_> { StructDef::new_dynamic("Unnamed", Fields::Unnamed(2)) } + + fn get(&self, field: Field<'_>) -> Option> { + match field { + Field::Unnamed(0) => Some(Value::U32(1)), + Field::Unnamed(1) => Some(Value::I32(-1)), + _ => None, + } + } } assert_ser_tokens( diff --git a/valuable/examples/hello_world.rs b/valuable/examples/hello_world.rs index 7c3d173..cb0fb37 100644 --- a/valuable/examples/hello_world.rs +++ b/valuable/examples/hello_world.rs @@ -16,6 +16,14 @@ impl Structable for HelloWorld { fn definition(&self) -> StructDef<'_> { StructDef::new_static("HelloWorld", Fields::Named(HELLO_WORLD_FIELDS)) } + + fn get(&self, field: Field<'_>) -> Option> { + match field { + Field::Named(field) if field.name() == "hello" => Some(Value::String(self.hello)), + Field::Named(field) if field.name() == "world" => Some(Value::Structable(&self.world)), + _ => None, + } + } } impl Valuable for HelloWorld { @@ -50,6 +58,13 @@ impl Structable for World { fn definition(&self) -> StructDef<'_> { StructDef::new_static("World", Fields::Named(WORLD_FIELDS)) } + + fn get(&self, field: Field<'_>) -> Option> { + match field { + Field::Named(field) if field.name() == "answer" => Some(Value::Usize(self.answer)), + _ => None, + } + } } fn main() { diff --git a/valuable/examples/indexing.rs b/valuable/examples/indexing.rs new file mode 100644 index 0000000..8ceee7a --- /dev/null +++ b/valuable/examples/indexing.rs @@ -0,0 +1,12 @@ +fn main() { + use valuable::{NamedField, NamedValues, Value}; + + let fields = [NamedField::new("foo"), NamedField::new("bar")]; + let values = [Value::U32(123), Value::U32(456)]; + + let named_values = NamedValues::new(&fields, &values); + + let field = &fields[0]; + + assert_eq!(named_values.get(field).and_then(Value::as_u32), Some(123)); +} diff --git a/valuable/examples/print.rs b/valuable/examples/print.rs index b6998d3..ae6a910 100644 --- a/valuable/examples/print.rs +++ b/valuable/examples/print.rs @@ -50,7 +50,7 @@ impl Visit for Print { } fn visit_named_fields(&mut self, named_values: &NamedValues<'_>) { - for (field, value) in named_values { + for (field, value) in named_values.into_iter() { print!("{}- {}: ", self.0, field.name()); value.visit(self); } diff --git a/valuable/src/field.rs b/valuable/src/field.rs index faa00f4..01da8ff 100644 --- a/valuable/src/field.rs +++ b/valuable/src/field.rs @@ -10,6 +10,16 @@ pub enum Fields<'a> { Unnamed(usize), } +/// A field, either named or positional, in a `Structable`. +#[derive(Debug)] +pub enum Field<'a> { + /// Named field. + Named(NamedField<'a>), + + /// Unnamed (positional) field. + Unnamed(usize), +} + /// A named field #[derive(Debug, Clone, Copy)] pub struct NamedField<'a>(&'a str); diff --git a/valuable/src/lib.rs b/valuable/src/lib.rs index 255ee5a..50fb8e0 100644 --- a/valuable/src/lib.rs +++ b/valuable/src/lib.rs @@ -124,7 +124,7 @@ mod enumerable; pub use enumerable::{EnumDef, Enumerable, Variant, VariantDef}; mod field; -pub use field::{Fields, NamedField}; +pub use field::{Field, Fields, NamedField}; mod listable; pub use listable::Listable; diff --git a/valuable/src/structable.rs b/valuable/src/structable.rs index 611a7e5..31c9e65 100644 --- a/valuable/src/structable.rs +++ b/valuable/src/structable.rs @@ -109,6 +109,9 @@ pub trait Structable: Valuable { /// /// assert_eq!("MyStruct", my_struct.definition().name()); fn definition(&self) -> StructDef<'_>; + + /// Returns the value of the given field, if any. + fn get(&self, field: Field<'_>) -> Option>; } /// A struct's name, fields, and other struct-level information. @@ -178,7 +181,7 @@ pub enum StructDef<'a> { /// The struct stores field values in a `HashMap`. /// /// ``` - /// use valuable::{Fields, NamedField, NamedValues, Structable, StructDef, Value, Valuable, Visit}; + /// use valuable::{Field, Fields, NamedField, NamedValues, Structable, StructDef, Value, Valuable, Visit}; /// use std::collections::HashMap; /// /// /// A dynamic struct @@ -210,13 +213,20 @@ pub enum StructDef<'a> { /// fn definition(&self) -> StructDef<'_> { /// StructDef::new_dynamic(&self.name, Fields::Named(&[])) /// } + /// + /// fn get(&self, field: Field<'_>) -> Option> { + /// match field { + /// Field::Named(field) => self.values.get(field.name()).map(|v| v.as_value()), + /// _ => None, + /// } + /// } /// } /// ``` /// /// Some fields are known statically. /// /// ``` - /// use valuable::{Fields, NamedField, NamedValues, Structable, StructDef, Value, Valuable, Visit}; + /// use valuable::{Field, Fields, NamedField, NamedValues, Structable, StructDef, Value, Valuable, Visit}; /// use std::collections::HashMap; /// /// struct HalfStatic { @@ -259,6 +269,17 @@ pub enum StructDef<'a> { /// "HalfStatic", /// Fields::Named(FIELDS)) /// } + /// + /// fn get(&self, field: Field<'_>) -> Option> { + /// match field { + /// Field::Named(field) if field.name() == "foo" => Some(self.foo.as_value()), + /// Field::Named(field) if field.name() == "bar" => Some(self.bar.as_value()), + /// Field::Named(field) => { + /// self.extra_values.get(field.name()).map(|v| v.as_value()) + /// }, + /// _ => None, + /// } + /// } /// } /// ``` #[non_exhaustive] @@ -478,6 +499,10 @@ macro_rules! deref { fn definition(&self) -> StructDef<'_> { T::definition(&**self) } + + fn get(&self, field: Field<'_>) -> Option> { + T::get(&**self, field) + } } )* };