struct_merge_codegen/lib.rs
1use generate::generate_impl;
2use module::get_struct_from_path;
3use path::{get_root_src_path, parse_input_paths};
4use proc_macro::TokenStream;
5use syn::{parse_macro_input, Expr, ExprPath, ItemStruct};
6
7/// Helper macro, which attaches an error to a given span.
8macro_rules! err {
9 ($span:expr, $($text:expr),*) => {
10 {
11 let message = format!($($text,)*);
12 let span = $span.span();
13 quote::quote_spanned!( span => compile_error!(#message); )
14 }
15 }
16}
17
18// Uncomment this as soon as proc_macro_diagnostic land in stable.
19//
20//#![feature(proc_macro_diagnostic)]
21///// Helper macro, which attaches an error to a given span.
22//macro_rules! err {
23// ($span:ident, $($text:expr),*) => {
24// $span.span()
25// .unwrap()
26// .error(format!($($text,)*))
27// .emit();
28// }
29//}
30
31/// Helper macro, which takes a result.
32/// Ok(T) => simply return the T
33/// Err(err) => Emits an compiler error on the given span with the provided error message.
34/// Also returns early with `None`.
35/// `None` is used throughout this crate as a gracefull failure.
36/// That way all code that can be created is being generated and the user sees all
37/// errors without the macro code panicking.
38macro_rules! ok_or_err_return {
39 ($expr:expr, $span:ident, $($text:expr),*) => {
40 match $expr {
41 Ok(result) => result,
42 Err(error) => {
43 return Err(err!($span, $($text,)* error));
44 }
45 }
46 }
47}
48
49mod generate;
50mod module;
51mod path;
52
53/// Implement the `struct_merge::StructMerge<S>` trait for all given targets.
54///
55/// Eiter a single struct or a list of structs can be provided.
56/// `StructMerge<T>` will then be implemented on each given target struct.
57///
58/// Examples:
59/// - `#[struct_merge(crate::structs::Target)]`
60/// - `#[struct_merge([crate::structs::Target, crate:structs::OtherTarget])]`
61///
62/// The targets struct paths have to be
63/// - absolute
64/// - relative to the current crate
65/// - contained in this crate
66///
67/// `struct.rs`
68/// ```ignore
69/// use struct_merge::struct_merge;
70///
71/// pub struct Target {
72/// pub test: String,
73/// }
74///
75/// pub struct OtherTarget {
76/// pub test: String,
77/// }
78///
79/// #[struct_merge([crate::structs::Target, crate:structs::OtherTarget])]
80/// pub struct Test {
81/// pub test: String,
82/// }
83/// ```
84#[proc_macro_attribute]
85pub fn struct_merge(args: TokenStream, struct_ast: TokenStream) -> TokenStream {
86 struct_merge_base(args, struct_ast, Mode::Owned)
87}
88
89/// Implement the `struct_merge::StructMergeRef<S>` trait for all given targets.
90///
91/// All fields to be merged must implement the [std::clone::Clone] trait.
92///
93/// Eiter a single struct or a list of structs can be provided.
94/// `StructMergeRef<T>` will then be implemented on each given target struct.
95///
96/// Examples:
97/// - `#[struct_merge_ref(crate::structs::Target)]`
98/// - `#[struct_merge_ref([crate::structs::Target, crate:structs::OtherTarget])]`
99///
100/// The targets struct paths have to be
101/// - absolute
102/// - relative to the current crate
103/// - contained in this crate
104///
105/// `struct.rs`
106/// ```ignore
107/// use struct_merge::struct_merge_ref;
108///
109/// pub struct Target {
110/// pub test: String,
111/// }
112///
113/// pub struct OtherTarget {
114/// pub test: String,
115/// }
116///
117/// #[struct_merge_ref([crate::structs::Target, crate:structs::OtherTarget])]
118/// pub struct Test {
119/// pub test: String,
120/// }
121/// ```
122#[proc_macro_attribute]
123pub fn struct_merge_ref(args: TokenStream, struct_ast: TokenStream) -> TokenStream {
124 struct_merge_base(args, struct_ast, Mode::Borrowed)
125}
126
127/// This enum is used to differentiate between owned and borrowed merge behavior.
128/// Depending on this, we need to generate another trait impl and slightly different code.
129enum Mode {
130 Owned,
131 Borrowed,
132}
133
134pub(crate) struct Parameters {
135 pub src_struct: ItemStruct,
136 pub target_path: ExprPath,
137 pub target_struct: ItemStruct,
138}
139
140fn struct_merge_base(args: TokenStream, mut struct_ast: TokenStream, mode: Mode) -> TokenStream {
141 let parsed_args = parse_macro_input!(args as Expr);
142 // Check if we can find the src root path of this crate.
143 // Return early if it doesn't exist.
144 let src_root_path = match get_root_src_path(&parsed_args) {
145 Some(path) => path,
146 None => return struct_ast,
147 };
148
149 // Parse the main macro input as a struct.
150 // We work on a clone of the struct ast.
151 // That way we don't have to parse it lateron when we return it.
152 let cloned_struct_ast = struct_ast.clone();
153 let src_struct = parse_macro_input!(cloned_struct_ast as ItemStruct);
154
155 // Get the input paths from the given argument expressions.
156 let paths = parse_input_paths(parsed_args);
157
158 // Go through all paths and process the respective struct.
159 let mut impls = Vec::new();
160 for target_path in paths {
161 // Make sure we found the struct at that path.
162 let target_struct = match get_struct_from_path(src_root_path.clone(), target_path.clone()) {
163 Ok(ast) => ast,
164 Err(error) => {
165 impls.push(error);
166 continue;
167 }
168 };
169
170 let params = Parameters {
171 src_struct: src_struct.clone(),
172 target_path,
173 target_struct,
174 };
175
176 // Generate the MergeStruct trait implementations.
177 match generate_impl(&mode, params) {
178 Ok(ast) => impls.push(ast),
179 Err(error) => {
180 impls.push(error);
181 continue;
182 }
183 }
184 }
185
186 // Merge all generated pieces of the code with the original unaltered struct.
187 struct_ast.extend(impls.into_iter().map(TokenStream::from));
188
189 // Hand the final output tokens back to the compiler.
190 struct_ast
191}