mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
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:
@@ -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.)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user