merge_derive-0.1.0/.cargo_vcs_info.json0000644000000001121372345646700136000ustar00{ "git": { "sha1": "c70b6a50a2f43ad9c1f59b056908559f21dc9122" } } merge_derive-0.1.0/.gitignore010064400017500001750000000002051371030276700143610ustar0000000000000000# SPDX-FileCopyrightText: 2020 Robin Krahl # SPDX-License-Identifier: CC0-1.0 /target **/*.rs.bk Cargo.lock merge_derive-0.1.0/CHANGELOG.md010064400017500001750000000003041372345645700142140ustar0000000000000000 # v0.1.0 (2020-09-01) Initial release providing a derive macro for the `Merge` trait. merge_derive-0.1.0/Cargo.toml0000644000000020761372345646700116110ustar00# 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 believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "merge_derive" version = "0.1.0" authors = ["Robin Krahl "] 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] proc-macro = true [dependencies.proc-macro-error] version = "1.0" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "1.0" merge_derive-0.1.0/Cargo.toml.orig010064400017500001750000000011111372345643100152570ustar0000000000000000# SPDX-FileCopyrightText: 2020 Robin Krahl # SPDX-License-Identifier: CC0-1.0 [package] name = "merge_derive" version = "0.1.0" authors = ["Robin Krahl "] edition = "2018" 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-error = "1.0" quote = "1.0" syn = "1.0" merge_derive-0.1.0/README.md010064400017500001750000000004201372345514700136540ustar0000000000000000 # 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.1.0/src/lib.rs010064400017500001750000000101041372345514700143000ustar0000000000000000// 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_error::{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; 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) } else { abort_call_site!("merge::Merge can only be derived for structs") } } fn impl_merge_for_struct(name: &syn::Ident, fields: &syn::Fields) -> TokenStream { let assignments = gen_assignments(fields); quote! { impl ::merge::Merge for #name { fn merge(&mut self, other: Self) { #assignments } } } } fn gen_assignments(fields: &syn::Fields) -> TokenStream { let fields = fields.iter().enumerate().map(Field::from); let assignments = fields.filter(|f| !f.attrs.skip).map(|f| gen_assignment(&f)); quote! { #( #assignments )* } } fn gen_assignment(field: &Field) -> 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 { 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) } } }