]>
Commit | Line | Data |
---|---|---|
1b1a35ee XL |
1 | #![deny(unused_must_use)] |
2 | use proc_macro::Diagnostic; | |
3 | use quote::{format_ident, quote}; | |
4 | use syn::spanned::Spanned; | |
5 | ||
6 | use std::collections::{BTreeSet, HashMap}; | |
7 | ||
8 | /// Implements #[derive(SessionDiagnostic)], which allows for errors to be specified as a struct, independent | |
9 | /// from the actual diagnostics emitting code. | |
10 | /// ```ignore (pseudo-rust) | |
11 | /// # extern crate rustc_errors; | |
12 | /// # use rustc_errors::Applicability; | |
13 | /// # extern crate rustc_span; | |
14 | /// # use rustc_span::{symbol::Ident, Span}; | |
15 | /// # extern crate rust_middle; | |
16 | /// # use rustc_middle::ty::Ty; | |
17 | /// #[derive(SessionDiagnostic)] | |
18 | /// #[code = "E0505"] | |
19 | /// #[error = "cannot move out of {name} because it is borrowed"] | |
20 | /// pub struct MoveOutOfBorrowError<'tcx> { | |
21 | /// pub name: Ident, | |
22 | /// pub ty: Ty<'tcx>, | |
23 | /// #[label = "cannot move out of borrow"] | |
24 | /// pub span: Span, | |
25 | /// #[label = "`{ty}` first borrowed here"] | |
26 | /// pub other_span: Span, | |
27 | /// #[suggestion(message = "consider cloning here", code = "{name}.clone()")] | |
28 | /// pub opt_sugg: Option<(Span, Applicability)> | |
29 | /// } | |
30 | /// ``` | |
31 | /// Then, later, to emit the error: | |
32 | /// | |
33 | /// ```ignore (pseudo-rust) | |
34 | /// sess.emit_err(MoveOutOfBorrowError { | |
35 | /// expected, | |
36 | /// actual, | |
37 | /// span, | |
38 | /// other_span, | |
39 | /// opt_sugg: Some(suggestion, Applicability::MachineApplicable), | |
40 | /// }); | |
41 | /// ``` | |
42 | pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { | |
43 | // Names for the diagnostic we build and the session we build it from. | |
44 | let diag = format_ident!("diag"); | |
45 | let sess = format_ident!("sess"); | |
46 | ||
47 | SessionDiagnosticDerive::new(diag, sess, s).into_tokens() | |
48 | } | |
49 | ||
50 | // Checks whether the type name of `ty` matches `name`. | |
51 | // | |
52 | // Given some struct at a::b::c::Foo, this will return true for c::Foo, b::c::Foo, or | |
53 | // a::b::c::Foo. This reasonably allows qualified names to be used in the macro. | |
54 | fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool { | |
55 | if let syn::Type::Path(ty) = ty { | |
56 | ty.path | |
57 | .segments | |
58 | .iter() | |
59 | .map(|s| s.ident.to_string()) | |
60 | .rev() | |
61 | .zip(name.iter().rev()) | |
62 | .all(|(x, y)| &x.as_str() == y) | |
63 | } else { | |
64 | false | |
65 | } | |
66 | } | |
67 | ||
68 | /// The central struct for constructing the as_error method from an annotated struct. | |
69 | struct SessionDiagnosticDerive<'a> { | |
70 | structure: synstructure::Structure<'a>, | |
71 | builder: SessionDiagnosticDeriveBuilder<'a>, | |
72 | } | |
73 | ||
74 | impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError { | |
75 | fn from(e: syn::Error) -> Self { | |
76 | SessionDiagnosticDeriveError::SynError(e) | |
77 | } | |
78 | } | |
79 | ||
80 | /// Equivalent to rustc:errors::diagnostic::DiagnosticId, except stores the quoted expression to | |
81 | /// initialise the code with. | |
82 | enum DiagnosticId { | |
83 | Error(proc_macro2::TokenStream), | |
84 | Lint(proc_macro2::TokenStream), | |
85 | } | |
86 | ||
87 | #[derive(Debug)] | |
88 | enum SessionDiagnosticDeriveError { | |
89 | SynError(syn::Error), | |
90 | ErrorHandled, | |
91 | } | |
92 | ||
93 | impl SessionDiagnosticDeriveError { | |
94 | fn to_compile_error(self) -> proc_macro2::TokenStream { | |
95 | match self { | |
96 | SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(), | |
97 | SessionDiagnosticDeriveError::ErrorHandled => { | |
98 | // Return ! to avoid having to create a blank DiagnosticBuilder to return when an | |
99 | // error has already been emitted to the compiler. | |
100 | quote! { | |
101 | unreachable!() | |
102 | } | |
103 | } | |
104 | } | |
105 | } | |
106 | } | |
107 | ||
108 | fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic { | |
109 | Diagnostic::spanned(span, proc_macro::Level::Error, msg) | |
110 | } | |
111 | ||
112 | /// For methods that return a Result<_, SessionDiagnosticDeriveError>: emit a diagnostic on | |
113 | /// span $span with msg $msg (and, optionally, perform additional decoration using the FnOnce | |
114 | /// passed in `diag`). Then, return Err(ErrorHandled). | |
115 | macro_rules! throw_span_err { | |
116 | ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }}; | |
117 | ($span:expr, $msg:expr, $f:expr) => {{ | |
118 | return Err(_throw_span_err($span, $msg, $f)); | |
119 | }}; | |
120 | } | |
121 | ||
122 | /// When possible, prefer using throw_span_err! over using this function directly. This only exists | |
123 | /// as a function to constrain `f` to an impl FnOnce. | |
124 | fn _throw_span_err( | |
125 | span: impl proc_macro::MultiSpan, | |
126 | msg: &str, | |
127 | f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic, | |
128 | ) -> SessionDiagnosticDeriveError { | |
129 | let diag = span_err(span, msg); | |
130 | f(diag).emit(); | |
131 | SessionDiagnosticDeriveError::ErrorHandled | |
132 | } | |
133 | ||
134 | impl<'a> SessionDiagnosticDerive<'a> { | |
135 | fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self { | |
136 | // Build the mapping of field names to fields. This allows attributes to peek values from | |
137 | // other fields. | |
138 | let mut fields_map = HashMap::new(); | |
139 | ||
140 | // Convenience bindings. | |
141 | let ast = structure.ast(); | |
142 | ||
143 | if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data { | |
144 | for field in fields.iter() { | |
145 | if let Some(ident) = &field.ident { | |
146 | fields_map.insert(ident.to_string(), field); | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | Self { | |
152 | builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None }, | |
153 | structure, | |
154 | } | |
155 | } | |
156 | fn into_tokens(self) -> proc_macro2::TokenStream { | |
157 | let SessionDiagnosticDerive { structure, mut builder } = self; | |
158 | ||
159 | let ast = structure.ast(); | |
160 | let attrs = &ast.attrs; | |
161 | ||
162 | let implementation = { | |
163 | if let syn::Data::Struct(..) = ast.data { | |
164 | let preamble = { | |
165 | let preamble = attrs.iter().map(|attr| { | |
166 | builder | |
167 | .generate_structure_code(attr) | |
168 | .unwrap_or_else(|v| v.to_compile_error()) | |
169 | }); | |
170 | quote! { | |
171 | #(#preamble)*; | |
172 | } | |
173 | }; | |
174 | ||
175 | let body = structure.each(|field_binding| { | |
176 | let field = field_binding.ast(); | |
177 | let result = field.attrs.iter().map(|attr| { | |
178 | builder | |
179 | .generate_field_code( | |
180 | attr, | |
181 | FieldInfo { | |
182 | vis: &field.vis, | |
183 | binding: field_binding, | |
184 | ty: &field.ty, | |
185 | span: &field.span(), | |
186 | }, | |
187 | ) | |
188 | .unwrap_or_else(|v| v.to_compile_error()) | |
189 | }); | |
190 | return quote! { | |
191 | #(#result);* | |
192 | }; | |
193 | }); | |
194 | // Finally, putting it altogether. | |
195 | match builder.kind { | |
196 | None => { | |
197 | span_err(ast.span().unwrap(), "`code` not specified") | |
198 | .help("use the [code = \"...\"] attribute to set this diagnostic's error code ") | |
199 | .emit(); | |
200 | SessionDiagnosticDeriveError::ErrorHandled.to_compile_error() | |
201 | } | |
202 | Some((kind, _)) => match kind { | |
203 | DiagnosticId::Lint(_lint) => todo!(), | |
204 | DiagnosticId::Error(code) => { | |
205 | let (diag, sess) = (&builder.diag, &builder.sess); | |
206 | quote! { | |
207 | let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code)); | |
208 | #preamble | |
209 | match self { | |
210 | #body | |
211 | } | |
212 | #diag | |
213 | } | |
214 | } | |
215 | }, | |
216 | } | |
217 | } else { | |
218 | span_err( | |
219 | ast.span().unwrap(), | |
220 | "`#[derive(SessionDiagnostic)]` can only be used on structs", | |
221 | ) | |
222 | .emit(); | |
223 | SessionDiagnosticDeriveError::ErrorHandled.to_compile_error() | |
224 | } | |
225 | }; | |
226 | ||
227 | let sess = &builder.sess; | |
228 | structure.gen_impl(quote! { | |
229 | gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess> | |
230 | for @Self | |
231 | { | |
232 | fn into_diagnostic( | |
233 | self, | |
234 | #sess: &'__session_diagnostic_sess rustc_session::Session | |
235 | ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess> { | |
236 | #implementation | |
237 | } | |
238 | } | |
239 | }) | |
240 | } | |
241 | } | |
242 | ||
243 | /// Field information passed to the builder. Deliberately omits attrs to discourage the generate_* | |
244 | /// methods from walking the attributes themselves. | |
245 | struct FieldInfo<'a> { | |
246 | vis: &'a syn::Visibility, | |
247 | binding: &'a synstructure::BindingInfo<'a>, | |
248 | ty: &'a syn::Type, | |
249 | span: &'a proc_macro2::Span, | |
250 | } | |
251 | ||
252 | /// Tracks persistent information required for building up the individual calls to diagnostic | |
253 | /// methods for the final generated method. This is a separate struct to SessionDerive only to be | |
254 | /// able to destructure and split self.builder and the self.structure up to avoid a double mut | |
255 | /// borrow later on. | |
256 | struct SessionDiagnosticDeriveBuilder<'a> { | |
257 | /// Name of the session parameter that's passed in to the as_error method. | |
258 | sess: syn::Ident, | |
259 | ||
260 | /// Store a map of field name to its corresponding field. This is built on construction of the | |
261 | /// derive builder. | |
262 | fields: HashMap<String, &'a syn::Field>, | |
263 | ||
264 | /// The identifier to use for the generated DiagnosticBuilder instance. | |
265 | diag: syn::Ident, | |
266 | ||
267 | /// Whether this is a lint or an error. This dictates how the diag will be initialised. Span | |
268 | /// stores at what Span the kind was first set at (for error reporting purposes, if the kind | |
269 | /// was multiply specified). | |
270 | kind: Option<(DiagnosticId, proc_macro2::Span)>, | |
271 | } | |
272 | ||
273 | impl<'a> SessionDiagnosticDeriveBuilder<'a> { | |
274 | fn generate_structure_code( | |
275 | &mut self, | |
276 | attr: &syn::Attribute, | |
277 | ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> { | |
278 | Ok(match attr.parse_meta()? { | |
279 | syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => { | |
280 | let formatted_str = self.build_format(&s.value(), attr.span()); | |
281 | let name = attr.path.segments.last().unwrap().ident.to_string(); | |
282 | let name = name.as_str(); | |
283 | match name { | |
284 | "message" => { | |
285 | let diag = &self.diag; | |
286 | quote! { | |
287 | #diag.set_primary_message(#formatted_str); | |
288 | } | |
289 | } | |
290 | attr @ "error" | attr @ "lint" => { | |
291 | self.set_kind_once( | |
292 | if attr == "error" { | |
293 | DiagnosticId::Error(formatted_str) | |
294 | } else if attr == "lint" { | |
295 | DiagnosticId::Lint(formatted_str) | |
296 | } else { | |
297 | unreachable!() | |
298 | }, | |
299 | s.span(), | |
300 | )?; | |
301 | // This attribute is only allowed to be applied once, and the attribute | |
302 | // will be set in the initialisation code. | |
303 | quote! {} | |
304 | } | |
305 | other => throw_span_err!( | |
306 | attr.span().unwrap(), | |
307 | &format!( | |
308 | "`#[{} = ...]` is not a valid SessionDiagnostic struct attribute", | |
309 | other | |
310 | ) | |
311 | ), | |
312 | } | |
313 | } | |
314 | _ => todo!("unhandled meta kind"), | |
315 | }) | |
316 | } | |
317 | ||
318 | #[must_use] | |
319 | fn set_kind_once( | |
320 | &mut self, | |
321 | kind: DiagnosticId, | |
322 | span: proc_macro2::Span, | |
323 | ) -> Result<(), SessionDiagnosticDeriveError> { | |
324 | if self.kind.is_none() { | |
325 | self.kind = Some((kind, span)); | |
326 | Ok(()) | |
327 | } else { | |
328 | let kind_str = |kind: &DiagnosticId| match kind { | |
329 | DiagnosticId::Lint(..) => "lint", | |
330 | DiagnosticId::Error(..) => "error", | |
331 | }; | |
332 | ||
333 | let existing_kind = kind_str(&self.kind.as_ref().unwrap().0); | |
334 | let this_kind = kind_str(&kind); | |
335 | ||
336 | let msg = if this_kind == existing_kind { | |
337 | format!("`{}` specified multiple times", existing_kind) | |
338 | } else { | |
339 | format!("`{}` specified when `{}` was already specified", this_kind, existing_kind) | |
340 | }; | |
341 | throw_span_err!(span.unwrap(), &msg); | |
342 | } | |
343 | } | |
344 | ||
345 | fn generate_field_code( | |
346 | &mut self, | |
347 | attr: &syn::Attribute, | |
348 | info: FieldInfo<'_>, | |
349 | ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> { | |
350 | let field_binding = &info.binding.binding; | |
351 | ||
3c0e092e | 352 | let option_ty = option_inner_ty(&info.ty); |
1b1a35ee XL |
353 | |
354 | let generated_code = self.generate_non_option_field_code( | |
355 | attr, | |
356 | FieldInfo { | |
357 | vis: info.vis, | |
358 | binding: info.binding, | |
3c0e092e | 359 | ty: option_ty.unwrap_or(&info.ty), |
1b1a35ee XL |
360 | span: info.span, |
361 | }, | |
362 | )?; | |
363 | Ok(if option_ty.is_none() { | |
364 | quote! { #generated_code } | |
365 | } else { | |
366 | quote! { | |
367 | if let Some(#field_binding) = #field_binding { | |
368 | #generated_code | |
369 | } | |
370 | } | |
371 | }) | |
372 | } | |
373 | ||
374 | fn generate_non_option_field_code( | |
375 | &mut self, | |
376 | attr: &syn::Attribute, | |
377 | info: FieldInfo<'_>, | |
378 | ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> { | |
379 | let diag = &self.diag; | |
380 | let field_binding = &info.binding.binding; | |
381 | let name = attr.path.segments.last().unwrap().ident.to_string(); | |
382 | let name = name.as_str(); | |
383 | // At this point, we need to dispatch based on the attribute key + the | |
384 | // type. | |
385 | let meta = attr.parse_meta()?; | |
386 | Ok(match meta { | |
387 | syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => { | |
388 | let formatted_str = self.build_format(&s.value(), attr.span()); | |
389 | match name { | |
390 | "message" => { | |
3c0e092e | 391 | if type_matches_path(&info.ty, &["rustc_span", "Span"]) { |
1b1a35ee XL |
392 | quote! { |
393 | #diag.set_span(*#field_binding); | |
394 | #diag.set_primary_message(#formatted_str); | |
395 | } | |
396 | } else { | |
397 | throw_span_err!( | |
398 | attr.span().unwrap(), | |
399 | "the `#[message = \"...\"]` attribute can only be applied to fields of type Span" | |
400 | ); | |
401 | } | |
402 | } | |
403 | "label" => { | |
3c0e092e | 404 | if type_matches_path(&info.ty, &["rustc_span", "Span"]) { |
1b1a35ee XL |
405 | quote! { |
406 | #diag.span_label(*#field_binding, #formatted_str); | |
407 | } | |
408 | } else { | |
409 | throw_span_err!( | |
410 | attr.span().unwrap(), | |
411 | "The `#[label = ...]` attribute can only be applied to fields of type Span" | |
412 | ); | |
413 | } | |
414 | } | |
415 | other => throw_span_err!( | |
416 | attr.span().unwrap(), | |
417 | &format!( | |
418 | "`#[{} = ...]` is not a valid SessionDiagnostic field attribute", | |
419 | other | |
420 | ) | |
421 | ), | |
422 | } | |
423 | } | |
424 | syn::Meta::List(list) => { | |
425 | match list.path.segments.iter().last().unwrap().ident.to_string().as_str() { | |
426 | suggestion_kind @ "suggestion" | |
427 | | suggestion_kind @ "suggestion_short" | |
428 | | suggestion_kind @ "suggestion_hidden" | |
429 | | suggestion_kind @ "suggestion_verbose" => { | |
430 | // For suggest, we need to ensure we are running on a (Span, | |
431 | // Applicability) pair. | |
432 | let (span, applicability) = (|| match &info.ty { | |
433 | ty @ syn::Type::Path(..) | |
434 | if type_matches_path(ty, &["rustc_span", "Span"]) => | |
435 | { | |
436 | let binding = &info.binding.binding; | |
437 | Ok(( | |
438 | quote!(*#binding), | |
439 | quote!(rustc_errors::Applicability::Unspecified), | |
440 | )) | |
441 | } | |
442 | syn::Type::Tuple(tup) => { | |
443 | let mut span_idx = None; | |
444 | let mut applicability_idx = None; | |
445 | for (idx, elem) in tup.elems.iter().enumerate() { | |
446 | if type_matches_path(elem, &["rustc_span", "Span"]) { | |
447 | if span_idx.is_none() { | |
448 | span_idx = Some(syn::Index::from(idx)); | |
449 | } else { | |
450 | throw_span_err!( | |
c295e0f8 | 451 | info.span.unwrap(), |
1b1a35ee XL |
452 | "type of field annotated with `#[suggestion(...)]` contains more than one Span" |
453 | ); | |
454 | } | |
455 | } else if type_matches_path( | |
456 | elem, | |
457 | &["rustc_errors", "Applicability"], | |
458 | ) { | |
459 | if applicability_idx.is_none() { | |
460 | applicability_idx = Some(syn::Index::from(idx)); | |
461 | } else { | |
462 | throw_span_err!( | |
c295e0f8 | 463 | info.span.unwrap(), |
1b1a35ee XL |
464 | "type of field annotated with `#[suggestion(...)]` contains more than one Applicability" |
465 | ); | |
466 | } | |
467 | } | |
468 | } | |
469 | if let Some(span_idx) = span_idx { | |
470 | let binding = &info.binding.binding; | |
471 | let span = quote!(#binding.#span_idx); | |
472 | let applicability = applicability_idx | |
473 | .map( | |
474 | |applicability_idx| quote!(#binding.#applicability_idx), | |
475 | ) | |
6a06907d XL |
476 | .unwrap_or_else(|| { |
477 | quote!(rustc_errors::Applicability::Unspecified) | |
478 | }); | |
1b1a35ee XL |
479 | return Ok((span, applicability)); |
480 | } | |
481 | throw_span_err!( | |
c295e0f8 | 482 | info.span.unwrap(), |
1b1a35ee XL |
483 | "wrong types for suggestion", |
484 | |diag| { | |
485 | diag.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)") | |
486 | } | |
487 | ); | |
488 | } | |
489 | _ => throw_span_err!( | |
c295e0f8 | 490 | info.span.unwrap(), |
1b1a35ee XL |
491 | "wrong field type for suggestion", |
492 | |diag| { | |
493 | diag.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)") | |
494 | } | |
495 | ), | |
496 | })()?; | |
497 | // Now read the key-value pairs. | |
498 | let mut msg = None; | |
499 | let mut code = None; | |
500 | ||
501 | for arg in list.nested.iter() { | |
502 | if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg | |
503 | { | |
504 | if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } = | |
505 | arg_name_value | |
506 | { | |
507 | let name = arg_name_value | |
508 | .path | |
509 | .segments | |
510 | .last() | |
511 | .unwrap() | |
512 | .ident | |
513 | .to_string(); | |
514 | let name = name.as_str(); | |
515 | let formatted_str = self.build_format(&s.value(), arg.span()); | |
516 | match name { | |
517 | "message" => { | |
518 | msg = Some(formatted_str); | |
519 | } | |
520 | "code" => { | |
521 | code = Some(formatted_str); | |
522 | } | |
523 | other => throw_span_err!( | |
524 | arg.span().unwrap(), | |
525 | &format!( | |
526 | "`{}` is not a valid key for `#[suggestion(...)]`", | |
527 | other | |
528 | ) | |
529 | ), | |
530 | } | |
531 | } | |
532 | } | |
533 | } | |
534 | let msg = if let Some(msg) = msg { | |
535 | quote!(#msg.as_str()) | |
536 | } else { | |
537 | throw_span_err!( | |
538 | list.span().unwrap(), | |
539 | "missing suggestion message", | |
540 | |diag| { | |
541 | diag.help("provide a suggestion message using #[suggestion(message = \"...\")]") | |
542 | } | |
543 | ); | |
544 | }; | |
545 | let code = code.unwrap_or_else(|| quote! { String::new() }); | |
546 | // Now build it out: | |
547 | let suggestion_method = format_ident!("span_{}", suggestion_kind); | |
548 | quote! { | |
549 | #diag.#suggestion_method(#span, #msg, #code, #applicability); | |
550 | } | |
551 | } | |
552 | other => throw_span_err!( | |
553 | list.span().unwrap(), | |
554 | &format!("invalid annotation list `#[{}(...)]`", other) | |
555 | ), | |
556 | } | |
557 | } | |
558 | _ => panic!("unhandled meta kind"), | |
559 | }) | |
560 | } | |
561 | ||
562 | /// In the strings in the attributes supplied to this macro, we want callers to be able to | |
563 | /// reference fields in the format string. Take this, for example: | |
564 | /// ```ignore (not-usage-example) | |
565 | /// struct Point { | |
566 | /// #[error = "Expected a point greater than ({x}, {y})"] | |
567 | /// x: i32, | |
568 | /// y: i32, | |
569 | /// } | |
570 | /// ``` | |
571 | /// We want to automatically pick up that {x} refers `self.x` and {y} refers to `self.y`, then | |
572 | /// generate this call to format!: | |
573 | /// ```ignore (not-usage-example) | |
574 | /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y) | |
575 | /// ``` | |
576 | /// This function builds the entire call to format!. | |
5869c6ff | 577 | fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream { |
1b1a35ee XL |
578 | // This set is used later to generate the final format string. To keep builds reproducible, |
579 | // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead | |
580 | // of a HashSet. | |
581 | let mut referenced_fields: BTreeSet<String> = BTreeSet::new(); | |
582 | ||
583 | // At this point, we can start parsing the format string. | |
584 | let mut it = input.chars().peekable(); | |
585 | // Once the start of a format string has been found, process the format string and spit out | |
586 | // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so the | |
587 | // next call to `it.next()` retrieves the next character. | |
588 | while let Some(c) = it.next() { | |
589 | if c == '{' && *it.peek().unwrap_or(&'\0') != '{' { | |
590 | #[must_use] | |
591 | let mut eat_argument = || -> Option<String> { | |
592 | let mut result = String::new(); | |
593 | // Format specifiers look like | |
594 | // format := '{' [ argument ] [ ':' format_spec ] '}' . | |
595 | // Therefore, we only need to eat until ':' or '}' to find the argument. | |
596 | while let Some(c) = it.next() { | |
597 | result.push(c); | |
598 | let next = *it.peek().unwrap_or(&'\0'); | |
599 | if next == '}' { | |
600 | break; | |
601 | } else if next == ':' { | |
602 | // Eat the ':' character. | |
603 | assert_eq!(it.next().unwrap(), ':'); | |
604 | break; | |
605 | } | |
606 | } | |
607 | // Eat until (and including) the matching '}' | |
608 | while it.next()? != '}' { | |
609 | continue; | |
610 | } | |
611 | Some(result) | |
612 | }; | |
613 | ||
614 | if let Some(referenced_field) = eat_argument() { | |
615 | referenced_fields.insert(referenced_field); | |
616 | } | |
617 | } | |
618 | } | |
619 | // At this point, `referenced_fields` contains a set of the unique fields that were | |
620 | // referenced in the format string. Generate the corresponding "x = self.x" format | |
621 | // string parameters: | |
622 | let args = referenced_fields.into_iter().map(|field: String| { | |
623 | let field_ident = format_ident!("{}", field); | |
624 | let value = if self.fields.contains_key(&field) { | |
625 | quote! { | |
626 | &self.#field_ident | |
627 | } | |
628 | } else { | |
629 | // This field doesn't exist. Emit a diagnostic. | |
630 | Diagnostic::spanned( | |
631 | span.unwrap(), | |
632 | proc_macro::Level::Error, | |
633 | format!("`{}` doesn't refer to a field on this type", field), | |
634 | ) | |
635 | .emit(); | |
636 | quote! { | |
637 | "{#field}" | |
638 | } | |
639 | }; | |
640 | quote! { | |
641 | #field_ident = #value | |
642 | } | |
643 | }); | |
644 | quote! { | |
645 | format!(#input #(,#args)*) | |
646 | } | |
647 | } | |
648 | } | |
649 | ||
650 | /// If `ty` is an Option, returns Some(inner type). Else, returns None. | |
651 | fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> { | |
652 | if type_matches_path(ty, &["std", "option", "Option"]) { | |
653 | if let syn::Type::Path(ty_path) = ty { | |
654 | let path = &ty_path.path; | |
655 | let ty = path.segments.iter().last().unwrap(); | |
656 | if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments { | |
657 | if bracketed.args.len() == 1 { | |
658 | if let syn::GenericArgument::Type(ty) = &bracketed.args[0] { | |
659 | return Some(ty); | |
660 | } | |
661 | } | |
662 | } | |
663 | } | |
664 | } | |
665 | None | |
666 | } |