]> git.proxmox.com Git - rustc.git/blobdiff - vendor/rustc-ap-rustc_macros/src/session_diagnostic.rs
Update upstream source from tag 'upstream/1.52.1+dfsg1'
[rustc.git] / vendor / rustc-ap-rustc_macros / src / session_diagnostic.rs
diff --git a/vendor/rustc-ap-rustc_macros/src/session_diagnostic.rs b/vendor/rustc-ap-rustc_macros/src/session_diagnostic.rs
new file mode 100644 (file)
index 0000000..5c061a9
--- /dev/null
@@ -0,0 +1,666 @@
+#![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> {
+                    #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.clone().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.clone().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(quote!(
+                                            rustc_errors::Applicability::Unspecified
+                                        ));
+                                    return Ok((span, applicability));
+                                }
+                                throw_span_err!(
+                                    info.span.clone().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.clone().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') != '{' {
+                #[must_use]
+                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
+}