merge_derive-0.2.0/.cargo_vcs_info.json0000644000000001520000000000100134660ustar { "git": { "sha1": "aa87e5ad5c63c3bd6fd9e45ad44ca96868449dbf" }, "path_in_vcs": "merge_derive" }merge_derive-0.2.0/.gitignore000064400000000000000000000002051046102023000142450ustar 00000000000000# SPDX-FileCopyrightText: 2020 Robin Krahl # SPDX-License-Identifier: CC0-1.0 /target **/*.rs.bk Cargo.lock merge_derive-0.2.0/CHANGELOG.md000064400000000000000000000006161046102023000140740ustar 00000000000000 # Unreleased - # v0.2.0 (2025-04-10) - Support setting a default merge strategy for a struct. - Update to `syn` 2.0 and replace `proc-macro-error` with `proc-macro-error2`. - Update MSRV to 1.70.0. # v0.1.0 (2020-09-01) Initial release providing a derive macro for the `Merge` trait. merge_derive-0.2.0/Cargo.toml0000644000000022570000000000100114740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "merge_derive" version = "0.2.0" authors = ["Robin Krahl "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Derive macro for the merge::Merge trait" readme = "README.md" keywords = [ "merge", "macros", "derive", ] categories = ["rust-patterns"] license = "Apache-2.0 OR MIT" repository = "https://git.sr.ht/~ireas/merge-rs/tree/master/merge_derive" [lib] name = "merge_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro-error2] version = "2.0" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" merge_derive-0.2.0/Cargo.toml.orig000064400000000000000000000011401046102023000151430ustar 00000000000000# SPDX-FileCopyrightText: 2020 Robin Krahl # SPDX-License-Identifier: CC0-1.0 [package] name = "merge_derive" version = "0.2.0" authors = ["Robin Krahl "] edition = "2021" rust-version = "1.70" description = "Derive macro for the merge::Merge trait" repository = "https://git.sr.ht/~ireas/merge-rs/tree/master/merge_derive" keywords = ["merge", "macros", "derive"] categories = ["rust-patterns"] readme = "README.md" license = "Apache-2.0 OR MIT" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" proc-macro-error2 = "2.0" quote = "1.0" syn = "2.0" merge_derive-0.2.0/README.md000064400000000000000000000004201046102023000135330ustar 00000000000000 # merge-derive-rs This crate provides a derive macro for the `merge::Merge` crate. See the [`merge`][] crate for more information. [`merge`]: https://lib.rs/crates/merge merge_derive-0.2.0/src/lib.rs000064400000000000000000000107111046102023000141630ustar 00000000000000// SPDX-FileCopyrightText: 2020 Robin Krahl // SPDX-License-Identifier: Apache-2.0 or MIT //! A derive macro for the [`merge::Merge`][] trait. //! //! See the documentation for the [`merge`][] crate for more information. //! //! [`merge`]: https://lib.rs/crates/merge //! [`merge::Merge`]: https://docs.rs/merge/latest/merge/trait.Merge.html extern crate proc_macro; use proc_macro2::TokenStream; use proc_macro_error2::{abort, abort_call_site, dummy::set_dummy, proc_macro_error, ResultExt}; use quote::{quote, quote_spanned}; use syn::Token; struct Field { name: syn::Member, span: proc_macro2::Span, attrs: FieldAttrs, } #[derive(Default)] struct FieldAttrs { skip: bool, strategy: Option, } enum FieldAttr { Skip, Strategy(syn::Path), } #[proc_macro_derive(Merge, attributes(merge))] #[proc_macro_error] pub fn merge_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse(input).unwrap(); impl_merge(&ast).into() } fn impl_merge(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let default_strategy = FieldAttrs::from(ast.attrs.iter()); set_dummy(quote! { impl ::merge::Merge for #name { fn merge(&mut self, other: Self) { unimplemented!() } } }); if let syn::Data::Struct(syn::DataStruct { ref fields, .. }) = ast.data { impl_merge_for_struct(name, fields, default_strategy) } else { abort_call_site!("merge::Merge can only be derived for structs") } } fn impl_merge_for_struct( name: &syn::Ident, fields: &syn::Fields, default_strategy: FieldAttrs, ) -> TokenStream { let assignments = gen_assignments(fields, default_strategy); quote! { impl ::merge::Merge for #name { fn merge(&mut self, other: Self) { #assignments } } } } fn gen_assignments(fields: &syn::Fields, default_strategy: FieldAttrs) -> TokenStream { let fields = fields.iter().enumerate().map(Field::from); let assignments = fields .filter(|f| !f.attrs.skip) .map(|f| gen_assignment(&f, &default_strategy)); quote! { #( #assignments )* } } fn gen_assignment(field: &Field, default_strategy: &FieldAttrs) -> TokenStream { use syn::spanned::Spanned; let name = &field.name; if let Some(strategy) = &field.attrs.strategy { quote_spanned!(strategy.span()=> #strategy(&mut self.#name, other.#name);) } else if let Some(default) = &default_strategy.strategy { quote_spanned!(default.span()=> #default(&mut self.#name, other.#name);) } else { quote_spanned!(field.span=> ::merge::Merge::merge(&mut self.#name, other.#name);) } } impl From<(usize, &syn::Field)> for Field { fn from(data: (usize, &syn::Field)) -> Self { use syn::spanned::Spanned; let (index, field) = data; Field { name: if let Some(ident) = &field.ident { syn::Member::Named(ident.clone()) } else { syn::Member::Unnamed(index.into()) }, span: field.span(), attrs: field.attrs.iter().into(), } } } impl FieldAttrs { fn apply(&mut self, attr: FieldAttr) { match attr { FieldAttr::Skip => self.skip = true, FieldAttr::Strategy(path) => self.strategy = Some(path), } } } impl<'a, I: Iterator> From for FieldAttrs { fn from(iter: I) -> Self { let mut field_attrs = Self::default(); for attr in iter { if !attr.path().is_ident("merge") { continue; } let parser = syn::punctuated::Punctuated::::parse_terminated; for attr in attr.parse_args_with(parser).unwrap_or_abort() { field_attrs.apply(attr); } } field_attrs } } impl syn::parse::Parse for FieldAttr { fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { let name: syn::Ident = input.parse()?; if name == "skip" { // TODO check remaining stream Ok(FieldAttr::Skip) } else if name == "strategy" { let _: Token![=] = input.parse()?; let path: syn::Path = input.parse()?; Ok(FieldAttr::Strategy(path)) } else { abort!(name, "Unexpected attribute: {}", name) } } }