diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:03:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:03:36 +0000 |
commit | 17d40c6057c88f4c432b0d7bac88e1b84cb7e67f (patch) | |
tree | 3f66c4a5918660bb8a758ab6cda5ff8ee4f6cdcd /compiler/rustc_macros | |
parent | Adding upstream version 1.64.0+dfsg1. (diff) | |
download | rustc-upstream/1.65.0+dfsg1.tar.xz rustc-upstream/1.65.0+dfsg1.zip |
Adding upstream version 1.65.0+dfsg1.upstream/1.65.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_macros/Cargo.toml | 2 | ||||
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic.rs | 84 | ||||
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs | 110 | ||||
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/fluent.rs | 56 | ||||
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/mod.rs | 20 | ||||
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/subdiagnostic.rs | 740 | ||||
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/utils.rs | 59 | ||||
-rw-r--r-- | compiler/rustc_macros/src/lib.rs | 33 | ||||
-rw-r--r-- | compiler/rustc_macros/src/query.rs | 502 | ||||
-rw-r--r-- | compiler/rustc_macros/src/symbols.rs | 2 |
10 files changed, 813 insertions, 795 deletions
diff --git a/compiler/rustc_macros/Cargo.toml b/compiler/rustc_macros/Cargo.toml index 25b3aadc1..547c8debb 100644 --- a/compiler/rustc_macros/Cargo.toml +++ b/compiler/rustc_macros/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" proc-macro = true [dependencies] -annotate-snippets = "0.8.0" +annotate-snippets = "0.9" fluent-bundle = "0.15.2" fluent-syntax = "0.11" synstructure = "0.12.1" diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 6b5b8b593..cf1c59455 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -21,7 +21,7 @@ impl<'a> SessionDiagnosticDerive<'a> { builder: DiagnosticDeriveBuilder { diag, fields: build_field_mapping(&structure), - kind: None, + kind: DiagnosticDeriveKind::SessionDiagnostic, code: None, slug: None, }, @@ -34,49 +34,31 @@ impl<'a> SessionDiagnosticDerive<'a> { let SessionDiagnosticDerive { mut structure, sess, mut builder } = self; let ast = structure.ast(); - let (implementation, param_ty) = { + let implementation = { if let syn::Data::Struct(..) = ast.data { let preamble = builder.preamble(&structure); let (attrs, args) = builder.body(&mut structure); let span = ast.span().unwrap(); let diag = &builder.diag; - let init = match (builder.kind.value(), builder.slug.value()) { - (None, _) => { - span_err(span, "diagnostic kind not specified") - .help("use the `#[error(...)]` attribute to create an error") - .emit(); - return DiagnosticDeriveError::ErrorHandled.to_compile_error(); - } - (Some(kind), None) => { + let init = match builder.slug.value() { + None => { span_err(span, "diagnostic slug not specified") .help(&format!( - "specify the slug as the first argument to the attribute, such as \ - `#[{}(typeck::example_error)]`", - kind.descr() + "specify the slug as the first argument to the `#[diag(...)]` attribute, \ + such as `#[diag(typeck::example_error)]`", )) .emit(); return DiagnosticDeriveError::ErrorHandled.to_compile_error(); } - (Some(DiagnosticDeriveKind::Lint), _) => { - span_err(span, "only `#[error(..)]` and `#[warning(..)]` are supported") - .help("use the `#[error(...)]` attribute to create a error") - .emit(); - return DiagnosticDeriveError::ErrorHandled.to_compile_error(); - } - (Some(DiagnosticDeriveKind::Error), Some(slug)) => { - quote! { - let mut #diag = #sess.struct_err(rustc_errors::fluent::#slug); - } - } - (Some(DiagnosticDeriveKind::Warn), Some(slug)) => { + Some(slug) => { quote! { - let mut #diag = #sess.struct_warn(rustc_errors::fluent::#slug); + let mut #diag = #sess.struct_diagnostic(rustc_errors::fluent::#slug); } } }; - let implementation = quote! { + quote! { #init #preamble match self { @@ -86,18 +68,7 @@ impl<'a> SessionDiagnosticDerive<'a> { #args } #diag - }; - let param_ty = match builder.kind { - Some((DiagnosticDeriveKind::Error, _)) => { - quote! { rustc_errors::ErrorGuaranteed } - } - Some((DiagnosticDeriveKind::Lint | DiagnosticDeriveKind::Warn, _)) => { - quote! { () } - } - _ => unreachable!(), - }; - - (implementation, param_ty) + } } else { span_err( ast.span().unwrap(), @@ -105,20 +76,20 @@ impl<'a> SessionDiagnosticDerive<'a> { ) .emit(); - let implementation = DiagnosticDeriveError::ErrorHandled.to_compile_error(); - let param_ty = quote! { rustc_errors::ErrorGuaranteed }; - (implementation, param_ty) + DiagnosticDeriveError::ErrorHandled.to_compile_error() } }; structure.gen_impl(quote! { - gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess, #param_ty> + gen impl<'__session_diagnostic_sess, G> + rustc_session::SessionDiagnostic<'__session_diagnostic_sess, G> for @Self + where G: rustc_errors::EmissionGuarantee { fn into_diagnostic( self, - #sess: &'__session_diagnostic_sess rustc_session::parse::ParseSess - ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, #param_ty> { + #sess: &'__session_diagnostic_sess rustc_errors::Handler + ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess, G> { use rustc_errors::IntoDiagnosticArg; #implementation } @@ -139,7 +110,7 @@ impl<'a> LintDiagnosticDerive<'a> { builder: DiagnosticDeriveBuilder { diag, fields: build_field_mapping(&structure), - kind: None, + kind: DiagnosticDeriveKind::LintDiagnostic, code: None, slug: None, }, @@ -158,30 +129,17 @@ impl<'a> LintDiagnosticDerive<'a> { let diag = &builder.diag; let span = ast.span().unwrap(); - let init = match (builder.kind.value(), builder.slug.value()) { - (None, _) => { - span_err(span, "diagnostic kind not specified") - .help("use the `#[error(...)]` attribute to create an error") - .emit(); - return DiagnosticDeriveError::ErrorHandled.to_compile_error(); - } - (Some(kind), None) => { + let init = match builder.slug.value() { + None => { span_err(span, "diagnostic slug not specified") .help(&format!( "specify the slug as the first argument to the attribute, such as \ - `#[{}(typeck::example_error)]`", - kind.descr() + `#[diag(typeck::example_error)]`", )) .emit(); return DiagnosticDeriveError::ErrorHandled.to_compile_error(); } - (Some(DiagnosticDeriveKind::Error | DiagnosticDeriveKind::Warn), _) => { - span_err(span, "only `#[lint(..)]` is supported") - .help("use the `#[lint(...)]` attribute to create a lint") - .emit(); - return DiagnosticDeriveError::ErrorHandled.to_compile_error(); - } - (Some(DiagnosticDeriveKind::Lint), Some(slug)) => { + Some(slug) => { quote! { let mut #diag = #diag.build(rustc_errors::fluent::#slug); } diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index 6c9561925..2a4fe48a8 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -18,30 +18,15 @@ use syn::{ }; use synstructure::{BindingInfo, Structure}; -/// What kind of diagnostic is being derived - an error, a warning or a lint? -#[derive(Copy, Clone)] +/// What kind of diagnostic is being derived - a fatal/error/warning or a lint? +#[derive(Copy, Clone, PartialEq, Eq)] pub(crate) enum DiagnosticDeriveKind { - /// `#[error(..)]` - Error, - /// `#[warn(..)]` - Warn, - /// `#[lint(..)]` - Lint, -} - -impl DiagnosticDeriveKind { - /// Returns human-readable string corresponding to the kind. - pub fn descr(&self) -> &'static str { - match self { - DiagnosticDeriveKind::Error => "error", - DiagnosticDeriveKind::Warn => "warning", - DiagnosticDeriveKind::Lint => "lint", - } - } + SessionDiagnostic, + LintDiagnostic, } /// Tracks persistent information required for building up individual calls to diagnostic methods -/// for generated diagnostic derives - both `SessionDiagnostic` for errors/warnings and +/// for generated diagnostic derives - both `SessionDiagnostic` for fatal/errors/warnings and /// `LintDiagnostic` for lints. pub(crate) struct DiagnosticDeriveBuilder { /// The identifier to use for the generated `DiagnosticBuilder` instance. @@ -51,8 +36,8 @@ pub(crate) struct DiagnosticDeriveBuilder { /// derive builder. pub fields: HashMap<String, TokenStream>, - /// Kind of diagnostic requested via the struct attribute. - pub kind: Option<(DiagnosticDeriveKind, proc_macro::Span)>, + /// Kind of diagnostic that should be derived. + pub kind: DiagnosticDeriveKind, /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that /// has the actual diagnostic message. pub slug: Option<(Path, proc_macro::Span)>, @@ -143,7 +128,7 @@ impl DiagnosticDeriveBuilder { } /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct - /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates + /// attributes like `#[diag(..)]`, such as the slug and error code. Generates /// diagnostic builder calls for setting error code and creating note/help messages. fn generate_structure_code_for_attr( &mut self, @@ -156,16 +141,16 @@ impl DiagnosticDeriveBuilder { let name = name.as_str(); let meta = attr.parse_meta()?; - let is_help_note_or_warn = matches!(name, "help" | "note" | "warn_"); + let is_diag = name == "diag"; let nested = match meta { - // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or + // Most attributes are lists, like `#[diag(..)]` for most cases or // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug. Meta::List(MetaList { ref nested, .. }) => nested, // Subdiagnostics without spans can be applied to the type too, and these are just - // paths: `#[help]` and `#[note]` - Meta::Path(_) if is_help_note_or_warn => { - let fn_name = if name == "warn_" { + // paths: `#[help]`, `#[note]` and `#[warning]` + Meta::Path(_) if !is_diag => { + let fn_name = if name == "warning" { Ident::new("warn", attr.span()) } else { Ident::new(name, attr.span()) @@ -178,41 +163,42 @@ impl DiagnosticDeriveBuilder { // Check the kind before doing any further processing so that there aren't misleading // "no kind specified" errors if there are failures later. match name { - "error" => self.kind.set_once((DiagnosticDeriveKind::Error, span)), - "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)), - "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)), - "help" | "note" | "warn_" => (), + "error" | "lint" => throw_invalid_attr!(attr, &meta, |diag| { + diag.help("`error` and `lint` have been replaced by `diag`") + }), + "warn_" => throw_invalid_attr!(attr, &meta, |diag| { + diag.help("`warn_` have been replaced by `warning`") + }), + "diag" | "help" | "note" | "warning" => (), _ => throw_invalid_attr!(attr, &meta, |diag| { - diag.help( - "only `error`, `warning`, `help`, `note` and `warn_` are valid attributes", - ) + diag.help("only `diag`, `help`, `note` and `warning` are valid attributes") }), } - // First nested element should always be the path, e.g. `#[error(typeck::invalid)]` or + // First nested element should always be the path, e.g. `#[diag(typeck::invalid)]` or // `#[help(typeck::another_help)]`. let mut nested_iter = nested.into_iter(); if let Some(nested_attr) = nested_iter.next() { // Report an error if there are any other list items after the path. - if is_help_note_or_warn && nested_iter.next().is_some() { + if !is_diag && nested_iter.next().is_some() { throw_invalid_nested_attr!(attr, &nested_attr, |diag| { diag.help( - "`help`, `note` and `warn_` struct attributes can only have one argument", + "`help`, `note` and `warning` struct attributes can only have one argument", ) }); } match nested_attr { - NestedMeta::Meta(Meta::Path(path)) if is_help_note_or_warn => { - let fn_name = proc_macro2::Ident::new(name, attr.span()); - return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); }); - } NestedMeta::Meta(Meta::Path(path)) => { - self.slug.set_once((path.clone(), span)); + if is_diag { + self.slug.set_once((path.clone(), span)); + } else { + let fn_name = proc_macro2::Ident::new(name, attr.span()); + return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); }); + } } NestedMeta::Meta(meta @ Meta::NameValue(_)) - if !is_help_note_or_warn - && meta.path().segments.last().unwrap().ident == "code" => + if is_diag && meta.path().segments.last().unwrap().ident == "code" => { // don't error for valid follow-up attributes } @@ -253,7 +239,7 @@ impl DiagnosticDeriveBuilder { } } - Ok(tokens.drain(..).collect()) + Ok(tokens.into_iter().collect()) } fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream { @@ -346,21 +332,31 @@ impl DiagnosticDeriveBuilder { Ok(quote! {}) } "primary_span" => { - report_error_if_not_applied_to_span(attr, &info)?; - Ok(quote! { - #diag.set_span(#binding); - }) + match self.kind { + DiagnosticDeriveKind::SessionDiagnostic => { + report_error_if_not_applied_to_span(attr, &info)?; + + Ok(quote! { + #diag.set_span(#binding); + }) + } + DiagnosticDeriveKind::LintDiagnostic => { + throw_invalid_attr!(attr, &meta, |diag| { + diag.help("the `primary_span` field attribute is not valid for lint diagnostics") + }) + } + } } "label" => { report_error_if_not_applied_to_span(attr, &info)?; Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label })) } - "note" | "help" | "warn_" => { + "note" | "help" | "warning" => { let warn_ident = Ident::new("warn", Span::call_site()); let (ident, path) = match name { "note" => (ident, parse_quote! { _subdiag::note }), "help" => (ident, parse_quote! { _subdiag::help }), - "warn_" => (&warn_ident, parse_quote! { _subdiag::warn }), + "warning" => (&warn_ident, parse_quote! { _subdiag::warn }), _ => unreachable!(), }; if type_matches_path(&info.ty, &["rustc_span", "Span"]) { @@ -397,7 +393,7 @@ impl DiagnosticDeriveBuilder { "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => { return self.generate_inner_field_code_suggestion(attr, info); } - "label" | "help" | "note" | "warn_" => (), + "label" | "help" | "note" | "warning" => (), _ => throw_invalid_attr!(attr, &meta, |diag| { diag.help( "only `label`, `help`, `note`, `warn` or `suggestion{,_short,_hidden,_verbose}` are \ @@ -429,14 +425,14 @@ impl DiagnosticDeriveBuilder { Ok(self.add_spanned_subdiagnostic(binding, ident, msg)) } "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)), - // `warn_` must be special-cased because the attribute `warn` already has meaning and + // `warning` must be special-cased because the attribute `warn` already has meaning and // so isn't used, despite the diagnostic API being named `warn`. - "warn_" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self + "warning" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self .add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)), - "warn_" if type_is_unit(&info.ty) => { + "warning" if type_is_unit(&info.ty) => { Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg)) } - "note" | "help" | "warn_" => report_type_error(attr, "`Span` or `()`")?, + "note" | "help" | "warning" => report_type_error(attr, "`Span` or `()`")?, _ => unreachable!(), } } diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs index 562d5e9f4..f7d8b494e 100644 --- a/compiler/rustc_macros/src/diagnostics/fluent.rs +++ b/compiler/rustc_macros/src/diagnostics/fluent.rs @@ -187,17 +187,41 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok for entry in resource.entries() { let span = res.ident.span(); if let Entry::Message(Message { id: Identifier { name }, attributes, .. }) = entry { - let _ = previous_defns.entry(name.to_string()).or_insert(ident_span); + let _ = previous_defns.entry(name.to_string()).or_insert(path_span); + + if name.contains('-') { + Diagnostic::spanned( + path_span, + Level::Error, + format!("name `{name}` contains a '-' character"), + ) + .help("replace any '-'s with '_'s") + .emit(); + } + + // `typeck_foo_bar` => `foo_bar` (in `typeck.ftl`) + // `const_eval_baz` => `baz` (in `const_eval.ftl`) + // `const-eval-hyphen-having` => `hyphen_having` (in `const_eval.ftl`) + // The last case we error about above, but we want to fall back gracefully + // so that only the error is being emitted and not also one about the macro + // failing. + let crate_prefix = format!("{}_", res.ident); + + let snake_name = name.replace('-', "_"); + let snake_name = match snake_name.strip_prefix(&crate_prefix) { + Some(rest) => Ident::new(rest, span), + None => { + Diagnostic::spanned( + path_span, + Level::Error, + format!("name `{name}` does not start with the crate name"), + ) + .help(format!("prepend `{crate_prefix}` to the slug name: `{crate_prefix}{snake_name}`")) + .emit(); + Ident::new(&snake_name, span) + } + }; - // `typeck-foo-bar` => `foo_bar` (in `typeck.ftl`) - // `const-eval-baz` => `baz` (in `const_eval.ftl`) - let snake_name = Ident::new( - // FIXME: should probably trim prefix, not replace all occurrences - &name - .replace(&format!("{}-", res.ident).replace('_', "-"), "") - .replace('-', "_"), - span, - ); constants.extend(quote! { pub const #snake_name: crate::DiagnosticMessage = crate::DiagnosticMessage::FluentIdentifier( @@ -212,6 +236,16 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok continue; } + if attr_name.contains('-') { + Diagnostic::spanned( + path_span, + Level::Error, + format!("attribute `{attr_name}` contains a '-' character"), + ) + .help("replace any '-'s with '_'s") + .emit(); + } + constants.extend(quote! { pub const #snake_name: crate::SubdiagnosticMessage = crate::SubdiagnosticMessage::FluentAttr( @@ -227,7 +261,7 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok match e { FluentError::Overriding { kind, id } => { Diagnostic::spanned( - ident_span, + path_span, Level::Error, format!("overrides existing {}: `{}`", kind, id), ) diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 399790026..2ff21e18f 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -23,7 +23,7 @@ use synstructure::Structure; /// # extern crate rust_middle; /// # use rustc_middle::ty::Ty; /// #[derive(SessionDiagnostic)] -/// #[error(borrowck::move_out_of_borrow, code = "E0505")] +/// #[diag(borrowck::move_out_of_borrow, code = "E0505")] /// pub struct MoveOutOfBorrowError<'tcx> { /// pub name: Ident, /// pub ty: Ty<'tcx>, @@ -38,9 +38,9 @@ use synstructure::Structure; /// ``` /// /// ```fluent -/// move-out-of-borrow = cannot move out of {$name} because it is borrowed +/// move_out_of_borrow = cannot move out of {$name} because it is borrowed /// .label = cannot move out of borrow -/// .first-borrow-label = `{$ty}` first borrowed here +/// .first_borrow_label = `{$ty}` first borrowed here /// .suggestion = consider cloning here /// ``` /// @@ -67,7 +67,7 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream { /// /// ```ignore (rust) /// #[derive(LintDiagnostic)] -/// #[lint(lint::atomic_ordering_invalid_fail_success)] +/// #[diag(lint::atomic_ordering_invalid_fail_success)] /// pub struct AtomicOrderingInvalidLint { /// method: Symbol, /// success_ordering: Symbol, @@ -84,9 +84,9 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream { /// ``` /// /// ```fluent -/// lint-atomic-ordering-invalid-fail-success = `{$method}`'s success ordering must be at least as strong as its failure ordering -/// .fail-label = `{$fail_ordering}` failure ordering -/// .success-label = `{$success_ordering}` success ordering +/// lint_atomic_ordering_invalid_fail_success = `{$method}`'s success ordering must be at least as strong as its failure ordering +/// .fail_label = `{$fail_ordering}` failure ordering +/// .success_label = `{$success_ordering}` success ordering /// .suggestion = consider using `{$success_suggestion}` success ordering instead /// ``` /// @@ -140,11 +140,11 @@ pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream { /// ``` /// /// ```fluent -/// parser-expected-identifier = expected identifier +/// parser_expected_identifier = expected identifier /// -/// parser-expected-identifier-found = expected identifier, found {$found} +/// parser_expected_identifier-found = expected identifier, found {$found} /// -/// parser-raw-identifier = escape `{$ident}` to use it as an identifier +/// parser_raw_identifier = escape `{$ident}` to use it as an identifier /// ``` /// /// Then, later, to add the subdiagnostic: diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index edf4dbed9..dce5d3cfb 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -12,7 +12,7 @@ use quote::{format_ident, quote}; use std::collections::HashMap; use std::fmt; use std::str::FromStr; -use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path}; +use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path}; use synstructure::{BindingInfo, Structure, VariantInfo}; /// Which kind of suggestion is being created? @@ -28,8 +28,41 @@ enum SubdiagnosticSuggestionKind { Verbose, } +impl FromStr for SubdiagnosticSuggestionKind { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "" => Ok(SubdiagnosticSuggestionKind::Normal), + "_short" => Ok(SubdiagnosticSuggestionKind::Short), + "_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden), + "_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose), + _ => Err(()), + } + } +} + +impl SubdiagnosticSuggestionKind { + pub fn to_suggestion_style(&self) -> TokenStream { + match self { + SubdiagnosticSuggestionKind::Normal => { + quote! { rustc_errors::SuggestionStyle::ShowCode } + } + SubdiagnosticSuggestionKind::Short => { + quote! { rustc_errors::SuggestionStyle::HideCodeInline } + } + SubdiagnosticSuggestionKind::Hidden => { + quote! { rustc_errors::SuggestionStyle::HideCodeAlways } + } + SubdiagnosticSuggestionKind::Verbose => { + quote! { rustc_errors::SuggestionStyle::ShowAlways } + } + } + } +} + /// Which kind of subdiagnostic is being created from a variant? -#[derive(Clone, Copy)] +#[derive(Clone)] enum SubdiagnosticKind { /// `#[label(...)]` Label, @@ -37,34 +70,12 @@ enum SubdiagnosticKind { Note, /// `#[help(...)]` Help, - /// `#[warn_(...)]` + /// `#[warning(...)]` Warn, /// `#[suggestion{,_short,_hidden,_verbose}]` - Suggestion(SubdiagnosticSuggestionKind), -} - -impl FromStr for SubdiagnosticKind { - type Err = (); - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "label" => Ok(SubdiagnosticKind::Label), - "note" => Ok(SubdiagnosticKind::Note), - "help" => Ok(SubdiagnosticKind::Help), - "warn_" => Ok(SubdiagnosticKind::Warn), - "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)), - "suggestion_short" => { - Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short)) - } - "suggestion_hidden" => { - Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden)) - } - "suggestion_verbose" => { - Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose)) - } - _ => Err(()), - } - } + Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream }, + /// `#[multipart_suggestion{,_short,_hidden,_verbose}]` + MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind }, } impl quote::IdentFragment for SubdiagnosticKind { @@ -74,17 +85,9 @@ impl quote::IdentFragment for SubdiagnosticKind { SubdiagnosticKind::Note => write!(f, "note"), SubdiagnosticKind::Help => write!(f, "help"), SubdiagnosticKind::Warn => write!(f, "warn"), - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => { - write!(f, "suggestion") - } - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => { - write!(f, "suggestion_short") - } - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => { - write!(f, "suggestion_hidden") - } - SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => { - write!(f, "suggestion_verbose") + SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"), + SubdiagnosticKind::MultipartSuggestion { .. } => { + write!(f, "multipart_suggestion_with_style") } } } @@ -148,11 +151,9 @@ impl<'a> SessionSubdiagnosticDerive<'a> { variant, span, fields: fields_map, - kind: None, - slug: None, - code: None, span_field: None, applicability: None, + has_suggestion_parts: false, }; builder.into_tokens().unwrap_or_else(|v| v.to_compile_error()) }); @@ -193,21 +194,15 @@ struct SessionSubdiagnosticDeriveBuilder<'a> { /// derive builder. fields: HashMap<String, TokenStream>, - /// Subdiagnostic kind of the type/variant. - kind: Option<(SubdiagnosticKind, proc_macro::Span)>, - - /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the - /// `#[kind(slug)]` attribute on the type or variant. - slug: Option<(Path, proc_macro::Span)>, - /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]` - /// attribute on the type or variant. - code: Option<(TokenStream, proc_macro::Span)>, - /// Identifier for the binding to the `#[primary_span]` field. span_field: Option<(proc_macro2::Ident, proc_macro::Span)>, /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a /// `rustc_errors::Applicability::*` variant directly. applicability: Option<(TokenStream, proc_macro::Span)>, + + /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error + /// during finalization if still `false`. + has_suggestion_parts: bool, } impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> { @@ -216,8 +211,40 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> { } } +/// Provides frequently-needed information about the diagnostic kinds being derived for this type. +#[derive(Clone, Copy, Debug)] +struct KindsStatistics { + has_multipart_suggestion: bool, + all_multipart_suggestions: bool, + has_normal_suggestion: bool, +} + +impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics { + fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self { + let mut ret = Self { + has_multipart_suggestion: false, + all_multipart_suggestions: true, + has_normal_suggestion: false, + }; + for kind in kinds { + if let SubdiagnosticKind::MultipartSuggestion { .. } = kind { + ret.has_multipart_suggestion = true; + } else { + ret.all_multipart_suggestions = false; + } + + if let SubdiagnosticKind::Suggestion { .. } = kind { + ret.has_normal_suggestion = true; + } + } + ret + } +} + impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { - fn identify_kind(&mut self) -> Result<(), DiagnosticDeriveError> { + fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> { + let mut kind_slugs = vec![]; + for attr in self.variant.ast().attrs { let span = attr.span().unwrap(); @@ -225,116 +252,121 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { let name = name.as_str(); let meta = attr.parse_meta()?; - let kind = match meta { - Meta::List(MetaList { ref nested, .. }) => { - let mut nested_iter = nested.into_iter(); - if let Some(nested_attr) = nested_iter.next() { - match nested_attr { - NestedMeta::Meta(Meta::Path(path)) => { - self.slug.set_once((path.clone(), span)); - } - NestedMeta::Meta(meta @ Meta::NameValue(_)) - if matches!( - meta.path().segments.last().unwrap().ident.to_string().as_str(), - "code" | "applicability" - ) => - { - // don't error for valid follow-up attributes - } - nested_attr => { - throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - diag.help( - "first argument of the attribute should be the diagnostic \ - slug", - ) - }) - } - }; - } + let Meta::List(MetaList { ref nested, .. }) = meta else { + throw_invalid_attr!(attr, &meta); + }; - for nested_attr in nested_iter { - let meta = match nested_attr { - NestedMeta::Meta(ref meta) => meta, - _ => throw_invalid_nested_attr!(attr, &nested_attr), - }; - - let span = meta.span().unwrap(); - let nested_name = meta.path().segments.last().unwrap().ident.to_string(); - let nested_name = nested_name.as_str(); - - match meta { - Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => { - match nested_name { - "code" => { - let formatted_str = self.build_format(&s.value(), s.span()); - self.code.set_once((formatted_str, span)); - } - "applicability" => { - let value = match Applicability::from_str(&s.value()) { - Ok(v) => v, - Err(()) => { - span_err(span, "invalid applicability").emit(); - Applicability::Unspecified - } - }; - self.applicability.set_once((quote! { #value }, span)); - } - _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - diag.help( - "only `code` and `applicability` are valid nested \ - attributes", - ) - }), - } - } - _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { - if matches!(meta, Meta::Path(_)) { - diag.help( - "a diagnostic slug must be the first argument to the \ - attribute", - ) - } else { - diag - } - }), - } + let mut kind = match name { + "label" => SubdiagnosticKind::Label, + "note" => SubdiagnosticKind::Note, + "help" => SubdiagnosticKind::Help, + "warning" => SubdiagnosticKind::Warn, + _ => { + if let Some(suggestion_kind) = + name.strip_prefix("suggestion").and_then(|s| s.parse().ok()) + { + SubdiagnosticKind::Suggestion { suggestion_kind, code: TokenStream::new() } + } else if let Some(suggestion_kind) = + name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok()) + { + SubdiagnosticKind::MultipartSuggestion { suggestion_kind } + } else { + throw_invalid_attr!(attr, &meta); } - - let Ok(kind) = SubdiagnosticKind::from_str(name) else { - throw_invalid_attr!(attr, &meta) - }; - - kind } - _ => throw_invalid_attr!(attr, &meta), }; - if matches!( - kind, - SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note - ) && self.code.is_some() - { - throw_span_err!( - span, - &format!("`code` is not a valid nested attribute of a `{}` attribute", name) - ); + let mut slug = None; + let mut code = None; + + let mut nested_iter = nested.into_iter(); + if let Some(nested_attr) = nested_iter.next() { + match nested_attr { + NestedMeta::Meta(Meta::Path(path)) => { + slug.set_once((path.clone(), span)); + } + NestedMeta::Meta(meta @ Meta::NameValue(_)) + if matches!( + meta.path().segments.last().unwrap().ident.to_string().as_str(), + "code" | "applicability" + ) => + { + // Don't error for valid follow-up attributes. + } + nested_attr => { + throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help( + "first argument of the attribute should be the diagnostic \ + slug", + ) + }) + } + }; } - if matches!( - kind, - SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note - ) && self.applicability.is_some() - { - throw_span_err!( - span, - &format!( - "`applicability` is not a valid nested attribute of a `{}` attribute", - name - ) - ); + for nested_attr in nested_iter { + let meta = match nested_attr { + NestedMeta::Meta(ref meta) => meta, + _ => throw_invalid_nested_attr!(attr, &nested_attr), + }; + + let span = meta.span().unwrap(); + let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + let nested_name = nested_name.as_str(); + + let value = match meta { + Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value, + Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("a diagnostic slug must be the first argument to the attribute") + }), + _ => throw_invalid_nested_attr!(attr, &nested_attr), + }; + + match nested_name { + "code" => { + if matches!(kind, SubdiagnosticKind::Suggestion { .. }) { + let formatted_str = self.build_format(&value.value(), value.span()); + code.set_once((formatted_str, span)); + } else { + span_err( + span, + &format!( + "`code` is not a valid nested attribute of a `{}` attribute", + name + ), + ) + .emit(); + } + } + "applicability" => { + if matches!( + kind, + SubdiagnosticKind::Suggestion { .. } + | SubdiagnosticKind::MultipartSuggestion { .. } + ) { + let value = + Applicability::from_str(&value.value()).unwrap_or_else(|()| { + span_err(span, "invalid applicability").emit(); + Applicability::Unspecified + }); + self.applicability.set_once((quote! { #value }, span)); + } else { + span_err( + span, + &format!( + "`applicability` is not a valid nested attribute of a `{}` attribute", + name + ) + ).emit(); + } + } + _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("only `code` and `applicability` are valid nested attributes") + }), + } } - if self.slug.is_none() { + let Some((slug, _)) = slug else { throw_span_err!( span, &format!( @@ -342,150 +374,334 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> { name ) ); + }; + + match kind { + SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => { + let Some((code, _)) = code else { + throw_span_err!(span, "suggestion without `code = \"...\"`"); + }; + *code_field = code; + } + SubdiagnosticKind::Label + | SubdiagnosticKind::Note + | SubdiagnosticKind::Help + | SubdiagnosticKind::Warn + | SubdiagnosticKind::MultipartSuggestion { .. } => {} } - self.kind.set_once((kind, span)); + kind_slugs.push((kind, slug)) } - Ok(()) + Ok(kind_slugs) } - fn generate_field_code( + /// Generates the code for a field with no attributes. + fn generate_field_set_arg(&mut self, binding: &BindingInfo<'_>) -> TokenStream { + let ast = binding.ast(); + assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg"); + + let diag = &self.diag; + let ident = ast.ident.as_ref().unwrap(); + quote! { + #diag.set_arg( + stringify!(#ident), + #binding + ); + } + } + + /// Generates the necessary code for all attributes on a field. + fn generate_field_attr_code( &mut self, binding: &BindingInfo<'_>, - is_suggestion: bool, - ) -> Result<TokenStream, DiagnosticDeriveError> { + kind_stats: KindsStatistics, + ) -> TokenStream { let ast = binding.ast(); + assert!(ast.attrs.len() > 0, "field without attributes generating attr code"); + // Abstract over `Vec<T>` and `Option<T>` fields using `FieldInnerTy`, which will + // apply the generated code on each element in the `Vec` or `Option`. let inner_ty = FieldInnerTy::from_type(&ast.ty); - let info = FieldInfo { - binding: binding, - ty: inner_ty.inner_type().unwrap_or(&ast.ty), - span: &ast.span(), - }; + ast.attrs + .iter() + .map(|attr| { + let info = FieldInfo { + binding, + ty: inner_ty.inner_type().unwrap_or(&ast.ty), + span: &ast.span(), + }; - for attr in &ast.attrs { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let name = name.as_str(); - let span = attr.span().unwrap(); + let generated = self + .generate_field_code_inner(kind_stats, attr, info) + .unwrap_or_else(|v| v.to_compile_error()); - let meta = attr.parse_meta()?; - match meta { - Meta::Path(_) => match name { - "primary_span" => { - report_error_if_not_applied_to_span(attr, &info)?; - self.span_field.set_once((binding.binding.clone(), span)); - return Ok(quote! {}); - } - "applicability" if is_suggestion => { - report_error_if_not_applied_to_applicability(attr, &info)?; - let binding = binding.binding.clone(); - self.applicability.set_once((quote! { #binding }, span)); - return Ok(quote! {}); - } - "applicability" => { - span_err(span, "`#[applicability]` is only valid on suggestions").emit(); - return Ok(quote! {}); - } - "skip_arg" => { - return Ok(quote! {}); - } - _ => throw_invalid_attr!(attr, &meta, |diag| { + inner_ty.with(binding, generated) + }) + .collect() + } + + fn generate_field_code_inner( + &mut self, + kind_stats: KindsStatistics, + attr: &Attribute, + info: FieldInfo<'_>, + ) -> Result<TokenStream, DiagnosticDeriveError> { + let meta = attr.parse_meta()?; + match meta { + Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path), + Meta::List(list @ MetaList { .. }) => { + self.generate_field_code_inner_list(kind_stats, attr, info, list) + } + _ => throw_invalid_attr!(attr, &meta), + } + } + + /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`). + fn generate_field_code_inner_path( + &mut self, + kind_stats: KindsStatistics, + attr: &Attribute, + info: FieldInfo<'_>, + path: Path, + ) -> Result<TokenStream, DiagnosticDeriveError> { + let span = attr.span().unwrap(); + let ident = &path.segments.last().unwrap().ident; + let name = ident.to_string(); + let name = name.as_str(); + + match name { + "skip_arg" => Ok(quote! {}), + "primary_span" => { + if kind_stats.has_multipart_suggestion { + throw_invalid_attr!(attr, &Meta::Path(path), |diag| { diag.help( - "only `primary_span`, `applicability` and `skip_arg` are valid field \ - attributes", + "multipart suggestions use one or more `#[suggestion_part]`s rather \ + than one `#[primary_span]`", ) - }), - }, - _ => throw_invalid_attr!(attr, &meta), + }) + } + + report_error_if_not_applied_to_span(attr, &info)?; + + let binding = info.binding.binding.clone(); + self.span_field.set_once((binding, span)); + + Ok(quote! {}) + } + "suggestion_part" => { + self.has_suggestion_parts = true; + + if kind_stats.has_multipart_suggestion { + span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") + .emit(); + Ok(quote! {}) + } else { + throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + diag.help( + "`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead", + ) + }); + } + } + "applicability" => { + if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion { + report_error_if_not_applied_to_applicability(attr, &info)?; + + let binding = info.binding.binding.clone(); + self.applicability.set_once((quote! { #binding }, span)); + } else { + span_err(span, "`#[applicability]` is only valid on suggestions").emit(); + } + + Ok(quote! {}) } + _ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| { + let mut span_attrs = vec![]; + if kind_stats.has_multipart_suggestion { + span_attrs.push("suggestion_part"); + } + if !kind_stats.all_multipart_suggestions { + span_attrs.push("primary_span") + } + diag.help(format!( + "only `{}`, `applicability` and `skip_arg` are valid field attributes", + span_attrs.join(", ") + )) + }), } + } - let ident = ast.ident.as_ref().unwrap(); + /// Generates the code for a `[Meta::List]`-like attribute on a field (e.g. + /// `#[suggestion_part(code = "...")]`). + fn generate_field_code_inner_list( + &mut self, + kind_stats: KindsStatistics, + attr: &Attribute, + info: FieldInfo<'_>, + list: MetaList, + ) -> Result<TokenStream, DiagnosticDeriveError> { + let span = attr.span().unwrap(); + let ident = &list.path.segments.last().unwrap().ident; + let name = ident.to_string(); + let name = name.as_str(); + + match name { + "suggestion_part" => { + if !kind_stats.has_multipart_suggestion { + throw_invalid_attr!(attr, &Meta::List(list), |diag| { + diag.help( + "`#[suggestion_part(...)]` is only valid in multipart suggestions", + ) + }) + } - let diag = &self.diag; - let generated = quote! { - #diag.set_arg( - stringify!(#ident), - #binding - ); - }; + self.has_suggestion_parts = true; + + report_error_if_not_applied_to_span(attr, &info)?; + + let mut code = None; + for nested_attr in list.nested.iter() { + let NestedMeta::Meta(ref meta) = nested_attr else { + throw_invalid_nested_attr!(attr, &nested_attr); + }; + + let span = meta.span().unwrap(); + let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + let nested_name = nested_name.as_str(); + + let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) = meta else { + throw_invalid_nested_attr!(attr, &nested_attr); + }; + + match nested_name { + "code" => { + let formatted_str = self.build_format(&value.value(), value.span()); + code.set_once((formatted_str, span)); + } + _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| { + diag.help("`code` is the only valid nested attribute") + }), + } + } - Ok(inner_ty.with(binding, generated)) + let Some((code, _)) = code else { + span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`") + .emit(); + return Ok(quote! {}); + }; + let binding = info.binding; + + Ok(quote! { suggestions.push((#binding, #code)); }) + } + _ => throw_invalid_attr!(attr, &Meta::List(list), |diag| { + let mut span_attrs = vec![]; + if kind_stats.has_multipart_suggestion { + span_attrs.push("suggestion_part"); + } + if !kind_stats.all_multipart_suggestions { + span_attrs.push("primary_span") + } + diag.help(format!( + "only `{}`, `applicability` and `skip_arg` are valid field attributes", + span_attrs.join(", ") + )) + }), + } } - fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> { - self.identify_kind()?; - let Some(kind) = self.kind.map(|(kind, _)| kind) else { + pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> { + let kind_slugs = self.identify_kind()?; + if kind_slugs.is_empty() { throw_span_err!( self.variant.ast().ident.span().unwrap(), "subdiagnostic kind not specified" ); }; - let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_)); + let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect(); - let mut args = TokenStream::new(); - for binding in self.variant.bindings() { - let arg = self - .generate_field_code(binding, is_suggestion) - .unwrap_or_else(|v| v.to_compile_error()); - args.extend(arg); - } - - // Missing slug errors will already have been reported. - let slug = self - .slug - .as_ref() - .map(|(slug, _)| slug.clone()) - .unwrap_or_else(|| parse_quote! { you::need::to::specify::a::slug }); - let code = match self.code.as_ref() { - Some((code, _)) => Some(quote! { #code }), - None if is_suggestion => { - span_err(self.span, "suggestion without `code = \"...\"`").emit(); - Some(quote! { /* macro error */ "..." }) - } - None => None, + let init = if kind_stats.has_multipart_suggestion { + quote! { let mut suggestions = Vec::new(); } + } else { + quote! {} }; + let attr_args: TokenStream = self + .variant + .bindings() + .iter() + .filter(|binding| !binding.ast().attrs.is_empty()) + .map(|binding| self.generate_field_attr_code(binding, kind_stats)) + .collect(); + let span_field = self.span_field.as_ref().map(|(span, _)| span); - let applicability = match self.applicability.clone() { - Some((applicability, _)) => Some(applicability), - None if is_suggestion => { - span_err(self.span, "suggestion without `applicability`").emit(); - Some(quote! { rustc_errors::Applicability::Unspecified }) - } - None => None, - }; + let applicability = self.applicability.take().map_or_else( + || quote! { rustc_errors::Applicability::Unspecified }, + |(applicability, _)| applicability, + ); let diag = &self.diag; - let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); - let message = quote! { rustc_errors::fluent::#slug }; - let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) { - if let Some(span) = span_field { - quote! { #diag.#name(#span, #message, #code, #applicability); } - } else { - span_err(self.span, "suggestion without `#[primary_span]` field").emit(); - quote! { unreachable!(); } - } - } else if matches!(kind, SubdiagnosticKind::Label) { - if let Some(span) = span_field { - quote! { #diag.#name(#span, #message); } - } else { - span_err(self.span, "label without `#[primary_span]` field").emit(); - quote! { unreachable!(); } - } - } else { - if let Some(span) = span_field { - quote! { #diag.#name(#span, #message); } - } else { - quote! { #diag.#name(#message); } - } - }; + let mut calls = TokenStream::new(); + for (kind, slug) in kind_slugs { + let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); + let message = quote! { rustc_errors::fluent::#slug }; + let call = match kind { + SubdiagnosticKind::Suggestion { suggestion_kind, code } => { + if let Some(span) = span_field { + let style = suggestion_kind.to_suggestion_style(); + + quote! { #diag.#name(#span, #message, #code, #applicability, #style); } + } else { + span_err(self.span, "suggestion without `#[primary_span]` field").emit(); + quote! { unreachable!(); } + } + } + SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => { + if !self.has_suggestion_parts { + span_err( + self.span, + "multipart suggestion without any `#[suggestion_part(...)]` fields", + ) + .emit(); + } + + let style = suggestion_kind.to_suggestion_style(); + + quote! { #diag.#name(#message, suggestions, #applicability, #style); } + } + SubdiagnosticKind::Label => { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message); } + } else { + span_err(self.span, "label without `#[primary_span]` field").emit(); + quote! { unreachable!(); } + } + } + _ => { + if let Some(span) = span_field { + quote! { #diag.#name(#span, #message); } + } else { + quote! { #diag.#name(#message); } + } + } + }; + calls.extend(call); + } + + let plain_args: TokenStream = self + .variant + .bindings() + .iter() + .filter(|binding| binding.ast().attrs.is_empty()) + .map(|binding| self.generate_field_set_arg(binding)) + .collect(); Ok(quote! { - #call - #args + #init + #attr_args + #calls + #plain_args }) } } diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 002abb152..ad9ecd39b 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -235,35 +235,40 @@ pub(crate) trait HasFieldMap { // 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; + if c != '{' { + continue; + } + if *it.peek().unwrap_or(&'\0') == '{' { + assert_eq!(it.next().unwrap(), '{'); + continue; + } + 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; } - Some(result) - }; - - if let Some(referenced_field) = eat_argument() { - referenced_fields.insert(referenced_field); } + // Eat until (and including) the matching '}' + while it.next()? != '}' { + continue; + } + Some(result) + }; + + if let Some(referenced_field) = eat_argument() { + referenced_fields.insert(referenced_field); } } diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index ab509b26f..2c027d754 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -1,9 +1,11 @@ #![feature(allow_internal_unstable)] -#![feature(let_else)] +#![cfg_attr(bootstrap, feature(let_else))] #![feature(never_type)] #![feature(proc_macro_diagnostic)] #![feature(proc_macro_span)] #![allow(rustc::default_hash_types)] +#![deny(rustc::untranslatable_diagnostic)] +#![deny(rustc::diagnostic_outside_of_impl)] #![recursion_limit = "128"] use synstructure::decl_derive; @@ -65,10 +67,10 @@ pub fn newtype_index(input: TokenStream) -> TokenStream { /// ..where `typeck.ftl` has the following contents.. /// /// ```fluent -/// typeck-field-multiply-specified-in-initializer = +/// typeck_field_multiply_specified_in_initializer = /// field `{$ident}` specified more than once /// .label = used more than once -/// .label-previous-use = first use of `{$ident}` +/// .label_previous_use = first use of `{$ident}` /// ``` /// ...then the macro parse the Fluent resource, emitting a diagnostic if it fails to do so, and /// will generate the following code: @@ -81,11 +83,11 @@ pub fn newtype_index(input: TokenStream) -> TokenStream { /// mod fluent_generated { /// mod typeck { /// pub const field_multiply_specified_in_initializer: DiagnosticMessage = -/// DiagnosticMessage::fluent("typeck-field-multiply-specified-in-initializer"); +/// DiagnosticMessage::fluent("typeck_field_multiply_specified_in_initializer"); /// pub const field_multiply_specified_in_initializer_label_previous_use: DiagnosticMessage = /// DiagnosticMessage::fluent_attr( -/// "typeck-field-multiply-specified-in-initializer", -/// "previous-use-label" +/// "typeck_field_multiply_specified_in_initializer", +/// "previous_use_label" /// ); /// } /// } @@ -127,12 +129,10 @@ decl_derive!([Lift, attributes(lift)] => lift::lift_derive); decl_derive!( [SessionDiagnostic, attributes( // struct attributes - warning, - error, - lint, + diag, help, note, - warn_, + warning, // field attributes skip_arg, primary_span, @@ -146,12 +146,10 @@ decl_derive!( decl_derive!( [LintDiagnostic, attributes( // struct attributes - warning, - error, - lint, + diag, help, note, - warn_, + warning, // field attributes skip_arg, primary_span, @@ -168,13 +166,18 @@ decl_derive!( label, help, note, - warn_, + warning, suggestion, suggestion_short, suggestion_hidden, suggestion_verbose, + multipart_suggestion, + multipart_suggestion_short, + multipart_suggestion_hidden, + multipart_suggestion_verbose, // field attributes skip_arg, primary_span, + suggestion_part, applicability)] => diagnostics::session_subdiagnostic_derive ); diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs index a69126533..d49926c90 100644 --- a/compiler/rustc_macros/src/query.rs +++ b/compiler/rustc_macros/src/query.rs @@ -1,139 +1,17 @@ use proc_macro::TokenStream; -use proc_macro2::{Delimiter, TokenTree}; use quote::{quote, quote_spanned}; use syn::parse::{Parse, ParseStream, Result}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{ - braced, parenthesized, parse_macro_input, parse_quote, AttrStyle, Attribute, Block, Error, - Expr, Ident, ReturnType, Token, Type, + braced, parenthesized, parse_macro_input, parse_quote, token, AttrStyle, Attribute, Block, + Error, Expr, Ident, Pat, ReturnType, Token, Type, }; mod kw { syn::custom_keyword!(query); } -/// Ident or a wildcard `_`. -struct IdentOrWild(Ident); - -impl Parse for IdentOrWild { - fn parse(input: ParseStream<'_>) -> Result<Self> { - Ok(if input.peek(Token![_]) { - let underscore = input.parse::<Token![_]>()?; - IdentOrWild(Ident::new("_", underscore.span())) - } else { - IdentOrWild(input.parse()?) - }) - } -} - -/// A modifier for a query -enum QueryModifier { - /// The description of the query. - Desc(Option<Ident>, Punctuated<Expr, Token![,]>), - - /// Use this type for the in-memory cache. - Storage(Type), - - /// Cache the query to disk if the `Expr` returns true. - Cache(Option<IdentOrWild>, Block), - - /// Custom code to load the query from disk. - LoadCached(Ident, Ident, Block), - - /// A cycle error for this query aborting the compilation with a fatal error. - FatalCycle(Ident), - - /// A cycle error results in a delay_bug call - CycleDelayBug(Ident), - - /// Don't hash the result, instead just mark a query red if it runs - NoHash(Ident), - - /// Generate a dep node based on the dependencies of the query - Anon(Ident), - - /// Always evaluate the query, ignoring its dependencies - EvalAlways(Ident), - - /// Use a separate query provider for local and extern crates - SeparateProvideExtern(Ident), - - /// Always remap the ParamEnv's constness before hashing and passing to the query provider - RemapEnvConstness(Ident), -} - -impl Parse for QueryModifier { - fn parse(input: ParseStream<'_>) -> Result<Self> { - let modifier: Ident = input.parse()?; - if modifier == "desc" { - // Parse a description modifier like: - // `desc { |tcx| "foo {}", tcx.item_path(key) }` - let attr_content; - braced!(attr_content in input); - let tcx = if attr_content.peek(Token![|]) { - attr_content.parse::<Token![|]>()?; - let tcx = attr_content.parse()?; - attr_content.parse::<Token![|]>()?; - Some(tcx) - } else { - None - }; - let desc = attr_content.parse_terminated(Expr::parse)?; - Ok(QueryModifier::Desc(tcx, desc)) - } else if modifier == "cache_on_disk_if" { - // Parse a cache modifier like: - // `cache(tcx, value) { |tcx| key.is_local() }` - let has_args = if let TokenTree::Group(group) = input.fork().parse()? { - group.delimiter() == Delimiter::Parenthesis - } else { - false - }; - let args = if has_args { - let args; - parenthesized!(args in input); - let tcx = args.parse()?; - Some(tcx) - } else { - None - }; - let block = input.parse()?; - Ok(QueryModifier::Cache(args, block)) - } else if modifier == "load_cached" { - // Parse a load_cached modifier like: - // `load_cached(tcx, id) { tcx.on_disk_cache.try_load_query_result(tcx, id) }` - let args; - parenthesized!(args in input); - let tcx = args.parse()?; - args.parse::<Token![,]>()?; - let id = args.parse()?; - let block = input.parse()?; - Ok(QueryModifier::LoadCached(tcx, id, block)) - } else if modifier == "storage" { - let args; - parenthesized!(args in input); - let ty = args.parse()?; - Ok(QueryModifier::Storage(ty)) - } else if modifier == "fatal_cycle" { - Ok(QueryModifier::FatalCycle(modifier)) - } else if modifier == "cycle_delay_bug" { - Ok(QueryModifier::CycleDelayBug(modifier)) - } else if modifier == "no_hash" { - Ok(QueryModifier::NoHash(modifier)) - } else if modifier == "anon" { - Ok(QueryModifier::Anon(modifier)) - } else if modifier == "eval_always" { - Ok(QueryModifier::EvalAlways(modifier)) - } else if modifier == "separate_provide_extern" { - Ok(QueryModifier::SeparateProvideExtern(modifier)) - } else if modifier == "remap_env_constness" { - Ok(QueryModifier::RemapEnvConstness(modifier)) - } else { - Err(Error::new(modifier.span(), "unknown query modifier")) - } - } -} - /// Ensures only doc comment attributes are used fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> { let inner = |attr: Attribute| { @@ -154,16 +32,16 @@ fn check_attributes(attrs: Vec<Attribute>) -> Result<Vec<Attribute>> { /// A compiler query. `query ... { ... }` struct Query { doc_comments: Vec<Attribute>, - modifiers: List<QueryModifier>, + modifiers: QueryModifiers, name: Ident, - key: IdentOrWild, + key: Pat, arg: Type, result: ReturnType, } impl Parse for Query { fn parse(input: ParseStream<'_>) -> Result<Self> { - let doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?; + let mut doc_comments = check_attributes(input.call(Attribute::parse_outer)?)?; // Parse the query declaration. Like `query type_of(key: DefId) -> Ty<'tcx>` input.parse::<kw::query>()?; @@ -178,7 +56,13 @@ impl Parse for Query { // Parse the query modifiers let content; braced!(content in input); - let modifiers = content.parse()?; + let modifiers = parse_query_modifiers(&content)?; + + // If there are no doc-comments, give at least some idea of what + // it does by showing the query description. + if doc_comments.is_empty() { + doc_comments.push(doc_comment_from_desc(&modifiers.desc.1)?); + } Ok(Query { doc_comments, modifiers, name, key, arg, result }) } @@ -202,13 +86,10 @@ struct QueryModifiers { desc: (Option<Ident>, Punctuated<Expr, Token![,]>), /// Use this type for the in-memory cache. - storage: Option<Type>, + arena_cache: Option<Ident>, /// Cache the query to disk if the `Block` returns true. - cache: Option<(Option<IdentOrWild>, Block)>, - - /// Custom code to load the query from disk. - load_cached: Option<(Ident, Ident, Block)>, + cache: Option<(Option<Pat>, Block)>, /// A cycle error for this query aborting the compilation with a fatal error. fatal_cycle: Option<Ident>, @@ -222,9 +103,12 @@ struct QueryModifiers { /// Generate a dep node based on the dependencies of the query anon: Option<Ident>, - // Always evaluate the query, ignoring its dependencies + /// Always evaluate the query, ignoring its dependencies eval_always: Option<Ident>, + /// Whether the query has a call depth limit + depth_limit: Option<Ident>, + /// Use a separate query provider for local and extern crates separate_provide_extern: Option<Ident>, @@ -232,10 +116,8 @@ struct QueryModifiers { remap_env_constness: Option<Ident>, } -/// Process query modifiers into a struct, erroring on duplicates -fn process_modifiers(query: &mut Query) -> QueryModifiers { - let mut load_cached = None; - let mut storage = None; +fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> { + let mut arena_cache = None; let mut cache = None; let mut desc = None; let mut fatal_cycle = None; @@ -243,121 +125,77 @@ fn process_modifiers(query: &mut Query) -> QueryModifiers { let mut no_hash = None; let mut anon = None; let mut eval_always = None; + let mut depth_limit = None; let mut separate_provide_extern = None; let mut remap_env_constness = None; - for modifier in query.modifiers.0.drain(..) { - match modifier { - QueryModifier::LoadCached(tcx, id, block) => { - if load_cached.is_some() { - panic!("duplicate modifier `load_cached` for query `{}`", query.name); - } - load_cached = Some((tcx, id, block)); - } - QueryModifier::Storage(ty) => { - if storage.is_some() { - panic!("duplicate modifier `storage` for query `{}`", query.name); - } - storage = Some(ty); - } - QueryModifier::Cache(args, expr) => { - if cache.is_some() { - panic!("duplicate modifier `cache` for query `{}`", query.name); - } - cache = Some((args, expr)); - } - QueryModifier::Desc(tcx, list) => { - if desc.is_some() { - panic!("duplicate modifier `desc` for query `{}`", query.name); - } - // If there are no doc-comments, give at least some idea of what - // it does by showing the query description. - if query.doc_comments.is_empty() { - use ::syn::*; - let mut list = list.iter(); - let format_str: String = match list.next() { - Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => { - lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency - } - _ => panic!("Expected a string literal"), - }; - let mut fmt_fragments = format_str.split("{}"); - let mut doc_string = fmt_fragments.next().unwrap().to_string(); - list.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each( - |(tts, next_fmt_fragment)| { - use ::core::fmt::Write; - write!( - &mut doc_string, - " `{}` {}", - tts.to_string().replace(" . ", "."), - next_fmt_fragment, - ) - .unwrap(); - }, - ); - let doc_string = format!( - "[query description - consider adding a doc-comment!] {}", - doc_string - ); - let comment = parse_quote! { - #[doc = #doc_string] - }; - query.doc_comments.push(comment); - } - desc = Some((tcx, list)); - } - QueryModifier::FatalCycle(ident) => { - if fatal_cycle.is_some() { - panic!("duplicate modifier `fatal_cycle` for query `{}`", query.name); - } - fatal_cycle = Some(ident); - } - QueryModifier::CycleDelayBug(ident) => { - if cycle_delay_bug.is_some() { - panic!("duplicate modifier `cycle_delay_bug` for query `{}`", query.name); - } - cycle_delay_bug = Some(ident); - } - QueryModifier::NoHash(ident) => { - if no_hash.is_some() { - panic!("duplicate modifier `no_hash` for query `{}`", query.name); - } - no_hash = Some(ident); - } - QueryModifier::Anon(ident) => { - if anon.is_some() { - panic!("duplicate modifier `anon` for query `{}`", query.name); - } - anon = Some(ident); - } - QueryModifier::EvalAlways(ident) => { - if eval_always.is_some() { - panic!("duplicate modifier `eval_always` for query `{}`", query.name); - } - eval_always = Some(ident); - } - QueryModifier::SeparateProvideExtern(ident) => { - if separate_provide_extern.is_some() { - panic!( - "duplicate modifier `separate_provide_extern` for query `{}`", - query.name - ); - } - separate_provide_extern = Some(ident); - } - QueryModifier::RemapEnvConstness(ident) => { - if remap_env_constness.is_some() { - panic!("duplicate modifier `remap_env_constness` for query `{}`", query.name); + + while !input.is_empty() { + let modifier: Ident = input.parse()?; + + macro_rules! try_insert { + ($name:ident = $expr:expr) => { + if $name.is_some() { + return Err(Error::new(modifier.span(), "duplicate modifier")); } - remap_env_constness = Some(ident) - } + $name = Some($expr); + }; + } + + if modifier == "desc" { + // Parse a description modifier like: + // `desc { |tcx| "foo {}", tcx.item_path(key) }` + let attr_content; + braced!(attr_content in input); + let tcx = if attr_content.peek(Token![|]) { + attr_content.parse::<Token![|]>()?; + let tcx = attr_content.parse()?; + attr_content.parse::<Token![|]>()?; + Some(tcx) + } else { + None + }; + let list = attr_content.parse_terminated(Expr::parse)?; + try_insert!(desc = (tcx, list)); + } else if modifier == "cache_on_disk_if" { + // Parse a cache modifier like: + // `cache(tcx) { |tcx| key.is_local() }` + let args = if input.peek(token::Paren) { + let args; + parenthesized!(args in input); + let tcx = args.parse()?; + Some(tcx) + } else { + None + }; + let block = input.parse()?; + try_insert!(cache = (args, block)); + } else if modifier == "arena_cache" { + try_insert!(arena_cache = modifier); + } else if modifier == "fatal_cycle" { + try_insert!(fatal_cycle = modifier); + } else if modifier == "cycle_delay_bug" { + try_insert!(cycle_delay_bug = modifier); + } else if modifier == "no_hash" { + try_insert!(no_hash = modifier); + } else if modifier == "anon" { + try_insert!(anon = modifier); + } else if modifier == "eval_always" { + try_insert!(eval_always = modifier); + } else if modifier == "depth_limit" { + try_insert!(depth_limit = modifier); + } else if modifier == "separate_provide_extern" { + try_insert!(separate_provide_extern = modifier); + } else if modifier == "remap_env_constness" { + try_insert!(remap_env_constness = modifier); + } else { + return Err(Error::new(modifier.span(), "unknown query modifier")); } } - let desc = desc.unwrap_or_else(|| { - panic!("no description provided for query `{}`", query.name); - }); - QueryModifiers { - load_cached, - storage, + let Some(desc) = desc else { + return Err(input.error("no description provided")); + }; + Ok(QueryModifiers { + arena_cache, cache, desc, fatal_cycle, @@ -365,43 +203,48 @@ fn process_modifiers(query: &mut Query) -> QueryModifiers { no_hash, anon, eval_always, + depth_limit, separate_provide_extern, remap_env_constness, - } + }) +} + +fn doc_comment_from_desc(list: &Punctuated<Expr, token::Comma>) -> Result<Attribute> { + use ::syn::*; + let mut iter = list.iter(); + let format_str: String = match iter.next() { + Some(&Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. })) => { + lit_str.value().replace("`{}`", "{}") // We add them later anyways for consistency + } + _ => return Err(Error::new(list.span(), "Expected a string literal")), + }; + let mut fmt_fragments = format_str.split("{}"); + let mut doc_string = fmt_fragments.next().unwrap().to_string(); + iter.map(::quote::ToTokens::to_token_stream).zip(fmt_fragments).for_each( + |(tts, next_fmt_fragment)| { + use ::core::fmt::Write; + write!( + &mut doc_string, + " `{}` {}", + tts.to_string().replace(" . ", "."), + next_fmt_fragment, + ) + .unwrap(); + }, + ); + let doc_string = format!("[query description - consider adding a doc-comment!] {}", doc_string); + Ok(parse_quote! { #[doc = #doc_string] }) } /// Add the impl of QueryDescription for the query to `impls` if one is requested -fn add_query_description_impl( - query: &Query, - modifiers: QueryModifiers, - impls: &mut proc_macro2::TokenStream, -) { +fn add_query_description_impl(query: &Query, impls: &mut proc_macro2::TokenStream) { let name = &query.name; - let key = &query.key.0; + let key = &query.key; + let modifiers = &query.modifiers; // Find out if we should cache the query on disk let cache = if let Some((args, expr)) = modifiers.cache.as_ref() { - let try_load_from_disk = if let Some((tcx, id, block)) = modifiers.load_cached.as_ref() { - // Use custom code to load the query from disk - quote! { - const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> - = Some(|#tcx, #id| { #block }); - } - } else { - // Use the default code to load the query from disk - quote! { - const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> - = Some(|tcx, id| tcx.on_disk_cache().as_ref()?.try_load_query_result(*tcx, id)); - } - }; - - let tcx = args - .as_ref() - .map(|t| { - let t = &t.0; - quote! { #t } - }) - .unwrap_or_else(|| quote! { _ }); + let tcx = args.as_ref().map(|t| quote! { #t }).unwrap_or_else(|| quote! { _ }); // expr is a `Block`, meaning that `{ #expr }` gets expanded // to `{ { stmts... } }`, which triggers the `unused_braces` lint. quote! { @@ -410,29 +253,22 @@ fn add_query_description_impl( fn cache_on_disk(#tcx: TyCtxt<'tcx>, #key: &Self::Key) -> bool { #expr } - - #try_load_from_disk } } else { - if modifiers.load_cached.is_some() { - panic!("load_cached modifier on query `{}` without a cache modifier", name); - } quote! { #[inline] fn cache_on_disk(_: TyCtxt<'tcx>, _: &Self::Key) -> bool { false } - - const TRY_LOAD_FROM_DISK: Option<fn(QueryCtxt<$tcx>, SerializedDepNodeIndex) -> Option<Self::Value>> = None; } }; - let (tcx, desc) = modifiers.desc; + let (tcx, desc) = &modifiers.desc; let tcx = tcx.as_ref().map_or_else(|| quote! { _ }, |t| quote! { #t }); let desc = quote! { #[allow(unused_variables)] - fn describe(tcx: QueryCtxt<$tcx>, key: Self::Key) -> String { + fn describe(tcx: QueryCtxt<'tcx>, key: Self::Key) -> String { let (#tcx, #key) = (*tcx, key); ::rustc_middle::ty::print::with_no_trimmed_paths!( format!(#desc) @@ -441,7 +277,7 @@ fn add_query_description_impl( }; impls.extend(quote! { - (#name<$tcx:tt>) => { + (#name) => { #desc #cache }; @@ -453,13 +289,10 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream { let mut query_stream = quote! {}; let mut query_description_stream = quote! {}; - let mut dep_node_def_stream = quote! {}; let mut cached_queries = quote! {}; - for mut query in queries.0 { - let modifiers = process_modifiers(&mut query); - let name = &query.name; - let arg = &query.arg; + for query in queries.0 { + let Query { name, arg, modifiers, .. } = &query; let result_full = &query.result; let result = match query.result { ReturnType::Default => quote! { -> () }, @@ -474,38 +307,32 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream { let mut attributes = Vec::new(); - // Pass on the fatal_cycle modifier - if let Some(fatal_cycle) = &modifiers.fatal_cycle { - attributes.push(quote! { (#fatal_cycle) }); - }; - // Pass on the storage modifier - if let Some(ref ty) = modifiers.storage { - let span = ty.span(); - attributes.push(quote_spanned! {span=> (storage #ty) }); - }; - // Pass on the cycle_delay_bug modifier - if let Some(cycle_delay_bug) = &modifiers.cycle_delay_bug { - attributes.push(quote! { (#cycle_delay_bug) }); - }; - // Pass on the no_hash modifier - if let Some(no_hash) = &modifiers.no_hash { - attributes.push(quote! { (#no_hash) }); - }; - // Pass on the anon modifier - if let Some(anon) = &modifiers.anon { - attributes.push(quote! { (#anon) }); - }; - // Pass on the eval_always modifier - if let Some(eval_always) = &modifiers.eval_always { - attributes.push(quote! { (#eval_always) }); - }; - // Pass on the separate_provide_extern modifier - if let Some(separate_provide_extern) = &modifiers.separate_provide_extern { - attributes.push(quote! { (#separate_provide_extern) }); + macro_rules! passthrough { + ( $( $modifier:ident ),+ $(,)? ) => { + $( if let Some($modifier) = &modifiers.$modifier { + attributes.push(quote! { (#$modifier) }); + }; )+ + } } - // Pass on the remap_env_constness modifier - if let Some(remap_env_constness) = &modifiers.remap_env_constness { - attributes.push(quote! { (#remap_env_constness) }); + + passthrough!( + fatal_cycle, + arena_cache, + cycle_delay_bug, + no_hash, + anon, + eval_always, + depth_limit, + separate_provide_extern, + remap_env_constness, + ); + + if modifiers.cache.is_some() { + attributes.push(quote! { (cache) }); + } + // Pass on the cache modifier + if modifiers.cache.is_some() { + attributes.push(quote! { (cache) }); } // This uses the span of the query definition for the commas, @@ -516,48 +343,27 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream { // be very useful. let span = name.span(); let attribute_stream = quote_spanned! {span=> #(#attributes),*}; - let doc_comments = query.doc_comments.iter(); + let doc_comments = &query.doc_comments; // Add the query to the group query_stream.extend(quote! { #(#doc_comments)* [#attribute_stream] fn #name(#arg) #result, }); - // Create a dep node for the query - dep_node_def_stream.extend(quote! { - [#attribute_stream] #name(#arg), - }); - - add_query_description_impl(&query, modifiers, &mut query_description_stream); + add_query_description_impl(&query, &mut query_description_stream); } TokenStream::from(quote! { #[macro_export] macro_rules! rustc_query_append { - ([$($macro:tt)*][$($other:tt)*]) => { - $($macro)* { - $($other)* - + ($macro:ident! $( [$($other:tt)*] )?) => { + $macro! { + $( $($other)* )? #query_stream - } } } - macro_rules! rustc_dep_node_append { - ([$($macro:tt)*][$($other:tt)*]) => { - $($macro)*( - $($other)* - #dep_node_def_stream - ); - } - } - #[macro_export] - macro_rules! rustc_cached_queries { - ($($macro:tt)*) => { - $($macro)*(#cached_queries); - } - } #[macro_export] macro_rules! rustc_query_description { #query_description_stream diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs index 1b245f2a7..92590c33b 100644 --- a/compiler/rustc_macros/src/symbols.rs +++ b/compiler/rustc_macros/src/symbols.rs @@ -195,10 +195,10 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec<syn::Error>) { #n, }); } - let _ = counter; // for future use let output = quote! { const SYMBOL_DIGITS_BASE: u32 = #digits_base; + const PREINTERNED_SYMBOLS_COUNT: u32 = #counter; #[doc(hidden)] #[allow(non_upper_case_globals)] |