1 #![deny(unused_must_use)]
2 use proc_macro
::Diagnostic
;
3 use quote
::{format_ident, quote}
;
4 use syn
::spanned
::Spanned
;
6 use std
::collections
::{BTreeSet, HashMap}
;
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)]
19 /// #[error = "cannot move out of {name} because it is borrowed"]
20 /// pub struct MoveOutOfBorrowError<'tcx> {
23 /// #[label = "cannot move out of borrow"]
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)>
31 /// Then, later, to emit the error:
33 /// ```ignore (pseudo-rust)
34 /// sess.emit_err(MoveOutOfBorrowError {
39 /// opt_sugg: Some(suggestion, Applicability::MachineApplicable),
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");
47 SessionDiagnosticDerive
::new(diag
, sess
, s
).into_tokens()
50 // Checks whether the type name of `ty` matches `name`.
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
{
59 .map(|s
| s
.ident
.to_string())
61 .zip(name
.iter().rev())
62 .all(|(x
, y
)| &x
.as_str() == y
)
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
>,
74 impl std
::convert
::From
<syn
::Error
> for SessionDiagnosticDeriveError
{
75 fn from(e
: syn
::Error
) -> Self {
76 SessionDiagnosticDeriveError
::SynError(e
)
80 /// Equivalent to rustc:errors::diagnostic::DiagnosticId, except stores the quoted expression to
81 /// initialise the code with.
83 Error(proc_macro2
::TokenStream
),
84 Lint(proc_macro2
::TokenStream
),
88 enum SessionDiagnosticDeriveError
{
93 impl SessionDiagnosticDeriveError
{
94 fn to_compile_error(self) -> proc_macro2
::TokenStream
{
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.
108 fn span_err(span
: impl proc_macro
::MultiSpan
, msg
: &str) -> proc_macro
::Diagnostic
{
109 Diagnostic
::spanned(span
, proc_macro
::Level
::Error
, msg
)
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
));
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.
125 span
: impl proc_macro
::MultiSpan
,
127 f
: impl FnOnce(proc_macro
::Diagnostic
) -> proc_macro
::Diagnostic
,
128 ) -> SessionDiagnosticDeriveError
{
129 let diag
= span_err(span
, msg
);
131 SessionDiagnosticDeriveError
::ErrorHandled
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
138 let mut fields_map
= HashMap
::new();
140 // Convenience bindings.
141 let ast
= structure
.ast();
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
);
152 builder
: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None }
,
156 fn into_tokens(self) -> proc_macro2
::TokenStream
{
157 let SessionDiagnosticDerive { structure, mut builder }
= self;
159 let ast
= structure
.ast();
160 let attrs
= &ast
.attrs
;
162 let implementation
= {
163 if let syn
::Data
::Struct(..) = ast
.data
{
165 let preamble
= attrs
.iter().map(|attr
| {
167 .generate_structure_code(attr
)
168 .unwrap_or_else(|v
| v
.to_compile_error())
175 let body
= structure
.each(|field_binding
| {
176 let field
= field_binding
.ast();
177 let result
= field
.attrs
.iter().map(|attr
| {
179 .generate_field_code(
183 binding
: field_binding
,
188 .unwrap_or_else(|v
| v
.to_compile_error())
194 // Finally, putting it altogether.
197 span_err(ast
.span().unwrap(), "`code` not specified")
198 .help("use the [code = \"...\"] attribute to set this diagnostic's error code ")
200 SessionDiagnosticDeriveError
::ErrorHandled
.to_compile_error()
202 Some((kind
, _
)) => match kind
{
203 DiagnosticId
::Lint(_lint
) => todo
!(),
204 DiagnosticId
::Error(code
) => {
205 let (diag
, sess
) = (&builder
.diag
, &builder
.sess
);
207 let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
220 "`#[derive(SessionDiagnostic)]` can only be used on structs",
223 SessionDiagnosticDeriveError
::ErrorHandled
.to_compile_error()
227 let sess
= &builder
.sess
;
228 structure
.gen_impl(quote
! {
229 gen
impl<'__session_diagnostic_sess
> rustc_session
::SessionDiagnostic
<'__session_diagnostic_sess
>
234 #sess: &'__session_diagnostic_sess rustc_session::Session
235 ) -> rustc_errors
::DiagnosticBuilder
<'__session_diagnostic_sess
> {
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
>,
249 span
: &'a proc_macro2
::Span
,
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
256 struct SessionDiagnosticDeriveBuilder
<'a
> {
257 /// Name of the session parameter that's passed in to the as_error method.
260 /// Store a map of field name to its corresponding field. This is built on construction of the
262 fields
: HashMap
<String
, &'a syn
::Field
>,
264 /// The identifier to use for the generated DiagnosticBuilder instance.
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
)>,
273 impl<'a
> SessionDiagnosticDeriveBuilder
<'a
> {
274 fn generate_structure_code(
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();
285 let diag
= &self.diag
;
287 #diag.set_primary_message(#formatted_str);
290 attr @
"error" | attr @
"lint" => {
293 DiagnosticId
::Error(formatted_str
)
294 } else if attr
== "lint" {
295 DiagnosticId
::Lint(formatted_str
)
301 // This attribute is only allowed to be applied once, and the attribute
302 // will be set in the initialisation code.
305 other
=> throw_span_err
!(
306 attr
.span().unwrap(),
308 "`#[{} = ...]` is not a valid SessionDiagnostic struct attribute",
314 _
=> todo
!("unhandled meta kind"),
322 span
: proc_macro2
::Span
,
323 ) -> Result
<(), SessionDiagnosticDeriveError
> {
324 if self.kind
.is_none() {
325 self.kind
= Some((kind
, span
));
328 let kind_str
= |kind
: &DiagnosticId
| match kind
{
329 DiagnosticId
::Lint(..) => "lint",
330 DiagnosticId
::Error(..) => "error",
333 let existing_kind
= kind_str(&self.kind
.as_ref().unwrap().0);
334 let this_kind
= kind_str(&kind
);
336 let msg
= if this_kind
== existing_kind
{
337 format
!("`{}` specified multiple times", existing_kind
)
339 format
!("`{}` specified when `{}` was already specified", this_kind
, existing_kind
)
341 throw_span_err
!(span
.unwrap(), &msg
);
345 fn generate_field_code(
347 attr
: &syn
::Attribute
,
349 ) -> Result
<proc_macro2
::TokenStream
, SessionDiagnosticDeriveError
> {
350 let field_binding
= &info
.binding
.binding
;
352 let option_ty
= option_inner_ty(&info
.ty
);
354 let generated_code
= self.generate_non_option_field_code(
358 binding
: info
.binding
,
359 ty
: option_ty
.unwrap_or(&info
.ty
),
363 Ok(if option_ty
.is_none() {
364 quote
! { #generated_code }
367 if let Some(#field_binding) = #field_binding {
374 fn generate_non_option_field_code(
376 attr
: &syn
::Attribute
,
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
385 let meta
= attr
.parse_meta()?
;
387 syn
::Meta
::NameValue(syn
::MetaNameValue { lit: syn::Lit::Str(s), .. }
) => {
388 let formatted_str
= self.build_format(&s
.value(), attr
.span());
391 if type_matches_path(&info
.ty
, &["rustc_span", "Span"]) {
393 #diag.set_span(*#field_binding);
394 #diag.set_primary_message(#formatted_str);
398 attr
.span().unwrap(),
399 "the `#[message = \"...\"]` attribute can only be applied to fields of type Span"
404 if type_matches_path(&info
.ty
, &["rustc_span", "Span"]) {
406 #diag.span_label(*#field_binding, #formatted_str);
410 attr
.span().unwrap(),
411 "The `#[label = ...]` attribute can only be applied to fields of type Span"
415 other
=> throw_span_err
!(
416 attr
.span().unwrap(),
418 "`#[{} = ...]` is not a valid SessionDiagnostic field attribute",
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"]) =>
436 let binding
= &info
.binding
.binding
;
439 quote
!(rustc_errors
::Applicability
::Unspecified
),
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
));
451 info
.span
.clone().unwrap(),
452 "type of field annotated with `#[suggestion(...)]` contains more than one Span"
455 } else if type_matches_path(
457 &["rustc_errors", "Applicability"],
459 if applicability_idx
.is_none() {
460 applicability_idx
= Some(syn
::Index
::from(idx
));
463 info
.span
.clone().unwrap(),
464 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
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
474 |applicability_idx
| quote
!(#binding.#applicability_idx),
477 rustc_errors
::Applicability
::Unspecified
479 return Ok((span
, applicability
));
482 info
.span
.clone().unwrap(),
483 "wrong types for suggestion",
485 diag
.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)")
489 _
=> throw_span_err
!(
490 info
.span
.clone().unwrap(),
491 "wrong field type for suggestion",
493 diag
.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)")
497 // Now read the key-value pairs.
501 for arg
in list
.nested
.iter() {
502 if let syn
::NestedMeta
::Meta(syn
::Meta
::NameValue(arg_name_value
)) = arg
504 if let syn
::MetaNameValue { lit: syn::Lit::Str(s), .. }
=
507 let name
= arg_name_value
514 let name
= name
.as_str();
515 let formatted_str
= self.build_format(&s
.value(), arg
.span());
518 msg
= Some(formatted_str
);
521 code
= Some(formatted_str
);
523 other
=> throw_span_err
!(
526 "`{}` is not a valid key for `#[suggestion(...)]`",
534 let msg
= if let Some(msg
) = msg
{
535 quote
!(#msg.as_str())
538 list
.span().unwrap(),
539 "missing suggestion message",
541 diag
.help("provide a suggestion message using #[suggestion(message = \"...\")]")
545 let code
= code
.unwrap_or_else(|| quote
! { String::new() }
);
547 let suggestion_method
= format_ident
!("span_{}", suggestion_kind
);
549 #diag.#suggestion_method(#span, #msg, #code, #applicability);
552 other
=> throw_span_err
!(
553 list
.span().unwrap(),
554 &format
!("invalid annotation list `#[{}(...)]`", other
)
558 _
=> panic
!("unhandled meta kind"),
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)
566 /// #[error = "Expected a point greater than ({x}, {y})"]
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)
576 /// This function builds the entire call to format!.
577 fn build_format(&self, input
: &String
, span
: proc_macro2
::Span
) -> proc_macro2
::TokenStream
{
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
581 let mut referenced_fields
: BTreeSet
<String
> = BTreeSet
::new();
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'
) != '
{'
{
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() {
598 let next
= *it
.peek().unwrap_or(&'
\0'
);
601 } else if next
== '
:'
{
602 // Eat the ':' character.
603 assert_eq
!(it
.next().unwrap(), '
:'
);
607 // Eat until (and including) the matching '}'
608 while it
.next()?
!= '
}'
{
614 if let Some(referenced_field
) = eat_argument() {
615 referenced_fields
.insert(referenced_field
);
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
) {
629 // This field doesn't exist. Emit a diagnostic.
632 proc_macro
::Level
::Error
,
633 format
!("`{}` doesn't refer to a field on this type", field
),
641 #field_ident = #value
645 format
!(#input #(,#args)*)
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] {