implemented and tested struct derive IntoValue

This commit is contained in:
Tim 'Piepmatz' Hesse 2024-05-20 23:05:10 +02:00
parent 809ee938e9
commit f1420ac377
7 changed files with 260 additions and 13 deletions

View 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!()
}

View File

@ -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!()

View File

@ -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::*;

View File

@ -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>;
}

View File

@ -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())

View File

@ -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;

View 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);
}