diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index c0737edd7d65..c9e887061305 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -247,6 +247,9 @@ pub enum SubdiagMessage { /// Identifier of a Fluent message. Instances of this variant are generated by the /// `Subdiagnostic` derive. FluentIdentifier(FluentId), + /// An inline Fluent message. Instances of this variant are generated by the + /// `Subdiagnostic` derive. + Inline(Cow<'static, str>), /// Attribute of a Fluent message. Needs to be combined with a Fluent identifier to produce an /// actual translated message. Instances of this variant are generated by the `fluent_messages` /// macro. @@ -291,6 +294,8 @@ pub enum DiagMessage { /// /// FluentIdentifier(FluentId, Option), + /// An inline Fluent message, containing the to be translated diagnostic message. + Inline(Cow<'static, str>), } impl DiagMessage { @@ -305,21 +310,22 @@ impl DiagMessage { SubdiagMessage::FluentIdentifier(id) => { return DiagMessage::FluentIdentifier(id, None); } + SubdiagMessage::Inline(s) => return DiagMessage::Inline(s), SubdiagMessage::FluentAttr(attr) => attr, }; match self { - DiagMessage::Str(s) => DiagMessage::Str(s.clone()), DiagMessage::FluentIdentifier(id, _) => { DiagMessage::FluentIdentifier(id.clone(), Some(attr)) } + _ => panic!("Tried to add a subdiagnostic to a message without a fluent identifier"), } } pub fn as_str(&self) -> Option<&str> { match self { DiagMessage::Str(s) => Some(s), - DiagMessage::FluentIdentifier(_, _) => None, + DiagMessage::FluentIdentifier(_, _) | DiagMessage::Inline(_) => None, } } } @@ -353,6 +359,7 @@ impl From for SubdiagMessage { // There isn't really a sensible behaviour for this because it loses information but // this is the most sensible of the behaviours. DiagMessage::FluentIdentifier(_, Some(attr)) => SubdiagMessage::FluentAttr(attr), + DiagMessage::Inline(s) => SubdiagMessage::Inline(s), } } } diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs index 43d5fca301ec..0ee2b7b06090 100644 --- a/compiler/rustc_errors/src/translation.rs +++ b/compiler/rustc_errors/src/translation.rs @@ -3,11 +3,13 @@ use std::env; use std::error::Report; use std::sync::Arc; +use rustc_error_messages::langid; pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle}; use tracing::{debug, trace}; use crate::error::{TranslateError, TranslateErrorKind}; -use crate::{DiagArg, DiagMessage, FluentBundle, Style}; +use crate::fluent_bundle::FluentResource; +use crate::{DiagArg, DiagMessage, FluentBundle, Style, fluent_bundle}; /// Convert diagnostic arguments (a rustc internal type that exists to implement /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation. @@ -79,6 +81,28 @@ impl Translator { return Ok(Cow::Borrowed(msg)); } DiagMessage::FluentIdentifier(identifier, attr) => (identifier, attr), + // This translates an inline fluent diagnostic message + // It does this by creating a new `FluentBundle` with only one message, + // and then translating using this bundle. + DiagMessage::Inline(msg) => { + const GENERATED_MSG_ID: &str = "generated_msg"; + let resource = + FluentResource::try_new(format!("{GENERATED_MSG_ID} = {msg}\n")).unwrap(); + let mut bundle = fluent_bundle::FluentBundle::new(vec![langid!("en-US")]); + bundle.set_use_isolating(false); + bundle.add_resource(resource).unwrap(); + let message = bundle.get_message(GENERATED_MSG_ID).unwrap(); + let value = message.value().unwrap(); + + let mut errs = vec![]; + let translated = bundle.format_pattern(value, Some(args), &mut errs).to_string(); + debug!(?translated, ?errs); + return if errs.is_empty() { + Ok(Cow::Owned(translated)) + } else { + Err(TranslateError::fluent(&Cow::Borrowed(GENERATED_MSG_ID), args, errs)) + }; + } }; let translate_with_bundle = |bundle: &'a FluentBundle| -> Result, TranslateError<'_>> { diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 7e784e3464e9..1ae6393b2860 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -27,15 +27,17 @@ impl<'a> DiagnosticDerive<'a> { let preamble = builder.preamble(variant); let body = builder.body(variant); - let Some(slug) = builder.primary_message() else { + let Some(message) = builder.primary_message() else { return DiagnosticDeriveError::ErrorHandled.to_compile_error(); }; - slugs.borrow_mut().push(slug.clone()); + slugs.borrow_mut().extend(message.slug().cloned()); + let message = message.diag_message(); + let init = quote! { let mut diag = rustc_errors::Diag::new( dcx, level, - crate::fluent_generated::#slug + #message ); }; @@ -91,12 +93,13 @@ impl<'a> LintDiagnosticDerive<'a> { let preamble = builder.preamble(variant); let body = builder.body(variant); - let Some(slug) = builder.primary_message() else { + let Some(message) = builder.primary_message() else { return DiagnosticDeriveError::ErrorHandled.to_compile_error(); }; - slugs.borrow_mut().push(slug.clone()); + slugs.borrow_mut().extend(message.slug().cloned()); + let message = message.diag_message(); let primary_message = quote! { - diag.primary_message(crate::fluent_generated::#slug); + diag.primary_message(#message); }; let formatting_init = &builder.formatting_init; diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index 25110fd4f908..14eda017970f 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -4,13 +4,14 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::parse::ParseStream; use syn::spanned::Spanned; -use syn::{Attribute, Meta, Path, Token, Type, parse_quote}; +use syn::{Attribute, LitStr, Meta, Path, Token, Type, parse_quote}; use synstructure::{BindingInfo, Structure, VariantInfo}; use super::utils::SubdiagnosticVariant; use crate::diagnostics::error::{ DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err, }; +use crate::diagnostics::message::Message; use crate::diagnostics::utils::{ FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption, SubdiagnosticKind, build_field_mapping, is_doc_comment, report_error_if_not_applied_to_span, report_type_error, @@ -41,9 +42,9 @@ pub(crate) struct DiagnosticDeriveVariantBuilder { /// derive builder. pub field_map: FieldMap, - /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that + /// Message is a mandatory part of the struct attribute as corresponds to the Fluent message that /// has the actual diagnostic message. - pub slug: Option, + pub message: Option, /// Error codes are a optional part of the struct attribute - this is only set to detect /// multiple specifications. @@ -90,7 +91,7 @@ impl DiagnosticDeriveKind { span, field_map: build_field_mapping(variant), formatting_init: TokenStream::new(), - slug: None, + message: None, code: None, }; f(builder, variant) @@ -105,8 +106,8 @@ impl DiagnosticDeriveKind { } impl DiagnosticDeriveVariantBuilder { - pub(crate) fn primary_message(&self) -> Option<&Path> { - match self.slug.as_ref() { + pub(crate) fn primary_message(&self) -> Option<&Message> { + match self.message.as_ref() { None => { span_err(self.span, "diagnostic slug not specified") .help( @@ -116,7 +117,7 @@ impl DiagnosticDeriveVariantBuilder { .emit(); None } - Some(slug) + Some(Message::Slug(slug)) if let Some(Mismatch { slug_name, crate_name, slug_prefix }) = Mismatch::check(slug) => { @@ -126,7 +127,7 @@ impl DiagnosticDeriveVariantBuilder { .emit(); None } - Some(slug) => Some(slug), + Some(msg) => Some(msg), } } @@ -163,7 +164,7 @@ impl DiagnosticDeriveVariantBuilder { fn parse_subdiag_attribute( &self, attr: &Attribute, - ) -> Result, DiagnosticDeriveError> { + ) -> Result, DiagnosticDeriveError> { let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, &self.field_map)? else { // Some attributes aren't errors - like documentation comments - but also aren't // subdiagnostics. @@ -175,15 +176,18 @@ impl DiagnosticDeriveVariantBuilder { .help("consider creating a `Subdiagnostic` instead")); } - let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind { - SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, - SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, - SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once }, - SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, - SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once }, - SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn }, - SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion }, - SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), + // For subdiagnostics without a message specified, insert a placeholder slug + let slug = subdiag.slug.unwrap_or_else(|| { + Message::Slug(match subdiag.kind { + SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, + SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, + SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once }, + SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, + SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once }, + SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn }, + SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion }, + SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), + }) }); Ok(Some((subdiag.kind, slug, false))) @@ -210,13 +214,28 @@ impl DiagnosticDeriveVariantBuilder { let mut input = &*input; let slug_recovery_point = input.fork(); - let slug = input.parse::()?; - if input.is_empty() || input.peek(Token![,]) { - self.slug = Some(slug); + if input.peek(LitStr) { + // Parse an inline message + let message = input.parse::()?; + if !message.suffix().is_empty() { + span_err( + message.span().unwrap(), + "Inline message is not allowed to have a suffix", + ) + .emit(); + } + self.message = Some(Message::Inline(message.value())); } else { - input = &slug_recovery_point; + // Parse a slug + let slug = input.parse::()?; + if input.is_empty() || input.peek(Token![,]) { + self.message = Some(Message::Slug(slug)); + } else { + input = &slug_recovery_point; + } } + // Parse arguments while !input.is_empty() { input.parse::()?; // Allow trailing comma @@ -429,6 +448,7 @@ impl DiagnosticDeriveVariantBuilder { applicability.set_once(quote! { #static_applicability }, span); } + let message = slug.diag_message(); let applicability = applicability .value() .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified }); @@ -438,7 +458,7 @@ impl DiagnosticDeriveVariantBuilder { Ok(quote! { diag.span_suggestions_with_style( #span_field, - crate::fluent_generated::#slug, + #message, #code_field, #applicability, #style @@ -455,22 +475,24 @@ impl DiagnosticDeriveVariantBuilder { &self, field_binding: TokenStream, kind: &Ident, - fluent_attr_identifier: Path, + message: Message, ) -> TokenStream { let fn_name = format_ident!("span_{}", kind); + let message = message.diag_message(); quote! { diag.#fn_name( #field_binding, - crate::fluent_generated::#fluent_attr_identifier + #message ); } } /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug /// and `fluent_attr_identifier`. - fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: Path) -> TokenStream { + fn add_subdiagnostic(&self, kind: &Ident, message: Message) -> TokenStream { + let message = message.diag_message(); quote! { - diag.#kind(crate::fluent_generated::#fluent_attr_identifier); + diag.#kind(#message); } } diff --git a/compiler/rustc_macros/src/diagnostics/message.rs b/compiler/rustc_macros/src/diagnostics/message.rs new file mode 100644 index 000000000000..cfce252fbdd8 --- /dev/null +++ b/compiler/rustc_macros/src/diagnostics/message.rs @@ -0,0 +1,28 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::Path; + +pub(crate) enum Message { + Slug(Path), + Inline(String), +} + +impl Message { + pub(crate) fn slug(&self) -> Option<&Path> { + match self { + Message::Slug(slug) => Some(slug), + Message::Inline(_) => None, + } + } + + pub(crate) fn diag_message(&self) -> TokenStream { + match self { + Message::Slug(slug) => { + quote! { crate::fluent_generated::#slug } + } + Message::Inline(message) => { + quote! { rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed(#message)) } + } + } + } +} diff --git a/compiler/rustc_macros/src/diagnostics/mod.rs b/compiler/rustc_macros/src/diagnostics/mod.rs index 55228248188e..09f05ce972f1 100644 --- a/compiler/rustc_macros/src/diagnostics/mod.rs +++ b/compiler/rustc_macros/src/diagnostics/mod.rs @@ -1,6 +1,7 @@ mod diagnostic; mod diagnostic_builder; mod error; +mod message; mod subdiagnostic; mod utils; diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index db2a19ab85ba..61a234f96d12 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -11,6 +11,7 @@ use super::utils::SubdiagnosticVariant; use crate::diagnostics::error::{ DiagnosticDeriveError, invalid_attr, span_err, throw_invalid_attr, throw_span_err, }; +use crate::diagnostics::message::Message; use crate::diagnostics::utils::{ AllowMultipleAlternatives, FieldInfo, FieldInnerTy, FieldMap, SetOnce, SpannedOption, SubdiagnosticKind, build_field_mapping, build_suggestion_code, is_doc_comment, new_code_ident, @@ -182,7 +183,9 @@ 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 { @@ -532,9 +535,8 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let mut calls = TokenStream::new(); for (kind, slug) in kind_slugs { let message = format_ident!("__message"); - calls.extend( - quote! { let #message = #diag.eagerly_translate(crate::fluent_generated::#slug); }, - ); + let message_stream = slug.diag_message(); + calls.extend(quote! { let #message = #diag.eagerly_translate(#message_stream); }); let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); let call = match kind { diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index f084ba60ae3f..0718448a0513 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -16,6 +16,7 @@ use super::error::invalid_attr; use crate::diagnostics::error::{ DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err, }; +use crate::diagnostics::message::Message; thread_local! { pub(crate) static CODE_IDENT_COUNT: RefCell = RefCell::new(0); @@ -587,7 +588,7 @@ pub(super) enum SubdiagnosticKind { pub(super) struct SubdiagnosticVariant { pub(super) kind: SubdiagnosticKind, - pub(super) slug: Option, + pub(super) slug: Option, } impl SubdiagnosticVariant { @@ -696,11 +697,31 @@ impl SubdiagnosticVariant { list.parse_args_with(|input: ParseStream<'_>| { let mut is_first = true; while !input.is_empty() { + // Try to parse an inline diagnostic message + if input.peek(LitStr) { + let message = input.parse::()?; + if !message.suffix().is_empty() { + span_err( + message.span().unwrap(), + "Inline message is not allowed to have a suffix", + ).emit(); + } + if !input.is_empty() { input.parse::()?; } + if is_first { + slug = Some(Message::Inline(message.value())); + is_first = false; + } else { + span_err(message.span().unwrap(), "a diagnostic message must be the first argument to the attribute").emit(); + } + continue + } + + // Try to parse a slug instead let arg_name: Path = input.parse::()?; let arg_name_span = arg_name.span().unwrap(); if input.is_empty() || input.parse::().is_ok() { if is_first { - slug = Some(arg_name); + slug = Some(Message::Slug(arg_name)); is_first = false; } else { span_err(arg_name_span, "a diagnostic slug must be the first argument to the attribute").emit(); @@ -709,6 +730,7 @@ impl SubdiagnosticVariant { } is_first = false; + // Try to parse an argument match (arg_name.require_ident()?.to_string().as_str(), &mut kind) { ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { let code_init = build_suggestion_code(