implemented and tested struct derive IntoValue
This commit is contained in:
parent
809ee938e9
commit
f1420ac377
44
crates/nu-derive-value/src/into.rs
Normal file
44
crates/nu-derive-value/src/into.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::{Data, DataEnum, DataStruct, DeriveInput, Fields, Generics, Ident};
|
||||
|
||||
pub fn derive_into_value(input: TokenStream2) -> syn::Result<TokenStream2> {
|
||||
let input: DeriveInput = syn::parse2(input)?;
|
||||
match input.data {
|
||||
Data::Struct(data_struct) => {
|
||||
Ok(struct_into_value(input.ident, data_struct, input.generics))
|
||||
}
|
||||
Data::Enum(data_enum) => Ok(enum_into_value(data_enum)),
|
||||
Data::Union(_) => todo!("throw some error"),
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_into_value(ident: Ident, data: DataStruct, generics: Generics) -> TokenStream2 {
|
||||
let fields: Vec<TokenStream2> = match data.fields {
|
||||
Fields::Named(fields) => fields
|
||||
.named
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
let ident = field.ident.expect("named fields have an ident");
|
||||
let field = ident.to_string();
|
||||
quote!(#field => nu_protocol::IntoValue::into_value(self.#ident, span))
|
||||
})
|
||||
.collect(),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
quote! {
|
||||
impl #impl_generics nu_protocol::IntoValue for #ident #ty_generics #where_clause {
|
||||
fn into_value(self, span: nu_protocol::Span) -> nu_protocol::Value {
|
||||
nu_protocol::Value::record(nu_protocol::record! {
|
||||
#(#fields),*
|
||||
}, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_into_value(input: DataEnum) -> TokenStream2 {
|
||||
todo!()
|
||||
}
|
|
@ -1,12 +1,19 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
|
||||
mod into;
|
||||
|
||||
#[proc_macro_derive(IntoValue)]
|
||||
#[proc_macro_error]
|
||||
pub fn derive_into_value(input: TokenStream) -> TokenStream {
|
||||
let _ = input;
|
||||
todo!()
|
||||
let input = TokenStream2::from(input);
|
||||
let output = into::derive_into_value(input).unwrap();
|
||||
TokenStream::from(output)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromValue)]
|
||||
#[proc_macro_error]
|
||||
pub fn derive_from_value(input: TokenStream) -> TokenStream {
|
||||
let _ = input;
|
||||
todo!()
|
||||
|
|
|
@ -31,6 +31,8 @@ pub use example::*;
|
|||
pub use id::*;
|
||||
pub use lev_distance::levenshtein_distance;
|
||||
pub use module::*;
|
||||
#[cfg(feature = "derive")]
|
||||
pub use nu_derive_value::*;
|
||||
pub use pipeline::*;
|
||||
#[cfg(feature = "plugin")]
|
||||
pub use plugin::*;
|
||||
|
@ -39,5 +41,3 @@ pub use span::*;
|
|||
pub use syntax_shape::*;
|
||||
pub use ty::*;
|
||||
pub use value::*;
|
||||
#[cfg(feature = "derive")]
|
||||
pub use nu_derive_value::*;
|
||||
|
|
|
@ -10,9 +10,9 @@ use std::path::PathBuf;
|
|||
pub trait FromValue: Sized {
|
||||
// TODO: instead of ShellError, maybe we could have a FromValueError that implements Into<ShellError>
|
||||
/// Loads a value from a `Value`.
|
||||
///
|
||||
/// Just like [`FromStr`](std::str::FromStr), this operation may fail
|
||||
/// because the raw `Value` is able to represent more values than the
|
||||
///
|
||||
/// Just like [`FromStr`](std::str::FromStr), this operation may fail
|
||||
/// because the raw `Value` is able to represent more values than the
|
||||
/// expected value here.
|
||||
fn from_value(v: Value) -> Result<Self, ShellError>;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use crate::{ShellError, Span, Value};
|
||||
|
||||
/// A trait for converting a value into a `Value`.
|
||||
///
|
||||
///
|
||||
/// This conversion is infallible, for fallible conversions use [`TryIntoValue`].
|
||||
pub trait IntoValue: Sized {
|
||||
/// Converts the given value to a `Value`.
|
||||
fn into_value(self, span: Span) -> Value;
|
||||
|
||||
/// Converts the given value to a `Value` with an unknown `Span`.
|
||||
///
|
||||
///
|
||||
/// Internally this simply calls [`Span::unknown`] for the `span`.
|
||||
fn into_value_unknown(self) -> Value {
|
||||
Self::into_value(self, Span::unknown())
|
||||
|
@ -118,6 +118,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> IntoValue for Option<T>
|
||||
where
|
||||
T: IntoValue,
|
||||
{
|
||||
fn into_value(self, span: Span) -> Value {
|
||||
match self {
|
||||
Some(v) => v.into_value(span),
|
||||
None => Value::nothing(span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nu Types
|
||||
|
||||
impl IntoValue for Value {
|
||||
|
@ -128,10 +140,10 @@ impl IntoValue for Value {
|
|||
|
||||
// TODO: use this type for all the `into_value` methods that types implement but return a Result
|
||||
/// A trait for trying to convert a value into a `Value`.
|
||||
///
|
||||
/// Types like streams may fail while collecting the `Value`,
|
||||
///
|
||||
/// Types like streams may fail while collecting the `Value`,
|
||||
/// for these types it is useful to implement a fallible variant.
|
||||
///
|
||||
///
|
||||
/// This conversion is fallible, for infallible conversions use [`IntoValue`].
|
||||
/// All types that implement `IntoValue` will automatically implement this trait.
|
||||
pub trait TryIntoValue: Sized {
|
||||
|
@ -140,7 +152,7 @@ pub trait TryIntoValue: Sized {
|
|||
fn try_into_value(self, span: Span) -> Result<Value, ShellError>;
|
||||
|
||||
/// Tries to convert the given value to a `Value` with an unknown `Span`.
|
||||
///
|
||||
///
|
||||
/// Internally this simply calls [`Span::unknown`] for the `span`.
|
||||
fn try_into_value_unknown(self) -> Result<Value, ShellError> {
|
||||
Self::try_into_value(self, Span::unknown())
|
||||
|
|
|
@ -6,6 +6,8 @@ mod from_value;
|
|||
mod glob;
|
||||
mod into_value;
|
||||
mod range;
|
||||
#[cfg(all(test, feature = "derive"))]
|
||||
mod test_derive;
|
||||
|
||||
pub mod record;
|
||||
pub use custom_value::CustomValue;
|
||||
|
|
182
crates/nu-protocol/src/value/test_derive.rs
Normal file
182
crates/nu-protocol/src/value/test_derive.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
use crate::{record, IntoValue, Record, Value};
|
||||
|
||||
// make nu_protocol available in this namespace, consumers of this crate will
|
||||
// have this without such an export
|
||||
// the derive macro fully qualifies paths to "nu_protocol"
|
||||
use crate as nu_protocol;
|
||||
|
||||
#[derive(IntoValue)]
|
||||
struct Primitives {
|
||||
p_array: [u8; 4],
|
||||
p_bool: bool,
|
||||
p_char: char,
|
||||
p_f32: f32,
|
||||
p_f64: f64,
|
||||
p_i8: i8,
|
||||
p_i16: i16,
|
||||
p_i32: i32,
|
||||
p_i64: i64,
|
||||
p_isize: isize,
|
||||
p_str: &'static str,
|
||||
p_u8: u8,
|
||||
p_u16: u16,
|
||||
p_u32: u32,
|
||||
p_u64: u64,
|
||||
p_usize: usize,
|
||||
p_unit: (),
|
||||
p_tuple: (u32, bool),
|
||||
}
|
||||
|
||||
impl Primitives {
|
||||
fn make() -> Primitives {
|
||||
Primitives {
|
||||
p_array: [12, 34, 56, 78],
|
||||
p_bool: true,
|
||||
p_char: 'A',
|
||||
p_f32: 123.456,
|
||||
p_f64: 789.1011,
|
||||
p_i8: -12,
|
||||
p_i16: -1234,
|
||||
p_i32: -123456,
|
||||
p_i64: -1234567890,
|
||||
p_isize: 1024,
|
||||
p_str: "Hello, world!",
|
||||
p_u8: 255,
|
||||
p_u16: 65535,
|
||||
p_u32: 4294967295,
|
||||
p_u64: 8446744073709551615,
|
||||
p_usize: 4096,
|
||||
p_unit: (),
|
||||
p_tuple: (123456789, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_record_field(value: &mut Record, key: &str, expected: Value) {
|
||||
let field = value
|
||||
.remove(key)
|
||||
.expect(&format!("expected record to have {key:?}"));
|
||||
assert_eq!(field, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primitives_into_value() {
|
||||
let primitives = Primitives::make();
|
||||
let mut record = primitives.into_value_unknown().into_record().unwrap();
|
||||
assert_record_field(
|
||||
&mut record,
|
||||
"p_array",
|
||||
Value::test_list(vec![
|
||||
Value::test_int(12),
|
||||
Value::test_int(34),
|
||||
Value::test_int(56),
|
||||
Value::test_int(78),
|
||||
]),
|
||||
);
|
||||
assert_record_field(&mut record, "p_bool", Value::test_bool(true));
|
||||
assert_record_field(&mut record, "p_char", Value::test_string("A"));
|
||||
assert_record_field(&mut record, "p_f64", Value::test_float(789.1011));
|
||||
assert_record_field(&mut record, "p_i8", Value::test_int(-12));
|
||||
assert_record_field(&mut record, "p_i16", Value::test_int(-1234));
|
||||
assert_record_field(&mut record, "p_i32", Value::test_int(-123456));
|
||||
assert_record_field(&mut record, "p_i64", Value::test_int(-1234567890));
|
||||
assert_record_field(&mut record, "p_isize", Value::test_int(1024));
|
||||
assert_record_field(&mut record, "p_str", Value::test_string("Hello, world!"));
|
||||
assert_record_field(&mut record, "p_u8", Value::test_int(255));
|
||||
assert_record_field(&mut record, "p_u16", Value::test_int(65535));
|
||||
assert_record_field(&mut record, "p_u32", Value::test_int(4294967295));
|
||||
assert_record_field(&mut record, "p_u64", Value::test_int(8446744073709551615));
|
||||
assert_record_field(&mut record, "p_usize", Value::test_int(4096));
|
||||
assert_record_field(&mut record, "p_unit", Value::test_nothing());
|
||||
assert_record_field(
|
||||
&mut record,
|
||||
"p_tuple",
|
||||
Value::test_list(vec![Value::test_int(123456789), Value::test_bool(false)]),
|
||||
);
|
||||
|
||||
// Handle f32 separately to cast the value back down to f32 for comparison.
|
||||
let key = "p_f32";
|
||||
let p_f32 = record
|
||||
.remove(key)
|
||||
.expect("expected record to have {key:?}")
|
||||
.as_float()
|
||||
.expect("{key:?} was not a float");
|
||||
assert_eq!(p_f32 as f32, 123.456);
|
||||
|
||||
assert!(record.is_empty());
|
||||
}
|
||||
|
||||
#[derive(IntoValue)]
|
||||
struct StdValues {
|
||||
some: Option<usize>,
|
||||
none: Option<usize>,
|
||||
vec: Vec<usize>,
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl StdValues {
|
||||
fn make() -> Self {
|
||||
StdValues {
|
||||
some: Some(123),
|
||||
none: None,
|
||||
vec: vec![1, 2],
|
||||
string: "Hello std!".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn std_values_into_value() {
|
||||
let actual = StdValues::make().into_value_unknown();
|
||||
let expected = Value::test_record(record! {
|
||||
"some" => Value::test_int(123),
|
||||
"none" => Value::test_nothing(),
|
||||
"vec" => Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
|
||||
"string" => Value::test_string("Hello std!")
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[derive(IntoValue)]
|
||||
struct Outer {
|
||||
a: InnerA,
|
||||
b: InnerB,
|
||||
c: u8,
|
||||
}
|
||||
|
||||
#[derive(IntoValue)]
|
||||
struct InnerA {
|
||||
d: bool,
|
||||
}
|
||||
|
||||
#[derive(IntoValue)]
|
||||
struct InnerB {
|
||||
e: f64,
|
||||
f: (),
|
||||
}
|
||||
|
||||
impl Outer {
|
||||
fn make() -> Self {
|
||||
Outer {
|
||||
a: InnerA { d: true },
|
||||
b: InnerB { e: 123.456, f: () },
|
||||
c: 69,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_into_value() {
|
||||
let nested = Outer::make().into_value_unknown();
|
||||
let expected = Value::test_record(record! {
|
||||
"a" => Value::test_record(record! {
|
||||
"d" => Value::test_bool(true),
|
||||
}),
|
||||
"b" => Value::test_record(record! {
|
||||
"e" => Value::test_float(123.456),
|
||||
"f" => Value::test_nothing(),
|
||||
}),
|
||||
"c" => Value::test_int(69),
|
||||
});
|
||||
assert_eq!(nested, expected);
|
||||
}
|
Loading…
Reference in New Issue
Block a user