mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 05:14:44 +02:00
Change expected type for derived FromValue
implementations via attribute (#13647)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> In this PR I expanded the helper attribute `#[nu_value]` on `#[derive(FromValue)]`. It now allows the usage of `#[nu_value(type_name = "...")]` to set a type name for the `FromValue::expected_type` implementation. Currently it only uses the default implementation but I'd like to change that without having to manually implement the entire trait on my own. # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> Users that derive `FromValue` may now change the name of the expected type. # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> I added some tests that check if this feature work and updated the documentation about the derive macro. - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
@ -42,9 +42,7 @@ pub fn derive_from_value(input: TokenStream2) -> Result<TokenStream2, DeriveErro
|
||||
|
||||
/// Implements the `#[derive(FromValue)]` macro for structs.
|
||||
///
|
||||
/// This function ensures that the helper attribute is not used anywhere, as it is not supported for
|
||||
/// structs.
|
||||
/// Other than this, this function provides the impl signature for `FromValue`.
|
||||
/// This function provides the impl signature for `FromValue`.
|
||||
/// The implementation for `FromValue::from_value` is handled by [`struct_from_value`] and the
|
||||
/// `FromValue::expected_type` is handled by [`struct_expected_type`].
|
||||
fn derive_struct_from_value(
|
||||
@ -53,11 +51,12 @@ fn derive_struct_from_value(
|
||||
generics: Generics,
|
||||
attrs: Vec<Attribute>,
|
||||
) -> Result<TokenStream2, DeriveError> {
|
||||
attributes::deny(&attrs)?;
|
||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||
attributes::deny_fields(&data.fields)?;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let from_value_impl = struct_from_value(&data);
|
||||
let expected_type_impl = struct_expected_type(&data.fields);
|
||||
let expected_type_impl =
|
||||
struct_expected_type(&data.fields, container_attrs.type_name.as_deref());
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||
@ -219,13 +218,16 @@ fn struct_from_value(data: &DataStruct) -> TokenStream2 {
|
||||
/// `list[type0, type1, type2]`.
|
||||
/// No fields expect the `Type::Nothing`.
|
||||
///
|
||||
/// If `#[nu_value(type_name = "...")]` is used, the output type will be `Type::Custom` with that
|
||||
/// passed name.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// These examples show what the macro would generate.
|
||||
///
|
||||
/// Struct with named fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// #[derive(FromValue)]
|
||||
/// struct Pet {
|
||||
/// name: String,
|
||||
/// age: u8,
|
||||
@ -256,7 +258,7 @@ fn struct_from_value(data: &DataStruct) -> TokenStream2 {
|
||||
///
|
||||
/// Struct with unnamed fields:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// #[derive(FromValue)]
|
||||
/// struct Color(u8, u8, u8);
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Color {
|
||||
@ -276,7 +278,7 @@ fn struct_from_value(data: &DataStruct) -> TokenStream2 {
|
||||
///
|
||||
/// Unit struct:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// #[derive(FromValue)]
|
||||
/// struct Unicorn;
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Color {
|
||||
@ -285,9 +287,30 @@ fn struct_from_value(data: &DataStruct) -> TokenStream2 {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn struct_expected_type(fields: &Fields) -> TokenStream2 {
|
||||
let ty = match fields {
|
||||
Fields::Named(fields) => {
|
||||
///
|
||||
/// Struct with passed type name:
|
||||
/// ```rust
|
||||
/// #[derive(FromValue)]
|
||||
/// #[nu_value(type_name = "bird")]
|
||||
/// struct Parrot;
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Parrot {
|
||||
/// fn expected_type() -> nu_protocol::Type {
|
||||
/// nu_protocol::Type::Custom(
|
||||
/// <std::string::String as std::convert::From::<&str>>::from("bird")
|
||||
/// .into_boxed_str()
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn struct_expected_type(fields: &Fields, attr_type_name: Option<&str>) -> TokenStream2 {
|
||||
let ty = match (fields, attr_type_name) {
|
||||
(_, Some(type_name)) => {
|
||||
quote!(nu_protocol::Type::Custom(
|
||||
<std::string::String as std::convert::From::<&str>>::from(#type_name).into_boxed_str()
|
||||
))
|
||||
}
|
||||
(Fields::Named(fields), _) => {
|
||||
let fields = fields.named.iter().map(|field| {
|
||||
let ident = field.ident.as_ref().expect("named has idents");
|
||||
let ident_s = ident.to_string();
|
||||
@ -301,7 +324,7 @@ fn struct_expected_type(fields: &Fields) -> TokenStream2 {
|
||||
std::vec![#(#fields),*].into_boxed_slice()
|
||||
))
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
(Fields::Unnamed(fields), _) => {
|
||||
let mut iter = fields.unnamed.iter();
|
||||
let fields = fields.unnamed.iter().map(|field| {
|
||||
let ty = &field.ty;
|
||||
@ -324,7 +347,7 @@ fn struct_expected_type(fields: &Fields) -> TokenStream2 {
|
||||
)
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote!(nu_protocol::Type::Nothing),
|
||||
(Fields::Unit, _) => quote!(nu_protocol::Type::Nothing),
|
||||
};
|
||||
|
||||
quote! {
|
||||
@ -338,23 +361,25 @@ fn struct_expected_type(fields: &Fields) -> TokenStream2 {
|
||||
///
|
||||
/// This function constructs the implementation of the `FromValue` trait for enums.
|
||||
/// It is designed to be on the same level as [`derive_struct_from_value`], even though this
|
||||
/// function only provides the impl signature for `FromValue`.
|
||||
/// The actual implementation for `FromValue::from_value` is handled by [`enum_from_value`].
|
||||
///
|
||||
/// Since variants are difficult to type with the current type system, this function uses the
|
||||
/// default implementation for `expected_type`.
|
||||
/// implementation is a lot simpler.
|
||||
/// The main `FromValue::from_value` implementation is handled by [`enum_from_value`].
|
||||
/// The `FromValue::expected_type` implementation is usually kept empty to use the default
|
||||
/// implementation, but if `#[nu_value(type_name = "...")]` if given, we use that.
|
||||
fn derive_enum_from_value(
|
||||
ident: Ident,
|
||||
data: DataEnum,
|
||||
generics: Generics,
|
||||
attrs: Vec<Attribute>,
|
||||
) -> Result<TokenStream2, DeriveError> {
|
||||
let container_attrs = ContainerAttributes::parse_attrs(attrs.iter())?;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let from_value_impl = enum_from_value(&data, &attrs)?;
|
||||
let expected_type_impl = enum_expected_type(container_attrs.type_name.as_deref());
|
||||
Ok(quote! {
|
||||
#[automatically_derived]
|
||||
impl #impl_generics nu_protocol::FromValue for #ident #ty_generics #where_clause {
|
||||
#from_value_impl
|
||||
#expected_type_impl
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -448,6 +473,41 @@ fn enum_from_value(data: &DataEnum, attrs: &[Attribute]) -> Result<TokenStream2,
|
||||
})
|
||||
}
|
||||
|
||||
/// Implements `FromValue::expected_type` for enums.
|
||||
///
|
||||
/// Since it's difficult to name the type of an enum in the current type system, we want to use the
|
||||
/// default implementation if `#[nu_value(type_name = "...")]` was *not* given.
|
||||
/// For that, a `None` value is returned, for a passed type name we return something like this:
|
||||
/// ```rust
|
||||
/// #[derive(IntoValue)]
|
||||
/// #[nu_value(type_name = "sunny | cloudy | raining")]
|
||||
/// enum Weather {
|
||||
/// Sunny,
|
||||
/// Cloudy,
|
||||
/// Raining
|
||||
/// }
|
||||
///
|
||||
/// impl nu_protocol::FromValue for Weather {
|
||||
/// fn expected_type() -> nu_protocol::Type {
|
||||
/// nu_protocol::Type::Custom(
|
||||
/// <std::string::String as std::convert::From::<&str>>::from("sunny | cloudy | raining")
|
||||
/// .into_boxed_str()
|
||||
/// )
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn enum_expected_type(attr_type_name: Option<&str>) -> Option<TokenStream2> {
|
||||
let type_name = attr_type_name?;
|
||||
Some(quote! {
|
||||
fn expected_type() -> nu_protocol::Type {
|
||||
nu_protocol::Type::Custom(
|
||||
<std::string::String as std::convert::From::<&str>>::from(#type_name)
|
||||
.into_boxed_str()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a `Value` into self.
|
||||
///
|
||||
/// This function handles the actual parsing of a `Value` into self.
|
||||
|
Reference in New Issue
Block a user