From dc0db358abe19481e475e10c32149b53370f1a1c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:57:31 +0200 Subject: Merging upstream version 1.72.1+dfsg1. Signed-off-by: Daniel Baumann --- .../rustc_macros/src/diagnostics/diagnostic.rs | 61 ++++++++++++++++++++-- .../src/diagnostics/diagnostic_builder.rs | 16 +++--- compiler/rustc_macros/src/diagnostics/error.rs | 10 ++-- .../rustc_macros/src/diagnostics/subdiagnostic.rs | 25 ++++++--- compiler/rustc_macros/src/diagnostics/utils.rs | 29 ++++++---- 5 files changed, 106 insertions(+), 35 deletions(-) (limited to 'compiler/rustc_macros/src') 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, DiagnosticDeriveError> { - let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else { + ) -> Result, 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>(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, DiagnosticDeriveError> { + fn identify_kind( + &mut self, + ) -> Result, 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, + 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)>, DiagnosticDeriveError> { + ) -> Result, 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 })) } } -- cgit v1.2.3