Rollup merge of #153508 - JonathanBrouwer:improved_eager_format, r=GuillaumeGomez

Clean up the eager formatting API

For https://github.com/rust-lang/rust/issues/151366#event-22181360642

Previously eager formatting worked by throwing the arguments into a diag, formatting, then removing the args again. This is ugly so instead we now just do the formatting completely separately.
This PR has nice commits, so I recommend reviewing commit by commit.

r? @GuillaumeGomez
This commit is contained in:
Jonathan Brouwer
2026-03-07 01:42:37 +01:00
committed by GitHub
23 changed files with 270 additions and 320 deletions
-28
View File
@@ -236,9 +236,6 @@ pub struct DiagInner {
pub suggestions: Suggestions,
pub args: DiagArgMap,
// This is used to store args and restore them after a subdiagnostic is rendered.
pub reserved_args: DiagArgMap,
/// This is not used for highlighting or rendering any error message. Rather, it can be used
/// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
/// `span` if there is one. Otherwise, it is `DUMMY_SP`.
@@ -269,7 +266,6 @@ pub fn new_with_messages(level: Level, messages: Vec<(DiagMessage, Style)>) -> S
children: vec![],
suggestions: Suggestions::Enabled(vec![]),
args: Default::default(),
reserved_args: Default::default(),
sort_span: DUMMY_SP,
is_lint: None,
long_ty_path: None,
@@ -334,14 +330,6 @@ pub fn remove_arg(&mut self, name: &str) {
self.args.swap_remove(name);
}
pub fn store_args(&mut self) {
self.reserved_args = self.args.clone();
}
pub fn restore_args(&mut self) {
self.args = std::mem::take(&mut self.reserved_args);
}
pub fn emitted_at_sub_diag(&self) -> Subdiag {
let track = format!("-Ztrack-diagnostics: created at {}", self.emitted_at);
Subdiag {
@@ -1144,16 +1132,6 @@ pub fn subdiagnostic(&mut self, subdiagnostic: impl Subdiagnostic) -> &mut Self
self
}
/// Fluent variables are not namespaced from each other, so when
/// `Diagnostic`s and `Subdiagnostic`s use the same variable name,
/// one value will clobber the other. Eagerly formatting the
/// diagnostic uses the variables defined right then, before the
/// clobbering occurs.
pub fn eagerly_format(&self, msg: impl Into<DiagMessage>) -> DiagMessage {
let args = self.args.iter();
self.dcx.eagerly_format(msg.into(), args)
}
with_fn! { with_span,
/// Add a span.
pub fn span(&mut self, sp: impl Into<MultiSpan>) -> &mut Self {
@@ -1341,12 +1319,6 @@ pub fn delay_as_bug(mut self) -> G::EmitResult {
self.downgrade_to_delayed_bug();
self.emit()
}
pub fn remove_arg(&mut self, name: &str) {
if let Some(diag) = self.diag.as_mut() {
diag.remove_arg(name);
}
}
}
/// Destructor bomb: every `Diag` must be consumed (emitted, cancelled, etc.)
+66 -24
View File
@@ -1,7 +1,7 @@
use std::borrow::Cow;
pub use rustc_error_messages::FluentArgs;
use rustc_error_messages::{DiagArgMap, langid, register_functions};
use rustc_error_messages::{DiagArgMap, DiagArgName, IntoDiagArg, langid, register_functions};
use tracing::{debug, trace};
use crate::fluent_bundle::FluentResource;
@@ -33,30 +33,72 @@ pub fn format_diag_messages(
/// Convert a `DiagMessage` to a string
pub fn format_diag_message<'a>(message: &'a DiagMessage, args: &DiagArgMap) -> Cow<'a, str> {
trace!(?message, ?args);
match message {
DiagMessage::Str(msg) => Cow::Borrowed(msg),
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();
register_functions(&mut bundle);
let message = bundle.get_message(GENERATED_MSG_ID).unwrap();
let value = message.value().unwrap();
let args = to_fluent_args(args.iter());
let mut errs = vec![];
let formatted = bundle.format_pattern(value, Some(&args), &mut errs).to_string();
debug!(?formatted, ?errs);
if errs.is_empty() {
Cow::Owned(formatted)
} else {
panic!("Fluent errors while formatting message: {errs:?}");
}
}
DiagMessage::Inline(msg) => format_fluent_str(msg, args),
}
}
fn format_fluent_str(message: &str, args: &DiagArgMap) -> Cow<'static, str> {
trace!(?message, ?args);
const GENERATED_MSG_ID: &str = "generated_msg";
let resource = FluentResource::try_new(format!("{GENERATED_MSG_ID} = {message}\n")).unwrap();
let mut bundle = fluent_bundle::FluentBundle::new(vec![langid!("en-US")]);
bundle.set_use_isolating(false);
bundle.add_resource(resource).unwrap();
register_functions(&mut bundle);
let message = bundle.get_message(GENERATED_MSG_ID).unwrap();
let value = message.value().unwrap();
let args = to_fluent_args(args.iter());
let mut errs = vec![];
let formatted = bundle.format_pattern(value, Some(&args), &mut errs).to_string();
debug!(?formatted, ?errs);
if errs.is_empty() {
Cow::Owned(formatted)
} else {
panic!("Fluent errors while formatting message: {errs:?}");
}
}
pub trait DiagMessageAddArg {
fn arg(self, name: impl Into<DiagArgName>, arg: impl IntoDiagArg) -> EagerDiagMessageBuilder;
}
pub struct EagerDiagMessageBuilder {
fluent_str: Cow<'static, str>,
args: DiagArgMap,
}
impl DiagMessageAddArg for EagerDiagMessageBuilder {
fn arg(
mut self,
name: impl Into<DiagArgName>,
arg: impl IntoDiagArg,
) -> EagerDiagMessageBuilder {
let name = name.into();
let value = arg.into_diag_arg(&mut None);
debug_assert!(
!self.args.contains_key(&name) || self.args.get(&name) == Some(&value),
"arg {} already exists",
name
);
self.args.insert(name, value);
self
}
}
impl DiagMessageAddArg for DiagMessage {
fn arg(self, name: impl Into<DiagArgName>, arg: impl IntoDiagArg) -> EagerDiagMessageBuilder {
let DiagMessage::Inline(fluent_str) = self else {
panic!("Tried to eagerly format an already formatted message")
};
EagerDiagMessageBuilder { fluent_str, args: Default::default() }.arg(name, arg)
}
}
impl EagerDiagMessageBuilder {
pub fn format(self) -> DiagMessage {
DiagMessage::Str(format_fluent_str(&self.fluent_str, &self.args))
}
}
+9 -57
View File
@@ -66,7 +66,8 @@
use tracing::debug;
use crate::emitter::TimingEvent;
use crate::formatting::format_diag_message;
use crate::formatting::DiagMessageAddArg;
pub use crate::formatting::format_diag_message;
use crate::timings::TimingRecord;
pub mod annotate_snippet_emitter_writer;
@@ -482,26 +483,6 @@ pub fn set_emitter(&self, emitter: Box<dyn Emitter + DynSend>) {
self.inner.borrow_mut().emitter = emitter;
}
/// Format `message` eagerly with `args` to `DiagMessage::Eager`.
pub fn eagerly_format<'a>(
&self,
message: DiagMessage,
args: impl Iterator<Item = DiagArg<'a>>,
) -> DiagMessage {
let inner = self.inner.borrow();
inner.eagerly_format(message, args)
}
/// Format `message` eagerly with `args` to `String`.
pub fn eagerly_format_to_string<'a>(
&self,
message: DiagMessage,
args: impl Iterator<Item = DiagArg<'a>>,
) -> String {
let inner = self.inner.borrow();
inner.eagerly_format_to_string(message, args)
}
// This is here to not allow mutation of flags;
// as of this writing it's used in Session::consider_optimizing and
// in tests in rustc_interface.
@@ -1417,33 +1398,6 @@ fn has_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
self.has_errors().or_else(|| self.delayed_bugs.get(0).map(|(_, guar)| guar).copied())
}
/// Format `message` eagerly with `args` to `DiagMessage::Eager`.
fn eagerly_format<'a>(
&self,
message: DiagMessage,
args: impl Iterator<Item = DiagArg<'a>>,
) -> DiagMessage {
DiagMessage::Str(Cow::from(self.eagerly_format_to_string(message, args)))
}
/// Format `message` eagerly with `args` to `String`.
fn eagerly_format_to_string<'a>(
&self,
message: DiagMessage,
args: impl Iterator<Item = DiagArg<'a>>,
) -> String {
let args = args.map(|(name, val)| (name.clone(), val.clone())).collect();
format_diag_message(&message, &args).to_string()
}
fn eagerly_format_for_subdiag(
&self,
diag: &DiagInner,
msg: impl Into<DiagMessage>,
) -> DiagMessage {
self.eagerly_format(msg.into(), diag.args.iter())
}
fn flush_delayed(&mut self) {
// Stashed diagnostics must be emitted before delayed bugs are flushed.
// Otherwise, we might ICE prematurely when errors would have
@@ -1493,7 +1447,7 @@ fn flush_delayed(&mut self) {
);
}
let mut bug = if decorate { bug.decorate(self) } else { bug.inner };
let mut bug = if decorate { bug.decorate() } else { bug.inner };
// "Undelay" the delayed bugs into plain bugs.
if bug.level != DelayedBug {
@@ -1503,11 +1457,9 @@ fn flush_delayed(&mut self) {
// We are at the `DiagInner`/`DiagCtxtInner` level rather than
// the usual `Diag`/`DiagCtxt` level, so we must augment `bug`
// in a lower-level fashion.
bug.arg("level", bug.level);
let msg = msg!(
"`flushed_delayed` got diagnostic with level {$level}, instead of the expected `DelayedBug`"
);
let msg = self.eagerly_format_for_subdiag(&bug, msg); // after the `arg` call
).arg("level", bug.level).format();
bug.sub(Note, msg, bug.span.primary_span().unwrap().into());
}
bug.level = Bug;
@@ -1542,7 +1494,7 @@ fn with_backtrace(diagnostic: DiagInner, backtrace: Backtrace) -> Self {
DelayedDiagInner { inner: diagnostic, note: backtrace }
}
fn decorate(self, dcx: &DiagCtxtInner) -> DiagInner {
fn decorate(self) -> DiagInner {
// We are at the `DiagInner`/`DiagCtxtInner` level rather than the
// usual `Diag`/`DiagCtxt` level, so we must construct `diag` in a
// lower-level fashion.
@@ -1555,10 +1507,10 @@ fn decorate(self, dcx: &DiagCtxtInner) -> DiagInner {
// Avoid the needless newline when no backtrace has been captured,
// the display impl should just be a single line.
_ => msg!("delayed at {$emitted_at} - {$note}"),
};
diag.arg("emitted_at", diag.emitted_at.clone());
diag.arg("note", self.note);
let msg = dcx.eagerly_format_for_subdiag(&diag, msg); // after the `arg` calls
}
.arg("emitted_at", diag.emitted_at.clone())
.arg("note", self.note)
.format();
diag.sub(Note, msg, diag.span.primary_span().unwrap_or(DUMMY_SP).into());
diag
}