Allow errors to be emitted as fatal during attribute parsing

This commit is contained in:
Jana Dönszelmann
2025-08-11 11:46:30 +02:00
parent 4eedad3126
commit 3bf6144461
4 changed files with 66 additions and 22 deletions

View File

@@ -5,7 +5,7 @@ use std::sync::LazyLock;
use private::Sealed;
use rustc_ast::{AttrStyle, MetaItemLit, NodeId};
use rustc_errors::Diagnostic;
use rustc_errors::{Diag, Diagnostic, Level};
use rustc_feature::AttributeTemplate;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::lints::{AttributeLint, AttributeLintKind};
@@ -263,11 +263,7 @@ impl Stage for Early {
sess: &'sess Session,
diag: impl for<'x> Diagnostic<'x>,
) -> ErrorGuaranteed {
if self.emit_errors.should_emit() {
sess.dcx().emit_err(diag)
} else {
sess.dcx().create_err(diag).delay_as_bug()
}
self.should_emit().emit_err_or_delay(sess.dcx().create_err(diag))
}
fn should_emit(&self) -> ShouldEmit {
@@ -333,7 +329,7 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> {
/// must be delayed until after HIR is built. This method will take care of the details of
/// that.
pub(crate) fn emit_lint(&mut self, lint: AttributeLintKind, span: Span) {
if !self.stage.should_emit().should_emit() {
if matches!(self.stage.should_emit(), ShouldEmit::Nothing) {
return;
}
let id = self.target_id;
@@ -651,8 +647,13 @@ pub enum OmitDoc {
Skip,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum ShouldEmit {
/// The operations will emit errors, and lints, and errors are fatal.
///
/// Only relevant when early parsing, in late parsing equivalent to `ErrorsAndLints`.
/// Late parsing is never fatal, and instead tries to emit as many diagnostics as possible.
EarlyFatal,
/// The operation will emit errors and lints.
/// This is usually what you need.
ErrorsAndLints,
@@ -662,10 +663,27 @@ pub enum ShouldEmit {
}
impl ShouldEmit {
pub fn should_emit(&self) -> bool {
pub(crate) fn emit_err_or_delay(&self, diag: Diag<'_>) -> ErrorGuaranteed {
match self {
ShouldEmit::ErrorsAndLints => true,
ShouldEmit::Nothing => false,
ShouldEmit::EarlyFatal if diag.level() == Level::DelayedBug => diag.emit(),
ShouldEmit::EarlyFatal => diag.upgrade_to_fatal().emit(),
ShouldEmit::ErrorsAndLints => diag.emit(),
ShouldEmit::Nothing => diag.delay_as_bug(),
}
}
pub(crate) fn maybe_emit_err(&self, diag: Diag<'_>) {
match self {
ShouldEmit::EarlyFatal if diag.level() == Level::DelayedBug => {
diag.emit();
}
ShouldEmit::EarlyFatal => {
diag.upgrade_to_fatal().emit();
}
ShouldEmit::ErrorsAndLints => {
diag.emit();
}
ShouldEmit::Nothing => {}
}
}
}

View File

@@ -252,7 +252,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
(accept.accept_fn)(&mut cx, args);
if self.stage.should_emit().should_emit() {
if !matches!(self.stage.should_emit(), ShouldEmit::Nothing) {
self.check_target(
path.get_attribute_path(),
attr.span,

View File

@@ -328,15 +328,16 @@ fn expr_to_lit(
match res {
Ok(lit) => {
if token_lit.suffix.is_some() {
psess
.dcx()
.create_err(SuffixedLiteralInAttribute { span: lit.span })
.emit_unless_delay(!should_emit.should_emit());
should_emit.emit_err_or_delay(
psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
);
None
} else {
if should_emit.should_emit() && !lit.kind.is_unsuffixed() {
if !lit.kind.is_unsuffixed() {
// Emit error and continue, we can still parse the attribute as if the suffix isn't there
psess.dcx().emit_err(SuffixedLiteralInAttribute { span: lit.span });
should_emit.maybe_emit_err(
psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
);
}
Some(lit)
@@ -366,7 +367,7 @@ fn expr_to_lit(
err.downgrade_to_delayed_bug();
}
err.emit_unless_delay(!should_emit.should_emit());
should_emit.emit_err_or_delay(err);
None
}
}
@@ -397,9 +398,11 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
}
};
if self.should_emit.should_emit() && !lit.kind.is_unsuffixed() {
if !lit.kind.is_unsuffixed() {
// Emit error and continue, we can still parse the attribute as if the suffix isn't there
self.parser.dcx().emit_err(SuffixedLiteralInAttribute { span: lit.span });
self.should_emit.maybe_emit_err(
self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
);
}
Ok(lit)
@@ -539,7 +542,7 @@ impl<'a> MetaItemListParser<'a> {
) {
Ok(s) => Some(s),
Err(e) => {
e.emit_unless_delay(!should_emit.should_emit());
should_emit.emit_err_or_delay(e);
None
}
}

View File

@@ -577,6 +577,29 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
self.level = Level::DelayedBug;
}
/// Make emitting this diagnostic fatal
///
/// Changes the level of this diagnostic to Fatal, and importantly also changes the emission guarantee.
/// This is sound for errors that would otherwise be printed, but now simply exit the process instead.
/// This function still gives an emission guarantee, the guarantee is now just that it exits fatally.
/// For delayed bugs this is different, since those are buffered. If we upgrade one to fatal, another
/// might now be ignored.
#[rustc_lint_diagnostics]
#[track_caller]
pub fn upgrade_to_fatal(mut self) -> Diag<'a, FatalAbort> {
assert!(
matches!(self.level, Level::Error),
"upgrade_to_fatal: cannot upgrade {:?} to Fatal: not an error",
self.level
);
self.level = Level::Fatal;
// Take is okay since we immediately rewrap it in another diagnostic.
// i.e. we do emit it despite defusing the original diagnostic's drop bomb.
let diag = self.diag.take();
Diag { dcx: self.dcx, diag, _marker: PhantomData }
}
with_fn! { with_span_label,
/// Appends a labeled span to the diagnostic.
///