Add inline syntax for diagnostic messages
This commit is contained in:
parent
7d8ebe3128
commit
8927aa5738
8 changed files with 151 additions and 42 deletions
|
|
@ -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 @@ 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<DiagMessage> 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Cow<'_, str>, TranslateError<'_>> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<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 @@ 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<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 @@ 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::<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 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
28
compiler/rustc_macros/src/diagnostics/message.rs
Normal file
28
compiler/rustc_macros/src/diagnostics/message.rs
Normal file
|
|
@ -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 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<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 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -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<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 @@ 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::<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 @@ 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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue