]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_macros/src/session_diagnostic.rs
New upstream version 1.58.1+dfsg1
[rustc.git] / compiler / rustc_macros / src / session_diagnostic.rs
CommitLineData
1b1a35ee
XL
1#![deny(unused_must_use)]
2use proc_macro::Diagnostic;
3use quote::{format_ident, quote};
4use syn::spanned::Spanned;
5
6use 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/// ```
42pub 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.
54fn 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.
69struct SessionDiagnosticDerive<'a> {
70 structure: synstructure::Structure<'a>,
71 builder: SessionDiagnosticDeriveBuilder<'a>,
72}
73
74impl 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.
82enum DiagnosticId {
83 Error(proc_macro2::TokenStream),
84 Lint(proc_macro2::TokenStream),
85}
86
87#[derive(Debug)]
88enum SessionDiagnosticDeriveError {
89 SynError(syn::Error),
90 ErrorHandled,
91}
92
93impl 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
108fn 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).
115macro_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.
124fn _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
134impl<'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.
245struct 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.
256struct 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
273impl<'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.
651fn 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}