+++ /dev/null
-#![deny(unused_must_use)]
-use proc_macro::Diagnostic;
-use quote::{format_ident, quote};
-use syn::spanned::Spanned;
-
-use std::collections::{BTreeSet, HashMap};
-
-/// Implements #[derive(SessionDiagnostic)], which allows for errors to be specified as a struct, independent
-/// from the actual diagnostics emitting code.
-/// ```ignore (pseudo-rust)
-/// # extern crate rustc_errors;
-/// # use rustc_errors::Applicability;
-/// # extern crate rustc_span;
-/// # use rustc_span::{symbol::Ident, Span};
-/// # extern crate rust_middle;
-/// # use rustc_middle::ty::Ty;
-/// #[derive(SessionDiagnostic)]
-/// #[code = "E0505"]
-/// #[error = "cannot move out of {name} because it is borrowed"]
-/// pub struct MoveOutOfBorrowError<'tcx> {
-/// pub name: Ident,
-/// pub ty: Ty<'tcx>,
-/// #[label = "cannot move out of borrow"]
-/// pub span: Span,
-/// #[label = "`{ty}` first borrowed here"]
-/// pub other_span: Span,
-/// #[suggestion(message = "consider cloning here", code = "{name}.clone()")]
-/// pub opt_sugg: Option<(Span, Applicability)>
-/// }
-/// ```
-/// Then, later, to emit the error:
-///
-/// ```ignore (pseudo-rust)
-/// sess.emit_err(MoveOutOfBorrowError {
-/// expected,
-/// actual,
-/// span,
-/// other_span,
-/// opt_sugg: Some(suggestion, Applicability::MachineApplicable),
-/// });
-/// ```
-pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
- // Names for the diagnostic we build and the session we build it from.
- let diag = format_ident!("diag");
- let sess = format_ident!("sess");
-
- SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
-}
-
-// Checks whether the type name of `ty` matches `name`.
-//
-// Given some struct at a::b::c::Foo, this will return true for c::Foo, b::c::Foo, or
-// a::b::c::Foo. This reasonably allows qualified names to be used in the macro.
-fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
- if let syn::Type::Path(ty) = ty {
- ty.path
- .segments
- .iter()
- .map(|s| s.ident.to_string())
- .rev()
- .zip(name.iter().rev())
- .all(|(x, y)| &x.as_str() == y)
- } else {
- false
- }
-}
-
-/// The central struct for constructing the as_error method from an annotated struct.
-struct SessionDiagnosticDerive<'a> {
- structure: synstructure::Structure<'a>,
- builder: SessionDiagnosticDeriveBuilder<'a>,
-}
-
-impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
- fn from(e: syn::Error) -> Self {
- SessionDiagnosticDeriveError::SynError(e)
- }
-}
-
-/// Equivalent to rustc:errors::diagnostic::DiagnosticId, except stores the quoted expression to
-/// initialise the code with.
-enum DiagnosticId {
- Error(proc_macro2::TokenStream),
- Lint(proc_macro2::TokenStream),
-}
-
-#[derive(Debug)]
-enum SessionDiagnosticDeriveError {
- SynError(syn::Error),
- ErrorHandled,
-}
-
-impl SessionDiagnosticDeriveError {
- fn to_compile_error(self) -> proc_macro2::TokenStream {
- match self {
- SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
- SessionDiagnosticDeriveError::ErrorHandled => {
- // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
- // error has already been emitted to the compiler.
- quote! {
- unreachable!()
- }
- }
- }
- }
-}
-
-fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic {
- Diagnostic::spanned(span, proc_macro::Level::Error, msg)
-}
-
-/// For methods that return a Result<_, SessionDiagnosticDeriveError>: emit a diagnostic on
-/// span $span with msg $msg (and, optionally, perform additional decoration using the FnOnce
-/// passed in `diag`). Then, return Err(ErrorHandled).
-macro_rules! throw_span_err {
- ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
- ($span:expr, $msg:expr, $f:expr) => {{
- return Err(_throw_span_err($span, $msg, $f));
- }};
-}
-
-/// When possible, prefer using throw_span_err! over using this function directly. This only exists
-/// as a function to constrain `f` to an impl FnOnce.
-fn _throw_span_err(
- span: impl proc_macro::MultiSpan,
- msg: &str,
- f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic,
-) -> SessionDiagnosticDeriveError {
- let diag = span_err(span, msg);
- f(diag).emit();
- SessionDiagnosticDeriveError::ErrorHandled
-}
-
-impl<'a> SessionDiagnosticDerive<'a> {
- fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self {
- // Build the mapping of field names to fields. This allows attributes to peek values from
- // other fields.
- let mut fields_map = HashMap::new();
-
- // Convenience bindings.
- let ast = structure.ast();
-
- if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
- for field in fields.iter() {
- if let Some(ident) = &field.ident {
- fields_map.insert(ident.to_string(), field);
- }
- }
- }
-
- Self {
- builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None },
- structure,
- }
- }
- fn into_tokens(self) -> proc_macro2::TokenStream {
- let SessionDiagnosticDerive { structure, mut builder } = self;
-
- let ast = structure.ast();
- let attrs = &ast.attrs;
-
- let implementation = {
- if let syn::Data::Struct(..) = ast.data {
- let preamble = {
- let preamble = attrs.iter().map(|attr| {
- builder
- .generate_structure_code(attr)
- .unwrap_or_else(|v| v.to_compile_error())
- });
- quote! {
- #(#preamble)*;
- }
- };
-
- let body = structure.each(|field_binding| {
- let field = field_binding.ast();
- let result = field.attrs.iter().map(|attr| {
- builder
- .generate_field_code(
- attr,
- FieldInfo {
- vis: &field.vis,
- binding: field_binding,
- ty: &field.ty,
- span: &field.span(),
- },
- )
- .unwrap_or_else(|v| v.to_compile_error())
- });
- return quote! {
- #(#result);*
- };
- });
- // Finally, putting it altogether.
- match builder.kind {
- None => {
- span_err(ast.span().unwrap(), "`code` not specified")
- .help("use the [code = \"...\"] attribute to set this diagnostic's error code ")
- .emit();
- SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
- }
- Some((kind, _)) => match kind {
- DiagnosticId::Lint(_lint) => todo!(),
- DiagnosticId::Error(code) => {
- let (diag, sess) = (&builder.diag, &builder.sess);
- quote! {
- let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
- #preamble
- match self {
- #body
- }
- #diag
- }
- }
- },
- }
- } else {
- span_err(
- ast.span().unwrap(),
- "`#[derive(SessionDiagnostic)]` can only be used on structs",
- )
- .emit();
- SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
- }
- };
-
- let sess = &builder.sess;
- structure.gen_impl(quote! {
- gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess>
- for @Self
- {
- fn into_diagnostic(
- self,
- #sess: &'__session_diagnostic_sess rustc_session::Session
- ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, rustc_errors::ErrorGuaranteed> {
- #implementation
- }
- }
- })
- }
-}
-
-/// Field information passed to the builder. Deliberately omits attrs to discourage the generate_*
-/// methods from walking the attributes themselves.
-struct FieldInfo<'a> {
- vis: &'a syn::Visibility,
- binding: &'a synstructure::BindingInfo<'a>,
- ty: &'a syn::Type,
- span: &'a proc_macro2::Span,
-}
-
-/// Tracks persistent information required for building up the individual calls to diagnostic
-/// methods for the final generated method. This is a separate struct to SessionDerive only to be
-/// able to destructure and split self.builder and the self.structure up to avoid a double mut
-/// borrow later on.
-struct SessionDiagnosticDeriveBuilder<'a> {
- /// Name of the session parameter that's passed in to the as_error method.
- sess: syn::Ident,
-
- /// Store a map of field name to its corresponding field. This is built on construction of the
- /// derive builder.
- fields: HashMap<String, &'a syn::Field>,
-
- /// The identifier to use for the generated DiagnosticBuilder instance.
- diag: syn::Ident,
-
- /// Whether this is a lint or an error. This dictates how the diag will be initialised. Span
- /// stores at what Span the kind was first set at (for error reporting purposes, if the kind
- /// was multiply specified).
- kind: Option<(DiagnosticId, proc_macro2::Span)>,
-}
-
-impl<'a> SessionDiagnosticDeriveBuilder<'a> {
- fn generate_structure_code(
- &mut self,
- attr: &syn::Attribute,
- ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
- Ok(match attr.parse_meta()? {
- syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
- let formatted_str = self.build_format(&s.value(), attr.span());
- let name = attr.path.segments.last().unwrap().ident.to_string();
- let name = name.as_str();
- match name {
- "message" => {
- let diag = &self.diag;
- quote! {
- #diag.set_primary_message(#formatted_str);
- }
- }
- attr @ "error" | attr @ "lint" => {
- self.set_kind_once(
- if attr == "error" {
- DiagnosticId::Error(formatted_str)
- } else if attr == "lint" {
- DiagnosticId::Lint(formatted_str)
- } else {
- unreachable!()
- },
- s.span(),
- )?;
- // This attribute is only allowed to be applied once, and the attribute
- // will be set in the initialisation code.
- quote! {}
- }
- other => throw_span_err!(
- attr.span().unwrap(),
- &format!(
- "`#[{} = ...]` is not a valid SessionDiagnostic struct attribute",
- other
- )
- ),
- }
- }
- _ => todo!("unhandled meta kind"),
- })
- }
-
- #[must_use]
- fn set_kind_once(
- &mut self,
- kind: DiagnosticId,
- span: proc_macro2::Span,
- ) -> Result<(), SessionDiagnosticDeriveError> {
- if self.kind.is_none() {
- self.kind = Some((kind, span));
- Ok(())
- } else {
- let kind_str = |kind: &DiagnosticId| match kind {
- DiagnosticId::Lint(..) => "lint",
- DiagnosticId::Error(..) => "error",
- };
-
- let existing_kind = kind_str(&self.kind.as_ref().unwrap().0);
- let this_kind = kind_str(&kind);
-
- let msg = if this_kind == existing_kind {
- format!("`{}` specified multiple times", existing_kind)
- } else {
- format!("`{}` specified when `{}` was already specified", this_kind, existing_kind)
- };
- throw_span_err!(span.unwrap(), &msg);
- }
- }
-
- fn generate_field_code(
- &mut self,
- attr: &syn::Attribute,
- info: FieldInfo<'_>,
- ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
- let field_binding = &info.binding.binding;
-
- let option_ty = option_inner_ty(&info.ty);
-
- let generated_code = self.generate_non_option_field_code(
- attr,
- FieldInfo {
- vis: info.vis,
- binding: info.binding,
- ty: option_ty.unwrap_or(&info.ty),
- span: info.span,
- },
- )?;
- Ok(if option_ty.is_none() {
- quote! { #generated_code }
- } else {
- quote! {
- if let Some(#field_binding) = #field_binding {
- #generated_code
- }
- }
- })
- }
-
- fn generate_non_option_field_code(
- &mut self,
- attr: &syn::Attribute,
- info: FieldInfo<'_>,
- ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
- let diag = &self.diag;
- let field_binding = &info.binding.binding;
- let name = attr.path.segments.last().unwrap().ident.to_string();
- let name = name.as_str();
- // At this point, we need to dispatch based on the attribute key + the
- // type.
- let meta = attr.parse_meta()?;
- Ok(match meta {
- syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
- let formatted_str = self.build_format(&s.value(), attr.span());
- match name {
- "message" => {
- if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
- quote! {
- #diag.set_span(*#field_binding);
- #diag.set_primary_message(#formatted_str);
- }
- } else {
- throw_span_err!(
- attr.span().unwrap(),
- "the `#[message = \"...\"]` attribute can only be applied to fields of type Span"
- );
- }
- }
- "label" => {
- if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
- quote! {
- #diag.span_label(*#field_binding, #formatted_str);
- }
- } else {
- throw_span_err!(
- attr.span().unwrap(),
- "The `#[label = ...]` attribute can only be applied to fields of type Span"
- );
- }
- }
- other => throw_span_err!(
- attr.span().unwrap(),
- &format!(
- "`#[{} = ...]` is not a valid SessionDiagnostic field attribute",
- other
- )
- ),
- }
- }
- syn::Meta::List(list) => {
- match list.path.segments.iter().last().unwrap().ident.to_string().as_str() {
- suggestion_kind @ "suggestion"
- | suggestion_kind @ "suggestion_short"
- | suggestion_kind @ "suggestion_hidden"
- | suggestion_kind @ "suggestion_verbose" => {
- // For suggest, we need to ensure we are running on a (Span,
- // Applicability) pair.
- let (span, applicability) = (|| match &info.ty {
- ty @ syn::Type::Path(..)
- if type_matches_path(ty, &["rustc_span", "Span"]) =>
- {
- let binding = &info.binding.binding;
- Ok((
- quote!(*#binding),
- quote!(rustc_errors::Applicability::Unspecified),
- ))
- }
- syn::Type::Tuple(tup) => {
- let mut span_idx = None;
- let mut applicability_idx = None;
- for (idx, elem) in tup.elems.iter().enumerate() {
- if type_matches_path(elem, &["rustc_span", "Span"]) {
- if span_idx.is_none() {
- span_idx = Some(syn::Index::from(idx));
- } else {
- throw_span_err!(
- info.span.unwrap(),
- "type of field annotated with `#[suggestion(...)]` contains more than one Span"
- );
- }
- } else if type_matches_path(
- elem,
- &["rustc_errors", "Applicability"],
- ) {
- if applicability_idx.is_none() {
- applicability_idx = Some(syn::Index::from(idx));
- } else {
- throw_span_err!(
- info.span.unwrap(),
- "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
- );
- }
- }
- }
- if let Some(span_idx) = span_idx {
- let binding = &info.binding.binding;
- let span = quote!(#binding.#span_idx);
- let applicability = applicability_idx
- .map(
- |applicability_idx| quote!(#binding.#applicability_idx),
- )
- .unwrap_or_else(|| {
- quote!(rustc_errors::Applicability::Unspecified)
- });
- return Ok((span, applicability));
- }
- throw_span_err!(
- info.span.unwrap(),
- "wrong types for suggestion",
- |diag| {
- diag.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)")
- }
- );
- }
- _ => throw_span_err!(
- info.span.unwrap(),
- "wrong field type for suggestion",
- |diag| {
- diag.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)")
- }
- ),
- })()?;
- // Now read the key-value pairs.
- let mut msg = None;
- let mut code = None;
-
- for arg in list.nested.iter() {
- if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg
- {
- if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } =
- arg_name_value
- {
- let name = arg_name_value
- .path
- .segments
- .last()
- .unwrap()
- .ident
- .to_string();
- let name = name.as_str();
- let formatted_str = self.build_format(&s.value(), arg.span());
- match name {
- "message" => {
- msg = Some(formatted_str);
- }
- "code" => {
- code = Some(formatted_str);
- }
- other => throw_span_err!(
- arg.span().unwrap(),
- &format!(
- "`{}` is not a valid key for `#[suggestion(...)]`",
- other
- )
- ),
- }
- }
- }
- }
- let msg = if let Some(msg) = msg {
- quote!(#msg.as_str())
- } else {
- throw_span_err!(
- list.span().unwrap(),
- "missing suggestion message",
- |diag| {
- diag.help("provide a suggestion message using #[suggestion(message = \"...\")]")
- }
- );
- };
- let code = code.unwrap_or_else(|| quote! { String::new() });
- // Now build it out:
- let suggestion_method = format_ident!("span_{}", suggestion_kind);
- quote! {
- #diag.#suggestion_method(#span, #msg, #code, #applicability);
- }
- }
- other => throw_span_err!(
- list.span().unwrap(),
- &format!("invalid annotation list `#[{}(...)]`", other)
- ),
- }
- }
- _ => panic!("unhandled meta kind"),
- })
- }
-
- /// In the strings in the attributes supplied to this macro, we want callers to be able to
- /// reference fields in the format string. Take this, for example:
- /// ```ignore (not-usage-example)
- /// struct Point {
- /// #[error = "Expected a point greater than ({x}, {y})"]
- /// x: i32,
- /// y: i32,
- /// }
- /// ```
- /// We want to automatically pick up that {x} refers `self.x` and {y} refers to `self.y`, then
- /// generate this call to format!:
- /// ```ignore (not-usage-example)
- /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
- /// ```
- /// This function builds the entire call to format!.
- fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream {
- // This set is used later to generate the final format string. To keep builds reproducible,
- // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead
- // of a HashSet.
- let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
-
- // At this point, we can start parsing the format string.
- let mut it = input.chars().peekable();
- // Once the start of a format string has been found, process the format string and spit out
- // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so the
- // next call to `it.next()` retrieves the next character.
- while let Some(c) = it.next() {
- if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
- let mut eat_argument = || -> Option<String> {
- let mut result = String::new();
- // Format specifiers look like
- // format := '{' [ argument ] [ ':' format_spec ] '}' .
- // Therefore, we only need to eat until ':' or '}' to find the argument.
- while let Some(c) = it.next() {
- result.push(c);
- let next = *it.peek().unwrap_or(&'\0');
- if next == '}' {
- break;
- } else if next == ':' {
- // Eat the ':' character.
- assert_eq!(it.next().unwrap(), ':');
- break;
- }
- }
- // Eat until (and including) the matching '}'
- while it.next()? != '}' {
- continue;
- }
- Some(result)
- };
-
- if let Some(referenced_field) = eat_argument() {
- referenced_fields.insert(referenced_field);
- }
- }
- }
- // At this point, `referenced_fields` contains a set of the unique fields that were
- // referenced in the format string. Generate the corresponding "x = self.x" format
- // string parameters:
- let args = referenced_fields.into_iter().map(|field: String| {
- let field_ident = format_ident!("{}", field);
- let value = if self.fields.contains_key(&field) {
- quote! {
- &self.#field_ident
- }
- } else {
- // This field doesn't exist. Emit a diagnostic.
- Diagnostic::spanned(
- span.unwrap(),
- proc_macro::Level::Error,
- format!("`{}` doesn't refer to a field on this type", field),
- )
- .emit();
- quote! {
- "{#field}"
- }
- };
- quote! {
- #field_ident = #value
- }
- });
- quote! {
- format!(#input #(,#args)*)
- }
- }
-}
-
-/// If `ty` is an Option, returns Some(inner type). Else, returns None.
-fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
- if type_matches_path(ty, &["std", "option", "Option"]) {
- if let syn::Type::Path(ty_path) = ty {
- let path = &ty_path.path;
- let ty = path.segments.iter().last().unwrap();
- if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
- if bracketed.args.len() == 1 {
- if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
- return Some(ty);
- }
- }
- }
- }
- }
- None
-}