diff --git a/CHANGELOG.md b/CHANGELOG.md index 05c90ec3..9e1f67ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Suppress deprecation warnings in generated code. ([#454](https://github.com/JelteF/derive_more/pull/454)) +- Added `#[mul(forward_and_scalar)]` to generate both Add-like and Mul-like semantics when deriving Mul-likes. ## 2.0.1 - 2025-02-03 diff --git a/impl/doc/mul.md b/impl/doc/mul.md index b3a9fbea..14951cde 100644 --- a/impl/doc/mul.md +++ b/impl/doc/mul.md @@ -16,6 +16,7 @@ type in question deriving for this is not implemented. NOTE: In case you don't want this behaviour you can add `#[mul(forward)]` in addition to `#[derive(Mul)]`. This will instead generate a `Mul` implementation with the same semantics as `Add`. +You can also add `#[mul(forward_and_scalar)]` to implement both to generate both Mul-like and Add-like semantics. diff --git a/impl/doc/mul_assign.md b/impl/doc/mul_assign.md index ea88b2e3..7e8c12a9 100644 --- a/impl/doc/mul_assign.md +++ b/impl/doc/mul_assign.md @@ -8,6 +8,7 @@ You can add the `#[mul_assign(forward)]` attribute if you don't want the same semantics as `Mul`. This will instead generate a `MulAssign` implementation with the same semantics as `AddAssign`. +You can add `#[mul_assign(forward_and_scalar)]` to generate both Mul-like and Add-like semantics. diff --git a/impl/src/mul_assign_like.rs b/impl/src/mul_assign_like.rs index b5fb4e50..4326f9f8 100644 --- a/impl/src/mul_assign_like.rs +++ b/impl/src/mul_assign_like.rs @@ -13,15 +13,28 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result) -> TokenStream { let scalar_ident = format_ident!("__RhsT"); state.add_trait_path_type_param(quote! { #scalar_ident }); let multi_field_data = state.enabled_fields_data(); @@ -51,7 +64,7 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result for #input_type #ty_generics #where_clause { #[inline] @@ -60,5 +73,5 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result Result { - let mut state = State::with_attr_params( + let state = State::with_attr_params( input, trait_name, trait_name.to_lowercase(), - AttrParams::struct_(vec!["forward"]), + AttrParams::struct_(vec!["forward", "forward_and_scalar"]), )?; if state.default_info.forward { return Ok(add_like::expand(input, trait_name)); } + if state.default_info.forward_and_scalar { + return Ok(quote! { + {add_like::expand(input, trait_name)} + {expand_mul_like(state)} + }); + } + Ok(expand_mul_like(state)) +} +fn expand_mul_like(mut state: State<'_>) -> TokenStream { let scalar_ident = format_ident!("__RhsT"); state.add_trait_path_type_param(quote! { #scalar_ident }); let multi_field_data = state.enabled_fields_data(); @@ -47,7 +56,7 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result Result State<'input> { let defaults = struct_meta_info.into_full(FullMetaInfo { enabled: default_enabled, forward: false, + forward_and_scalar: false, // Default to owned true, except when first attribute has one of owned, // ref or ref_mut // - not a single attribute means default true @@ -873,6 +874,13 @@ fn get_meta_info( None, )?; + if info.forward.is_some() && info.forward_and_scalar.is_some() { + return Err(Error::new( + list.span(), + "Attributes `forward` and `forward_and_scalar` are mutually exclusive", + )); + } + Ok(info) } @@ -1016,7 +1024,13 @@ fn parse_punctuated_nested_meta( match (wrapper_name, attr_name.as_str()) { (None, "ignore") => info.enabled = Some(false), (None, "forward") => info.forward = Some(true), + (None, "forward_and_scalar") => { + info.forward_and_scalar = Some(true) + } (Some("not"), "forward") => info.forward = Some(false), + (Some("not"), "forward_and_scalar") => { + info.forward_and_scalar = Some(false) + } (None, "owned") => info.owned = Some(true), (None, "ref") => info.ref_ = Some(true), (None, "ref_mut") => info.ref_mut = Some(true), @@ -1204,6 +1218,7 @@ pub(crate) mod polyfill { pub struct FullMetaInfo { pub enabled: bool, pub forward: bool, + pub forward_and_scalar: bool, pub owned: bool, pub ref_: bool, pub ref_mut: bool, @@ -1214,6 +1229,7 @@ pub struct FullMetaInfo { pub struct MetaInfo { pub enabled: Option, pub forward: Option, + pub forward_and_scalar: Option, pub owned: Option, pub ref_: Option, pub ref_mut: Option, @@ -1228,6 +1244,9 @@ impl MetaInfo { FullMetaInfo { enabled: self.enabled.unwrap_or(defaults.enabled), forward: self.forward.unwrap_or(defaults.forward), + forward_and_scalar: self + .forward_and_scalar + .unwrap_or(defaults.forward_and_scalar), owned: self.owned.unwrap_or(defaults.owned), ref_: self.ref_.unwrap_or(defaults.ref_), ref_mut: self.ref_mut.unwrap_or(defaults.ref_mut), @@ -1727,6 +1746,7 @@ pub(crate) mod attr { fn parse(input: ParseStream<'_>) -> syn::Result { match input.parse::()? { p if p.is_ident("forward") => Ok(Self), + p if p.is_ident("forward_and_scalar") => Ok(Self), p => Err(syn::Error::new(p.span(), "only `forward` allowed here")), } }