summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_macros')
-rw-r--r--compiler/rustc_macros/Cargo.toml3
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic.rs61
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs16
-rw-r--r--compiler/rustc_macros/src/diagnostics/error.rs10
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs25
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs29
6 files changed, 108 insertions, 36 deletions
diff --git a/compiler/rustc_macros/Cargo.toml b/compiler/rustc_macros/Cargo.toml
index 1f1201b00..16c4a8500 100644
--- a/compiler/rustc_macros/Cargo.toml
+++ b/compiler/rustc_macros/Cargo.toml
@@ -8,6 +8,7 @@ proc-macro = true
[dependencies]
synstructure = "0.13.0"
-syn = { version = "2", features = ["full"] }
+# FIXME(Nilstrieb): Updating this causes changes in the diagnostics output.
+syn = { version = "=2.0.8", features = ["full"] }
proc-macro2 = "1"
quote = "1"
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 12a954258..04b7c5fee 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -1,5 +1,7 @@
#![deny(unused_must_use)]
+use std::cell::RefCell;
+
use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind};
use crate::diagnostics::error::{span_err, DiagnosticDeriveError};
use crate::diagnostics::utils::SetOnce;
@@ -28,6 +30,7 @@ impl<'a> DiagnosticDerive<'a> {
pub(crate) fn into_tokens(self) -> TokenStream {
let DiagnosticDerive { mut structure, mut builder } = self;
+ let slugs = RefCell::new(Vec::new());
let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
let preamble = builder.preamble(variant);
let body = builder.body(variant);
@@ -56,6 +59,7 @@ impl<'a> DiagnosticDerive<'a> {
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
}
Some(slug) => {
+ slugs.borrow_mut().push(slug.clone());
quote! {
let mut #diag = #handler.struct_diagnostic(crate::fluent_generated::#slug);
}
@@ -73,7 +77,8 @@ impl<'a> DiagnosticDerive<'a> {
});
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
- structure.gen_impl(quote! {
+
+ let mut imp = structure.gen_impl(quote! {
gen impl<'__diagnostic_handler_sess, G>
rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
for @Self
@@ -89,7 +94,11 @@ impl<'a> DiagnosticDerive<'a> {
#implementation
}
}
- })
+ });
+ for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
+ imp.extend(test);
+ }
+ imp
}
}
@@ -124,6 +133,7 @@ impl<'a> LintDiagnosticDerive<'a> {
}
});
+ let slugs = RefCell::new(Vec::new());
let msg = builder.each_variant(&mut structure, |mut builder, variant| {
// Collect the slug by generating the preamble.
let _ = builder.preamble(variant);
@@ -148,6 +158,7 @@ impl<'a> LintDiagnosticDerive<'a> {
DiagnosticDeriveError::ErrorHandled.to_compile_error()
}
Some(slug) => {
+ slugs.borrow_mut().push(slug.clone());
quote! {
crate::fluent_generated::#slug.into()
}
@@ -156,7 +167,7 @@ impl<'a> LintDiagnosticDerive<'a> {
});
let diag = &builder.diag;
- structure.gen_impl(quote! {
+ let mut imp = structure.gen_impl(quote! {
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
#[track_caller]
fn decorate_lint<'__b>(
@@ -171,7 +182,12 @@ impl<'a> LintDiagnosticDerive<'a> {
#msg
}
}
- })
+ });
+ for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) {
+ imp.extend(test);
+ }
+
+ imp
}
}
@@ -198,3 +214,40 @@ impl Mismatch {
}
}
}
+
+/// Generates a `#[test]` that verifies that all referenced variables
+/// exist on this structure.
+fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream {
+ // FIXME: We can't identify variables in a subdiagnostic
+ for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
+ for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
+ if attr_name == "subdiagnostic" {
+ return quote!();
+ }
+ }
+ }
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ // We need to make sure that the same diagnostic slug can be used multiple times without causing an
+ // error, so just have a global counter here.
+ static COUNTER: AtomicUsize = AtomicUsize::new(0);
+ let slug = slug.get_ident().unwrap();
+ let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed));
+ let ref_slug = quote::format_ident!("{slug}_refs");
+ let struct_name = &structure.ast().ident;
+ let variables: Vec<_> = structure
+ .variants()
+ .iter()
+ .flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string())))
+ .collect();
+ // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
+ quote! {
+ #[cfg(test)]
+ #[test ]
+ fn #ident() {
+ let variables = [#(#variables),*];
+ for vref in crate::fluent_generated::#ref_slug {
+ assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index cd6e36874..2e6e84ad8 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -14,6 +14,8 @@ use syn::Token;
use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type};
use synstructure::{BindingInfo, Structure, VariantInfo};
+use super::utils::SubdiagnosticVariant;
+
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
#[derive(Clone, PartialEq, Eq)]
pub(crate) enum DiagnosticDeriveKind {
@@ -150,19 +152,19 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
fn parse_subdiag_attribute(
&self,
attr: &Attribute,
- ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
- let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
+ ) -> Result<Option<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
+ let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(None);
};
- if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
+ if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag.kind {
throw_invalid_attr!(attr, |diag| diag
.help("consider creating a `Subdiagnostic` instead"));
}
- let slug = slug.unwrap_or_else(|| match subdiag {
+ let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind {
SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
@@ -171,7 +173,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
});
- Ok(Some((subdiag, slug)))
+ Ok(Some((subdiag.kind, slug, subdiag.no_span)))
}
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
@@ -229,7 +231,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
return Ok(tokens);
}
- let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
+ let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(quote! {});
@@ -380,7 +382,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> {
_ => (),
}
- let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else {
+ let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
return Ok(quote! {});
diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs
index b37dc826d..84b18a620 100644
--- a/compiler/rustc_macros/src/diagnostics/error.rs
+++ b/compiler/rustc_macros/src/diagnostics/error.rs
@@ -54,7 +54,7 @@ fn path_to_string(path: &syn::Path) -> String {
/// Returns an error diagnostic on span `span` with msg `msg`.
#[must_use]
-pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
+pub(crate) fn span_err<T: Into<String>>(span: impl MultiSpan, msg: T) -> Diagnostic {
Diagnostic::spanned(span, Level::Error, msg)
}
@@ -77,11 +77,9 @@ pub(crate) fn invalid_attr(attr: &Attribute) -> Diagnostic {
let span = attr.span().unwrap();
let path = path_to_string(attr.path());
match attr.meta {
- Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")),
- Meta::NameValue(_) => {
- span_err(span, &format!("`#[{path} = ...]` is not a valid attribute"))
- }
- Meta::List(_) => span_err(span, &format!("`#[{path}(...)]` is not a valid attribute")),
+ Meta::Path(_) => span_err(span, format!("`#[{path}]` is not a valid attribute")),
+ Meta::NameValue(_) => span_err(span, format!("`#[{path} = ...]` is not a valid attribute")),
+ Meta::List(_) => span_err(span, format!("`#[{path}(...)]` is not a valid attribute")),
}
}
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 374ba1a45..e8dc98691 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -14,6 +14,8 @@ use quote::{format_ident, quote};
use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path};
use synstructure::{BindingInfo, Structure, VariantInfo};
+use super::utils::SubdiagnosticVariant;
+
/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
pub(crate) struct SubdiagnosticDeriveBuilder {
diag: syn::Ident,
@@ -180,11 +182,13 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
}
impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
- fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
+ fn identify_kind(
+ &mut self,
+ ) -> Result<Vec<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
let mut kind_slugs = vec![];
for attr in self.variant.ast().attrs {
- let Some((kind, slug)) = SubdiagnosticKind::from_attr(attr, self)? else {
+ let Some(SubdiagnosticVariant { kind, slug, no_span }) = SubdiagnosticVariant::from_attr(attr, self)? else {
// Some attributes aren't errors - like documentation comments - but also aren't
// subdiagnostics.
continue;
@@ -196,13 +200,13 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
throw_span_err!(
attr.span().unwrap(),
- &format!(
+ format!(
"diagnostic slug must be first argument of a `#[{name}(...)]` attribute"
)
);
};
- kind_slugs.push((kind, slug));
+ kind_slugs.push((kind, slug, no_span));
}
Ok(kind_slugs)
@@ -487,7 +491,8 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
}
};
- let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();
+ let kind_stats: KindsStatistics =
+ kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect();
let init = if kind_stats.has_multipart_suggestion {
quote! { let mut suggestions = Vec::new(); }
@@ -508,13 +513,17 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
let diag = &self.parent.diag;
let f = &self.parent.f;
let mut calls = TokenStream::new();
- for (kind, slug) in kind_slugs {
+ for (kind, slug, no_span) in kind_slugs {
let message = format_ident!("__message");
calls.extend(
quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); },
);
- let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
+ let name = format_ident!(
+ "{}{}",
+ if span_field.is_some() && !no_span { "span_" } else { "" },
+ kind
+ );
let call = match kind {
SubdiagnosticKind::Suggestion {
suggestion_kind,
@@ -566,7 +575,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
}
}
_ => {
- if let Some(span) = span_field {
+ if let Some(span) = span_field && !no_span {
quote! { #diag.#name(#span, #message); }
} else {
quote! { #diag.#name(#message); }
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index e2434981f..125632921 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -347,7 +347,7 @@ pub(crate) trait HasFieldMap {
None => {
span_err(
span.unwrap(),
- &format!("`{field}` doesn't refer to a field on this type"),
+ format!("`{field}` doesn't refer to a field on this type"),
)
.emit();
quote! {
@@ -597,14 +597,20 @@ pub(super) enum SubdiagnosticKind {
},
}
-impl SubdiagnosticKind {
- /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
- /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
+pub(super) struct SubdiagnosticVariant {
+ pub(super) kind: SubdiagnosticKind,
+ pub(super) slug: Option<Path>,
+ pub(super) no_span: bool,
+}
+
+impl SubdiagnosticVariant {
+ /// Constructs a `SubdiagnosticVariant` from a field or type attribute such as `#[note]`,
+ /// `#[error(parser::add_paren, no_span)]` or `#[suggestion(code = "...")]`. Returns the
/// `SubdiagnosticKind` and the diagnostic slug, if specified.
pub(super) fn from_attr(
attr: &Attribute,
fields: &impl HasFieldMap,
- ) -> Result<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> {
+ ) -> Result<Option<SubdiagnosticVariant>, DiagnosticDeriveError> {
// Always allow documentation comments.
if is_doc_comment(attr) {
return Ok(None);
@@ -679,7 +685,7 @@ impl SubdiagnosticKind {
| SubdiagnosticKind::Help
| SubdiagnosticKind::Warn
| SubdiagnosticKind::MultipartSuggestion { .. } => {
- return Ok(Some((kind, None)));
+ return Ok(Some(SubdiagnosticVariant { kind, slug: None, no_span: false }));
}
SubdiagnosticKind::Suggestion { .. } => {
throw_span_err!(span, "suggestion without `code = \"...\"`")
@@ -696,11 +702,14 @@ impl SubdiagnosticKind {
let mut first = true;
let mut slug = None;
+ let mut no_span = false;
list.parse_nested_meta(|nested| {
if nested.input.is_empty() || nested.input.peek(Token![,]) {
if first {
slug = Some(nested.path);
+ } else if nested.path.is_ident("no_span") {
+ no_span = true;
} else {
span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit();
}
@@ -775,19 +784,19 @@ impl SubdiagnosticKind {
(_, SubdiagnosticKind::Suggestion { .. }) => {
span_err(path_span, "invalid nested attribute")
.help(
- "only `style`, `code` and `applicability` are valid nested attributes",
+ "only `no_span`, `style`, `code` and `applicability` are valid nested attributes",
)
.emit();
has_errors = true;
}
(_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
span_err(path_span, "invalid nested attribute")
- .help("only `style` and `applicability` are valid nested attributes")
+ .help("only `no_span`, `style` and `applicability` are valid nested attributes")
.emit();
has_errors = true;
}
_ => {
- span_err(path_span, "invalid nested attribute").emit();
+ span_err(path_span, "only `no_span` is a valid nested attribute").emit();
has_errors = true;
}
}
@@ -831,7 +840,7 @@ impl SubdiagnosticKind {
| SubdiagnosticKind::Warn => {}
}
- Ok(Some((kind, slug)))
+ Ok(Some(SubdiagnosticVariant { kind, slug, no_span }))
}
}