nushell/crates/nu-derive-value/src/error.rs

105 lines
3.8 KiB
Rust
Raw Normal View History

Add derive macros for `FromValue` and `IntoValue` to ease the use of `Value`s in Rust code (#13031) # Description After discussing with @sholderbach the cumbersome usage of `nu_protocol::Value` in Rust, I created a derive macro to simplify it. I’ve added a new crate called `nu-derive-value`, which includes two macros, `IntoValue` and `FromValue`. These are re-exported in `nu-protocol` and should be encouraged to be used via that re-export. The macros ensure that all types can easily convert from and into `Value`. For example, as a plugin author, you can define your plugin configuration using a Rust struct and easily convert it using `FromValue`. This makes plugin configuration less of a hassle. I introduced the `IntoValue` trait for a standardized approach to converting values into `Value` (and a fallible variant `TryIntoValue`). This trait could potentially replace existing `into_value` methods. Along with this, I've implemented `FromValue` for several standard types and refined other implementations to use blanket implementations where applicable. I made these design choices with input from @devyn. There are more improvements possible, but this is a solid start and the PR is already quite substantial. # User-Facing Changes For `nu-protocol` users, these changes simplify the handling of `Value`s. There are no changes for end-users of nushell itself. # Tests + Formatting Documenting the macros itself is not really possible, as they cannot really reference any other types since they are the root of the dependency graph. The standard library has the same problem ([std::Debug](https://doc.rust-lang.org/stable/std/fmt/derive.Debug.html)). However I documented the `FromValue` and `IntoValue` traits completely. For testing, I made of use `proc-macro2` in the derive macro code. This would allow testing the generated source code. Instead I just tested that the derived functionality is correct. This is done in `nu_protocol::value::test_derive`, as a consumer of `nu-derive-value` needs to do the testing of the macro usage. I think that these tests should provide a stable baseline so that users can be sure that the impl works. # After Submitting With these macros available, we can probably use them in some examples for plugins to showcase the use of them.
2024-06-17 23:05:11 +00:00
use std::{any, fmt::Debug, marker::PhantomData};
use proc_macro2::Span;
use proc_macro_error::{Diagnostic, Level};
#[derive(Debug)]
pub enum DeriveError<M> {
/// Marker variant, makes the `M` generic parameter valid.
_Marker(PhantomData<M>),
/// Parsing errors thrown by `syn`.
Syn(syn::parse::Error),
/// `syn::DeriveInput` was a union, currently not supported
UnsupportedUnions,
/// Only plain enums are supported right now.
UnsupportedEnums { fields_span: Span },
/// Found a `#[nu_value(x)]` attribute where `x` is unexpected.
UnexpectedAttribute { meta_span: Span },
/// Found a `#[nu_value(x)]` attribute at a invalid position.
InvalidAttributePosition { attribute_span: Span },
/// Found a valid `#[nu_value(x)]` attribute but the passed values is invalid.
InvalidAttributeValue {
value_span: Span,
value: Box<dyn Debug>,
},
/// Two keys or variants are called the same name breaking bidirectionality.
NonUniqueName {
name: String,
first: Span,
second: Span,
},
}
impl<M> From<syn::parse::Error> for DeriveError<M> {
fn from(value: syn::parse::Error) -> Self {
Self::Syn(value)
}
Add derive macros for `FromValue` and `IntoValue` to ease the use of `Value`s in Rust code (#13031) # Description After discussing with @sholderbach the cumbersome usage of `nu_protocol::Value` in Rust, I created a derive macro to simplify it. I’ve added a new crate called `nu-derive-value`, which includes two macros, `IntoValue` and `FromValue`. These are re-exported in `nu-protocol` and should be encouraged to be used via that re-export. The macros ensure that all types can easily convert from and into `Value`. For example, as a plugin author, you can define your plugin configuration using a Rust struct and easily convert it using `FromValue`. This makes plugin configuration less of a hassle. I introduced the `IntoValue` trait for a standardized approach to converting values into `Value` (and a fallible variant `TryIntoValue`). This trait could potentially replace existing `into_value` methods. Along with this, I've implemented `FromValue` for several standard types and refined other implementations to use blanket implementations where applicable. I made these design choices with input from @devyn. There are more improvements possible, but this is a solid start and the PR is already quite substantial. # User-Facing Changes For `nu-protocol` users, these changes simplify the handling of `Value`s. There are no changes for end-users of nushell itself. # Tests + Formatting Documenting the macros itself is not really possible, as they cannot really reference any other types since they are the root of the dependency graph. The standard library has the same problem ([std::Debug](https://doc.rust-lang.org/stable/std/fmt/derive.Debug.html)). However I documented the `FromValue` and `IntoValue` traits completely. For testing, I made of use `proc-macro2` in the derive macro code. This would allow testing the generated source code. Instead I just tested that the derived functionality is correct. This is done in `nu_protocol::value::test_derive`, as a consumer of `nu-derive-value` needs to do the testing of the macro usage. I think that these tests should provide a stable baseline so that users can be sure that the impl works. # After Submitting With these macros available, we can probably use them in some examples for plugins to showcase the use of them.
2024-06-17 23:05:11 +00:00
}
impl<M> From<DeriveError<M>> for Diagnostic {
fn from(value: DeriveError<M>) -> Self {
let derive_name = any::type_name::<M>().split("::").last().expect("not empty");
match value {
DeriveError::_Marker(_) => panic!("used marker variant"),
DeriveError::Syn(e) => Diagnostic::spanned(e.span(), Level::Error, e.to_string()),
DeriveError::UnsupportedUnions => Diagnostic::new(
Level::Error,
format!("`{derive_name}` cannot be derived from unions"),
)
.help("consider refactoring to a struct".to_string())
.note("if you really need a union, consider opening an issue on Github".to_string()),
DeriveError::UnsupportedEnums { fields_span } => Diagnostic::spanned(
fields_span,
Level::Error,
format!("`{derive_name}` can only be derived from plain enums"),
)
.help(
"consider refactoring your data type to a struct with a plain enum as a field"
.to_string(),
)
.note("more complex enums could be implemented in the future".to_string()),
DeriveError::InvalidAttributePosition { attribute_span } => Diagnostic::spanned(
attribute_span,
Level::Error,
"invalid attribute position".to_string(),
)
.help(format!(
"check documentation for `{derive_name}` for valid placements"
)),
DeriveError::UnexpectedAttribute { meta_span } => {
Diagnostic::spanned(meta_span, Level::Error, "unknown attribute".to_string()).help(
format!("check documentation for `{derive_name}` for valid attributes"),
)
}
DeriveError::InvalidAttributeValue { value_span, value } => {
Diagnostic::spanned(value_span, Level::Error, format!("invalid value {value:?}"))
.help(format!(
"check documentation for `{derive_name}` for valid attribute values"
))
}
DeriveError::NonUniqueName {
name,
first,
second,
} => Diagnostic::new(Level::Error, format!("non-unique name {name:?} found"))
.span_error(first, "first occurrence found here".to_string())
.span_error(second, "second occurrence found here".to_string())
.help("use `#[nu_value(rename = \"...\")]` to ensure unique names".to_string()),
Add derive macros for `FromValue` and `IntoValue` to ease the use of `Value`s in Rust code (#13031) # Description After discussing with @sholderbach the cumbersome usage of `nu_protocol::Value` in Rust, I created a derive macro to simplify it. I’ve added a new crate called `nu-derive-value`, which includes two macros, `IntoValue` and `FromValue`. These are re-exported in `nu-protocol` and should be encouraged to be used via that re-export. The macros ensure that all types can easily convert from and into `Value`. For example, as a plugin author, you can define your plugin configuration using a Rust struct and easily convert it using `FromValue`. This makes plugin configuration less of a hassle. I introduced the `IntoValue` trait for a standardized approach to converting values into `Value` (and a fallible variant `TryIntoValue`). This trait could potentially replace existing `into_value` methods. Along with this, I've implemented `FromValue` for several standard types and refined other implementations to use blanket implementations where applicable. I made these design choices with input from @devyn. There are more improvements possible, but this is a solid start and the PR is already quite substantial. # User-Facing Changes For `nu-protocol` users, these changes simplify the handling of `Value`s. There are no changes for end-users of nushell itself. # Tests + Formatting Documenting the macros itself is not really possible, as they cannot really reference any other types since they are the root of the dependency graph. The standard library has the same problem ([std::Debug](https://doc.rust-lang.org/stable/std/fmt/derive.Debug.html)). However I documented the `FromValue` and `IntoValue` traits completely. For testing, I made of use `proc-macro2` in the derive macro code. This would allow testing the generated source code. Instead I just tested that the derived functionality is correct. This is done in `nu_protocol::value::test_derive`, as a consumer of `nu-derive-value` needs to do the testing of the macro usage. I think that these tests should provide a stable baseline so that users can be sure that the impl works. # After Submitting With these macros available, we can probably use them in some examples for plugins to showcase the use of them.
2024-06-17 23:05:11 +00:00
}
}
}