diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_macros/src/diagnostics/utils.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics/utils.rs')
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/utils.rs | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs new file mode 100644 index 000000000..002abb152 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -0,0 +1,356 @@ +use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError}; +use proc_macro::Span; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use std::collections::{BTreeSet, HashMap}; +use std::str::FromStr; +use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple}; +use synstructure::{BindingInfo, Structure}; + +/// 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. +pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool { + if let 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 + } +} + +/// Checks whether the type `ty` is `()`. +pub(crate) fn type_is_unit(ty: &Type) -> bool { + if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false } +} + +/// Reports a type error for field with `attr`. +pub(crate) fn report_type_error( + attr: &Attribute, + ty_name: &str, +) -> Result<!, DiagnosticDeriveError> { + let name = attr.path.segments.last().unwrap().ident.to_string(); + let meta = attr.parse_meta()?; + + throw_span_err!( + attr.span().unwrap(), + &format!( + "the `#[{}{}]` attribute can only be applied to fields of type {}", + name, + match meta { + Meta::Path(_) => "", + Meta::NameValue(_) => " = ...", + Meta::List(_) => "(...)", + }, + ty_name + ) + ); +} + +/// Reports an error if the field's type does not match `path`. +fn report_error_if_not_applied_to_ty( + attr: &Attribute, + info: &FieldInfo<'_>, + path: &[&str], + ty_name: &str, +) -> Result<(), DiagnosticDeriveError> { + if !type_matches_path(&info.ty, path) { + report_type_error(attr, ty_name)?; + } + + Ok(()) +} + +/// Reports an error if the field's type is not `Applicability`. +pub(crate) fn report_error_if_not_applied_to_applicability( + attr: &Attribute, + info: &FieldInfo<'_>, +) -> Result<(), DiagnosticDeriveError> { + report_error_if_not_applied_to_ty( + attr, + info, + &["rustc_errors", "Applicability"], + "`Applicability`", + ) +} + +/// Reports an error if the field's type is not `Span`. +pub(crate) fn report_error_if_not_applied_to_span( + attr: &Attribute, + info: &FieldInfo<'_>, +) -> Result<(), DiagnosticDeriveError> { + if !type_matches_path(&info.ty, &["rustc_span", "Span"]) + && !type_matches_path(&info.ty, &["rustc_errors", "MultiSpan"]) + { + report_type_error(attr, "`Span` or `MultiSpan`")?; + } + + Ok(()) +} + +/// Inner type of a field and type of wrapper. +pub(crate) enum FieldInnerTy<'ty> { + /// Field is wrapped in a `Option<$inner>`. + Option(&'ty Type), + /// Field is wrapped in a `Vec<$inner>`. + Vec(&'ty Type), + /// Field isn't wrapped in an outer type. + None, +} + +impl<'ty> FieldInnerTy<'ty> { + /// Returns inner type for a field, if there is one. + /// + /// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`. + /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`. + /// - Otherwise returns `None`. + pub(crate) fn from_type(ty: &'ty Type) -> Self { + let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> = + if type_matches_path(ty, &["std", "option", "Option"]) { + &FieldInnerTy::Option + } else if type_matches_path(ty, &["std", "vec", "Vec"]) { + &FieldInnerTy::Vec + } else { + return FieldInnerTy::None; + }; + + if let 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 variant(ty); + } + } + } + } + + unreachable!(); + } + + /// Returns `Option` containing inner type if there is one. + pub(crate) fn inner_type(&self) -> Option<&'ty Type> { + match self { + FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner), + FieldInnerTy::None => None, + } + } + + /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`. + pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream { + match self { + FieldInnerTy::Option(..) => quote! { + if let Some(#binding) = #binding { + #inner + } + }, + FieldInnerTy::Vec(..) => quote! { + for #binding in #binding { + #inner + } + }, + FieldInnerTy::None => quote! { #inner }, + } + } +} + +/// Field information passed to the builder. Deliberately omits attrs to discourage the +/// `generate_*` methods from walking the attributes themselves. +pub(crate) struct FieldInfo<'a> { + pub(crate) binding: &'a BindingInfo<'a>, + pub(crate) ty: &'a Type, + pub(crate) span: &'a proc_macro2::Span, +} + +/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span` +/// for error reporting if they are set more than once. +pub(crate) trait SetOnce<T> { + fn set_once(&mut self, _: (T, Span)); + + fn value(self) -> Option<T>; +} + +impl<T> SetOnce<T> for Option<(T, Span)> { + fn set_once(&mut self, (value, span): (T, Span)) { + match self { + None => { + *self = Some((value, span)); + } + Some((_, prev_span)) => { + span_err(span, "specified multiple times") + .span_note(*prev_span, "previously specified here") + .emit(); + } + } + } + + fn value(self) -> Option<T> { + self.map(|(v, _)| v) + } +} + +pub(crate) trait HasFieldMap { + /// Returns the binding for the field with the given name, if it exists on the type. + fn get_field_binding(&self, field: &String) -> Option<&TokenStream>; + + /// In the strings in the attributes supplied to this macro, we want callers to be able to + /// reference fields in the format string. For example: + /// + /// ```ignore (not-usage-example) + /// /// Suggest `==` when users wrote `===`. + /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")] + /// struct NotJavaScriptEq { + /// #[primary_span] + /// span: Span, + /// lhs: Ident, + /// rhs: Ident, + /// } + /// ``` + /// + /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to + /// `self.rhs`, then generate this call to `format!`: + /// + /// ```ignore (not-usage-example) + /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs) + /// ``` + /// + /// This function builds the entire call to `format!`. + fn build_format(&self, input: &str, span: proc_macro2::Span) -> 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 = match self.get_field_binding(&field) { + Some(value) => value.clone(), + // This field doesn't exist. Emit a diagnostic. + None => { + span_err( + span.unwrap(), + &format!("`{}` doesn't refer to a field on this type", field), + ) + .emit(); + quote! { + "{#field}" + } + } + }; + quote! { + #field_ident = #value + } + }); + quote! { + format!(#input #(,#args)*) + } + } +} + +/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent +/// the user's selection of applicability if specified in an attribute. +pub(crate) enum Applicability { + MachineApplicable, + MaybeIncorrect, + HasPlaceholders, + Unspecified, +} + +impl FromStr for Applicability { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "machine-applicable" => Ok(Applicability::MachineApplicable), + "maybe-incorrect" => Ok(Applicability::MaybeIncorrect), + "has-placeholders" => Ok(Applicability::HasPlaceholders), + "unspecified" => Ok(Applicability::Unspecified), + _ => Err(()), + } + } +} + +impl quote::ToTokens for Applicability { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + Applicability::MachineApplicable => { + quote! { rustc_errors::Applicability::MachineApplicable } + } + Applicability::MaybeIncorrect => { + quote! { rustc_errors::Applicability::MaybeIncorrect } + } + Applicability::HasPlaceholders => { + quote! { rustc_errors::Applicability::HasPlaceholders } + } + Applicability::Unspecified => { + quote! { rustc_errors::Applicability::Unspecified } + } + }); + } +} + +/// Build the mapping of field names to fields. This allows attributes to peek values from +/// other fields. +pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<String, TokenStream> { + let mut fields_map = HashMap::new(); + + 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(), quote! { &self.#ident }); + } + } + } + + fields_map +} |