mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Add inline syntax for diagnostic messages
This commit is contained in:
@@ -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 {
|
||||
/// <https://projectfluent.org/fluent/guide/hello.html>
|
||||
/// <https://projectfluent.org/fluent/guide/attributes.html>
|
||||
FluentIdentifier(FluentId, Option<FluentId>),
|
||||
/// An inline Fluent message, containing the to be translated diagnostic message.
|
||||
Inline(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl DiagMessage {
|
||||
@@ -305,21 +310,22 @@ pub fn with_subdiagnostic_message(&self, sub: SubdiagMessage) -> Self {
|
||||
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 @@ fn from(val: DiagMessage) -> Self {
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
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 @@ pub fn translate_message<'a>(
|
||||
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<Cow<'_, str>, TranslateError<'_>> {
|
||||
|
||||
@@ -27,15 +27,17 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
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 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
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;
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
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<Path>,
|
||||
pub message: Option<Message>,
|
||||
|
||||
/// Error codes are a optional part of the struct attribute - this is only set to detect
|
||||
/// multiple specifications.
|
||||
@@ -90,7 +91,7 @@ pub(crate) fn each_variant<'s, F>(self, structure: &mut Structure<'s>, f: F) ->
|
||||
span,
|
||||
field_map: build_field_mapping(variant),
|
||||
formatting_init: TokenStream::new(),
|
||||
slug: None,
|
||||
message: None,
|
||||
code: None,
|
||||
};
|
||||
f(builder, variant)
|
||||
@@ -105,8 +106,8 @@ pub(crate) fn each_variant<'s, F>(self, structure: &mut Structure<'s>, f: F) ->
|
||||
}
|
||||
|
||||
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 @@ pub(crate) fn primary_message(&self) -> Option<&Path> {
|
||||
.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 @@ pub(crate) fn primary_message(&self) -> Option<&Path> {
|
||||
.emit();
|
||||
None
|
||||
}
|
||||
Some(slug) => Some(slug),
|
||||
Some(msg) => Some(msg),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ pub(crate) fn body(&mut self, variant: &VariantInfo<'_>) -> TokenStream {
|
||||
fn parse_subdiag_attribute(
|
||||
&self,
|
||||
attr: &Attribute,
|
||||
) -> Result<Option<(SubdiagnosticKind, Path, bool)>, DiagnosticDeriveError> {
|
||||
) -> Result<Option<(SubdiagnosticKind, Message, bool)>, 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 @@ fn parse_subdiag_attribute(
|
||||
.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 @@ fn generate_structure_code_for_attr(
|
||||
let mut input = &*input;
|
||||
let slug_recovery_point = input.fork();
|
||||
|
||||
let slug = input.parse::<Path>()?;
|
||||
if input.is_empty() || input.peek(Token![,]) {
|
||||
self.slug = Some(slug);
|
||||
if input.peek(LitStr) {
|
||||
// Parse an inline message
|
||||
let message = input.parse::<LitStr>()?;
|
||||
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::<Path>()?;
|
||||
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::<Token![,]>()?;
|
||||
// Allow trailing comma
|
||||
@@ -429,6 +448,7 @@ fn generate_inner_field_code(
|
||||
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 @@ fn generate_inner_field_code(
|
||||
Ok(quote! {
|
||||
diag.span_suggestions_with_style(
|
||||
#span_field,
|
||||
crate::fluent_generated::#slug,
|
||||
#message,
|
||||
#code_field,
|
||||
#applicability,
|
||||
#style
|
||||
@@ -455,22 +475,24 @@ fn add_spanned_subdiagnostic(
|
||||
&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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod diagnostic;
|
||||
mod diagnostic_builder;
|
||||
mod error;
|
||||
mod message;
|
||||
mod subdiagnostic;
|
||||
mod utils;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
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 @@ fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self {
|
||||
}
|
||||
|
||||
impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
||||
fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
|
||||
fn identify_kind(
|
||||
&mut self,
|
||||
) -> Result<Vec<(SubdiagnosticKind, Message)>, DiagnosticDeriveError> {
|
||||
let mut kind_slugs = vec![];
|
||||
|
||||
for attr in self.variant.ast().attrs {
|
||||
@@ -532,9 +535,8 @@ pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveErro
|
||||
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 {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
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<u32> = RefCell::new(0);
|
||||
@@ -587,7 +588,7 @@ pub(super) enum SubdiagnosticKind {
|
||||
|
||||
pub(super) struct SubdiagnosticVariant {
|
||||
pub(super) kind: SubdiagnosticKind,
|
||||
pub(super) slug: Option<Path>,
|
||||
pub(super) slug: Option<Message>,
|
||||
}
|
||||
|
||||
impl SubdiagnosticVariant {
|
||||
@@ -696,11 +697,31 @@ pub(super) fn from_attr(
|
||||
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::<LitStr>()?;
|
||||
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::<Token![,]>()?; }
|
||||
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::<Path>()?;
|
||||
let arg_name_span = arg_name.span().unwrap();
|
||||
if input.is_empty() || input.parse::<Token![,]>().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 @@ pub(super) fn from_attr(
|
||||
}
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user