mirror of
https://github.com/nushell/nushell.git
synced 2025-05-18 00:40:48 +02:00
# 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.
70 lines
2.9 KiB
Rust
70 lines
2.9 KiB
Rust
//! Macro implementations of `#[derive(FromValue, IntoValue)]`.
|
|
//!
|
|
//! As this crate is a [`proc_macro`] crate, it is only allowed to export
|
|
//! [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html).
|
|
//! Therefore, it only exports [`IntoValue`] and [`FromValue`].
|
|
//!
|
|
//! To get documentation for other functions and types used in this crate, run
|
|
//! `cargo doc -p nu-derive-value --document-private-items`.
|
|
//!
|
|
//! This crate uses a lot of
|
|
//! [`proc_macro2::TokenStream`](https://docs.rs/proc-macro2/1.0.24/proc_macro2/struct.TokenStream.html)
|
|
//! as `TokenStream2` to allow testing the behavior of the macros directly, including the output
|
|
//! token stream or if the macro errors as expected.
|
|
//! The tests for functionality can be found in `nu_protocol::value::test_derive`.
|
|
//!
|
|
//! This documentation is often less reference-heavy than typical Rust documentation.
|
|
//! This is because this crate is a dependency for `nu_protocol`, and linking to it would create a
|
|
//! cyclic dependency.
|
|
//! Also all examples in the documentation aren't tested as this crate cannot be compiled as a
|
|
//! normal library very easily.
|
|
//! This might change in the future if cargo allows building a proc-macro crate differently for
|
|
//! `cfg(doctest)` as they are already doing for `cfg(test)`.
|
|
//!
|
|
//! The generated code from the derive macros tries to be as
|
|
//! [hygienic](https://doc.rust-lang.org/reference/macros-by-example.html#hygiene) as possible.
|
|
//! This ensures that the macro can be called anywhere without requiring specific imports.
|
|
//! This results in obtuse code, which isn't recommended for manual, handwritten Rust
|
|
//! but ensures that no other code may influence this generated code or vice versa.
|
|
|
|
use proc_macro::TokenStream;
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
use proc_macro_error::{proc_macro_error, Diagnostic};
|
|
|
|
mod attributes;
|
|
mod error;
|
|
mod from;
|
|
mod into;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
const HELPER_ATTRIBUTE: &str = "nu_value";
|
|
|
|
/// Derive macro generating an impl of the trait `IntoValue`.
|
|
///
|
|
/// For further information, see the docs on the trait itself.
|
|
#[proc_macro_derive(IntoValue, attributes(nu_value))]
|
|
#[proc_macro_error]
|
|
pub fn derive_into_value(input: TokenStream) -> TokenStream {
|
|
let input = TokenStream2::from(input);
|
|
let output = match into::derive_into_value(input) {
|
|
Ok(output) => output,
|
|
Err(e) => Diagnostic::from(e).abort(),
|
|
};
|
|
TokenStream::from(output)
|
|
}
|
|
|
|
/// Derive macro generating an impl of the trait `FromValue`.
|
|
///
|
|
/// For further information, see the docs on the trait itself.
|
|
#[proc_macro_derive(FromValue, attributes(nu_value))]
|
|
#[proc_macro_error]
|
|
pub fn derive_from_value(input: TokenStream) -> TokenStream {
|
|
let input = TokenStream2::from(input);
|
|
let output = match from::derive_from_value(input) {
|
|
Ok(output) => output,
|
|
Err(e) => Diagnostic::from(e).abort(),
|
|
};
|
|
TokenStream::from(output)
|
|
}
|