1use proc_macro::TokenStream;
10use proc_macro2::{Ident, Literal, Span, TokenStream as TokenStream2, TokenTree};
11use proc_macro_crate::{FoundCrate, crate_name};
12use quote::{quote, quote_spanned};
13
14const KNOWN_DERIVES: &[(&str, &str)] = &[
26 ("Args", "clap"),
27 ("Parser", "clap"),
28 ("Subcommand", "clap"),
29 ("ValueEnum", "clap"),
30 ("Add", "derive_more"),
31 ("AddAssign", "derive_more"),
32 ("AsMut", "derive_more"),
33 ("AsRef", "derive_more"),
34 ("Binary", "derive_more"),
35 ("BitAnd", "derive_more"),
36 ("BitAndAssign", "derive_more"),
37 ("BitOr", "derive_more"),
38 ("BitOrAssign", "derive_more"),
39 ("BitXor", "derive_more"),
40 ("BitXorAssign", "derive_more"),
41 ("Constructor", "derive_more"),
42 ("Deref", "derive_more"),
43 ("DerefMut", "derive_more"),
44 ("Display", "derive_more"),
45 ("Div", "derive_more"),
46 ("DivAssign", "derive_more"),
47 ("From", "derive_more"),
48 ("FromStr", "derive_more"),
49 ("Index", "derive_more"),
50 ("IndexMut", "derive_more"),
51 ("Into", "derive_more"),
52 ("IntoIterator", "derive_more"),
53 ("IsVariant", "derive_more"),
54 ("LowerExp", "derive_more"),
55 ("LowerHex", "derive_more"),
56 ("Mul", "derive_more"),
57 ("MulAssign", "derive_more"),
58 ("Neg", "derive_more"),
59 ("Not", "derive_more"),
60 ("Octal", "derive_more"),
61 ("Pointer", "derive_more"),
62 ("Product", "derive_more"),
63 ("Rem", "derive_more"),
64 ("RemAssign", "derive_more"),
65 ("Shl", "derive_more"),
66 ("ShlAssign", "derive_more"),
67 ("Shr", "derive_more"),
68 ("ShrAssign", "derive_more"),
69 ("Sub", "derive_more"),
70 ("SubAssign", "derive_more"),
71 ("Sum", "derive_more"),
72 ("TryFrom", "derive_more"),
73 ("TryInto", "derive_more"),
74 ("TryUnwrap", "derive_more"),
75 ("Unwrap", "derive_more"),
76 ("UpperExp", "derive_more"),
77 ("UpperHex", "derive_more"),
78 ("FromPrimitive", "num_enum"),
79 ("IntoPrimitive", "num_enum"),
80 ("TryFromPrimitive", "num_enum"),
81 ("UnsafeFromPrimitive", "num_enum"),
82 ("Deserialize", "serde"),
83 ("Serialize", "serde"),
84 ("Error", "thiserror"),
85];
86
87enum Naming {
92 CrateAttrStr,
95 CrateAttrPath,
98 Scoped,
101 ExternOnly,
104}
105
106fn naming(krate: &str) -> Naming {
107 match krate {
108 "serde" => Naming::CrateAttrStr,
109 "num_enum" => Naming::CrateAttrPath,
110 "clap" | "derive_more" => Naming::Scoped,
111 "thiserror" => Naming::ExternOnly,
112 _ => unreachable!("no naming strategy for `{krate}`"),
113 }
114}
115
116#[proc_macro_attribute]
134pub fn derive(attr: TokenStream, item: TokenStream) -> TokenStream {
135 let item = TokenStream2::from(item);
136 let requests = match parse_requests(TokenStream2::from(attr)) {
137 Ok(requests) => requests,
138 Err(err) => return prepend(err, item).into(),
139 };
140
141 let rustmax = match rustmax_path() {
142 Ok(path) => path,
143 Err(err) => return prepend(err, item).into(),
144 };
145
146 let mut paths = Vec::new();
147 let mut scoped = Vec::new();
148 let mut crate_attrs = Vec::new();
149 let mut errors = TokenStream2::new();
150
151 for request in requests {
152 let (name, krate, span) = match request {
153 Request::Passthrough(tokens) => {
154 paths.push(tokens);
155 continue;
156 }
157 Request::Bare(name) => match lookup(&name.to_string()) {
158 Some(krate) => {
159 let span = name.span();
160 (name, krate, span)
161 }
162 None => {
163 paths.push(TokenTree::Ident(name).into());
164 continue;
165 }
166 },
167 Request::Qualified(krate, name) => match lookup_crate(&krate.to_string()) {
168 Some(krate) => {
169 let span = name.span();
170 (name, krate, span)
171 }
172 None => {
173 paths.push(quote!(#krate::#name));
174 continue;
175 }
176 },
177 };
178
179 match naming(krate) {
180 Naming::CrateAttrStr | Naming::CrateAttrPath => {
183 if !crate_attrs.iter().any(|(k, _)| *k == krate) {
184 crate_attrs.push((krate, crate_attr(krate, &rustmax)));
185 }
186 }
187 Naming::Scoped => match direct_path(krate) {
190 Some(direct) => {
191 paths.push(quote!(#direct::#name));
192 continue;
193 }
194 None => {
195 if !scoped.contains(&krate) {
196 scoped.push(krate);
197 }
198 }
199 },
200 Naming::ExternOnly => match direct_path(krate) {
201 Some(direct) => {
202 paths.push(quote!(#direct::#name));
203 continue;
204 }
205 None => {
206 errors.extend(extern_only_error(span, krate));
207 continue;
208 }
209 },
210 }
211
212 let root = &rustmax.tokens;
213 let module = Ident::new(krate, span);
214 paths.push(quote!(#root::#module::#name));
215 }
216
217 let root = &rustmax.tokens;
218 let scope_imports = scoped.iter().map(|krate| {
219 let module = Ident::new(krate, Span::call_site());
220 quote! {
221 #[allow(unused_imports)]
222 use #root::__macro_scope::#module::*;
223 }
224 });
225 let crate_attrs = crate_attrs.into_iter().map(|(_, attr)| attr);
226 let derive_attr = (!paths.is_empty()).then(|| quote!(#[derive(#(#paths),*)]));
227
228 quote! {
229 #errors
230 #(#scope_imports)*
231 #derive_attr
232 #(#crate_attrs)*
233 #item
234 }
235 .into()
236}
237
238fn crate_attr(krate: &str, rustmax: &RustmaxPath) -> TokenStream2 {
240 let module = Ident::new(krate, Span::call_site());
241 match naming(krate) {
242 Naming::CrateAttrStr => {
243 let path = Literal::string(&format!("{}::{}", rustmax.text, krate));
244 quote!(#[#module(crate = #path)])
245 }
246 Naming::CrateAttrPath => {
247 let root = &rustmax.tokens;
248 quote!(#[#module(crate = #root::#module)])
249 }
250 Naming::Scoped | Naming::ExternOnly => {
251 unreachable!("`{krate}` has no crate-path attribute")
252 }
253 }
254}
255
256fn extern_only_error(span: Span, krate: &str) -> TokenStream2 {
257 let message = format!(
258 "`{krate}` derives only work when `{krate}` is a direct dependency: \
259 the code they generate names `::{krate}` absolutely, which resolves \
260 only against direct dependencies. Add `{krate}` to your Cargo.toml \
261 alongside `rustmax`, or use `derive_more` instead, as in \
262 `#[rmx::derive(Display, derive_more::Error)]`."
263 );
264 error(span, &message)
265}
266
267struct RustmaxPath {
269 tokens: TokenStream2,
270 text: String,
271}
272
273fn rustmax_path() -> Result<RustmaxPath, TokenStream2> {
274 match crate_name("rustmax") {
275 Ok(FoundCrate::Itself) if in_doctest() => Ok(RustmaxPath {
276 tokens: quote!(::rustmax),
277 text: "::rustmax".to_owned(),
278 }),
279 Ok(FoundCrate::Itself) => Ok(RustmaxPath {
280 tokens: quote!(crate),
281 text: "crate".to_owned(),
282 }),
283 Ok(FoundCrate::Name(name)) => {
284 let ident = Ident::new(&name, Span::call_site());
285 Ok(RustmaxPath {
286 tokens: quote!(::#ident),
287 text: format!("::{name}"),
288 })
289 }
290 Err(_) => Err(error(
291 Span::call_site(),
292 "`#[rmx::derive]` requires `rustmax` to be a direct dependency \
293 of this crate",
294 )),
295 }
296}
297
298fn in_doctest() -> bool {
305 std::env::var_os("UNSTABLE_RUSTDOC_TEST_PATH").is_some()
306}
307
308fn direct_path(krate: &str) -> Option<TokenStream2> {
317 match crate_name(krate) {
318 Ok(FoundCrate::Itself) => Some(quote!(crate)),
319 Ok(FoundCrate::Name(name)) => {
320 let ident = Ident::new(&name, Span::call_site());
321 Some(quote!(::#ident))
322 }
323 Err(_) => None,
324 }
325}
326
327fn lookup(name: &str) -> Option<&'static str> {
328 KNOWN_DERIVES
329 .iter()
330 .find(|(known, _)| *known == name)
331 .map(|(_, krate)| *krate)
332}
333
334fn lookup_crate(name: &str) -> Option<&'static str> {
335 KNOWN_DERIVES
336 .iter()
337 .find(|(_, krate)| *krate == name)
338 .map(|(_, krate)| *krate)
339}
340
341enum Request {
343 Bare(Ident),
345 Qualified(Ident, Ident),
347 Passthrough(TokenStream2),
349}
350
351fn parse_requests(attr: TokenStream2) -> Result<Vec<Request>, TokenStream2> {
352 let mut requests = Vec::new();
353
354 for segment in split_commas(attr) {
355 let idents: Vec<&TokenTree> = segment.iter().collect();
356 match idents.as_slice() {
357 [] => continue,
358 [TokenTree::Ident(name)] => requests.push(Request::Bare(name.clone())),
359 [
360 TokenTree::Ident(krate),
361 TokenTree::Punct(first),
362 TokenTree::Punct(second),
363 TokenTree::Ident(name),
364 ] if first.as_char() == ':' && second.as_char() == ':' => {
365 requests.push(Request::Qualified(krate.clone(), name.clone()));
366 }
367 _ => {
368 let span = segment
369 .first()
370 .map(|tree| tree.span())
371 .unwrap_or_else(Span::call_site);
372 if segment.iter().any(|tree| matches!(tree, TokenTree::Ident(_))) {
373 requests.push(Request::Passthrough(segment.into_iter().collect()));
374 } else {
375 return Err(error(
376 span,
377 "expected a derive macro name, as in \
378 `#[rmx::derive(Serialize, Clone)]`",
379 ));
380 }
381 }
382 }
383 }
384
385 Ok(requests)
386}
387
388fn split_commas(input: TokenStream2) -> Vec<Vec<TokenTree>> {
389 let mut segments = vec![Vec::new()];
390
391 for tree in input {
392 match &tree {
393 TokenTree::Punct(punct) if punct.as_char() == ',' => segments.push(Vec::new()),
394 _ => segments
395 .last_mut()
396 .expect("`segments` is never empty")
397 .push(tree),
398 }
399 }
400
401 segments
402}
403
404fn error(span: Span, message: &str) -> TokenStream2 {
405 quote_spanned!(span=> ::core::compile_error!{#message})
406}
407
408fn prepend(errors: TokenStream2, item: TokenStream2) -> TokenStream2 {
411 quote! {
412 #errors
413 #item
414 }
415}