Skip to content

Port #[crate_name] to the new attribute parsing infrastructure #137729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3803,7 +3803,6 @@ dependencies = [
"rustc_error_messages",
"rustc_fluent_macro",
"rustc_hashes",
"rustc_hir_id",
"rustc_index",
"rustc_lexer",
"rustc_lint_defs",
Expand Down
33 changes: 33 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/crate_level.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use super::prelude::*;

pub(crate) struct CrateNameParser;

impl<S: Stage> SingleAttributeParser<S> for CrateNameParser {
const PATH: &[Symbol] = &[sym::crate_name];
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");

// FIXME: crate name is allowed on all targets and ignored,
// even though it should only be valid on crates of course
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);

fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
let ArgParser::NameValue(n) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
};

let Some(name) = n.value_as_str() else {
cx.expected_string_literal(n.value_span, Some(n.value_as_lit()));
return None;
};

Some(AttributeKind::CrateName {
name,
name_span: n.value_span,
attr_span: cx.attr_span,
style: cx.attr_style,
})
}
}
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub(crate) mod cfg;
pub(crate) mod cfg_old;
pub(crate) mod codegen_attrs;
pub(crate) mod confusables;
pub(crate) mod crate_level;
pub(crate) mod deprecation;
pub(crate) mod dummy;
pub(crate) mod inline;
Expand Down
6 changes: 1 addition & 5 deletions compiler/rustc_attr_parsing/src/attributes/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use rustc_ast::LitKind;
use rustc_ast::attr::{AttributeExt, first_attr_value_str_by_name};
use rustc_ast::attr::AttributeExt;
use rustc_feature::is_builtin_attr_name;
use rustc_hir::RustcVersion;
use rustc_span::{Symbol, sym};
Expand Down Expand Up @@ -27,10 +27,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
}

pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> {
first_attr_value_str_by_name(attrs, sym::crate_name)
}

pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
attrs: impl Iterator<Item = &'tcx T>,
symbol: Symbol,
Expand Down
29 changes: 18 additions & 11 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -23,6 +23,7 @@ use crate::attributes::codegen_attrs::{
NoMangleParser, OptimizeParser, TargetFeatureParser, TrackCallerParser, UsedParser,
};
use crate::attributes::confusables::ConfusablesParser;
use crate::attributes::crate_level::CrateNameParser;
use crate::attributes::deprecation::DeprecationParser;
use crate::attributes::dummy::DummyParser;
use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
Expand Down Expand Up @@ -165,6 +166,7 @@ attribute_parsers!(

// tidy-alphabetical-start
Single<CoverageParser>,
Single<CrateNameParser>,
Single<CustomMirParser>,
Single<DeprecationParser>,
Single<DummyParser>,
Expand Down Expand Up @@ -261,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(sess.dcx().create_err(diag))
}

fn should_emit(&self) -> ShouldEmit {
Expand Down Expand Up @@ -312,7 +310,9 @@ pub struct AcceptContext<'f, 'sess, S: Stage> {
/// The span of the attribute currently being parsed
pub(crate) attr_span: Span,

/// Whether it is an inner or outer attribute
pub(crate) attr_style: AttrStyle,

/// The expected structure of the attribute.
///
/// Used in reporting errors to give a hint to users what the attribute *should* look like.
Expand All @@ -331,7 +331,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;
Expand Down Expand Up @@ -649,8 +649,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,
Expand All @@ -660,10 +665,12 @@ pub enum ShouldEmit {
}

impl ShouldEmit {
pub fn should_emit(&self) -> bool {
pub(crate) fn emit_err(&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(),
}
}
}
34 changes: 27 additions & 7 deletions compiler/rustc_attr_parsing/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ impl<'sess> AttributeParser<'sess, Early> {
target_span: Span,
target_node_id: NodeId,
features: Option<&'sess Features>,
) -> Option<Attribute> {
Self::parse_limited_should_emit(
sess,
attrs,
sym,
target_span,
target_node_id,
features,
ShouldEmit::Nothing,
)
}

/// Usually you want `parse_limited`, which defaults to no errors.
pub fn parse_limited_should_emit(
sess: &'sess Session,
attrs: &[ast::Attribute],
sym: Symbol,
target_span: Span,
target_node_id: NodeId,
features: Option<&'sess Features>,
should_emit: ShouldEmit,
) -> Option<Attribute> {
let mut parsed = Self::parse_limited_all(
sess,
Expand All @@ -59,7 +80,7 @@ impl<'sess> AttributeParser<'sess, Early> {
target_span,
target_node_id,
features,
ShouldEmit::Nothing,
should_emit,
);
assert!(parsed.len() <= 1);
parsed.pop()
Expand All @@ -84,9 +105,8 @@ impl<'sess> AttributeParser<'sess, Early> {
target,
OmitDoc::Skip,
std::convert::identity,
|_lint| {
// FIXME: Can't emit lints here for now
// This branch can be hit when an attribute produces a warning during early parsing (such as attributes on macro calls)
|lint| {
crate::lints::emit_attribute_lint(&lint, sess);
},
)
}
Expand Down Expand Up @@ -121,8 +141,8 @@ impl<'sess> AttributeParser<'sess, Early> {
cx: &mut parser,
target_span,
target_id: target_node_id,
emit_lint: &mut |_lint| {
panic!("can't emit lints here for now (nothing uses this atm)");
emit_lint: &mut |lint| {
crate::lints::emit_attribute_lint(&lint, sess);
},
},
attr_span: attr.span,
Expand Down Expand Up @@ -252,7 +272,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,
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ pub mod validate_attr;

pub use attributes::cfg::{CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg_attr};
pub use attributes::cfg_old::*;
pub use attributes::util::{
find_crate_name, is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version,
};
pub use attributes::util::{is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version};
pub use context::{Early, Late, OmitDoc, ShouldEmit};
pub use interface::AttributeParser;
pub use lints::emit_attribute_lint;
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_attr_parsing/src/lints.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::borrow::Cow;

use rustc_errors::{DiagArgValue, LintEmitter};
use rustc_hir::Target;
use rustc_hir::lints::{AttributeLint, AttributeLintKind};
use rustc_hir::{HirId, Target};
use rustc_span::sym;

use crate::session_diagnostics;

pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emitter: L) {
pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<L::Id>, lint_emitter: L) {
let AttributeLint { id, span, kind } = lint;

match kind {
Expand Down Expand Up @@ -51,7 +51,7 @@ pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emi
*id,
*span,
session_diagnostics::InvalidTargetLint {
name,
name: name.clone(),
target: target.plural_name(),
applied: DiagArgValue::StrListSepByAnd(
applied.into_iter().map(|i| Cow::Owned(i.to_string())).collect(),
Expand Down
33 changes: 18 additions & 15 deletions compiler/rustc_attr_parsing/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
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.emit_err(
psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
);
}

Some(lit)
Expand All @@ -354,19 +355,19 @@ fn expr_to_lit(
}
}
} else {
if matches!(should_emit, ShouldEmit::Nothing) {
return None;
}

// Example cases:
// - `#[foo = 1+1]`: results in `ast::ExprKind::BinOp`.
// - `#[foo = include_str!("nonexistent-file.rs")]`:
// results in `ast::ExprKind::Err`. In that case we delay
// the error because an earlier error will have already
// been reported.
let msg = "attribute value must be a literal";
let mut err = psess.dcx().struct_span_err(span, msg);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a subtle bug in this version, fixed in my last commit. When an expression in an attribute is a macro, and the macro expands to a literal, and we parse an attribute with ShouldEmit::Nothing, we would delay that as a bug expecting a second parse to emit the error. However, when the macro then expands to a literal, we don't hit the same codepath and we never emit an error about this. The delayed bug turns into an ICE.

A very similar problem was happening in February already (#137687) which was recognized and even a backport was made for it. However, in later versions it still made it to stable.

The last commit of this PR adds tests for these cases and fixes this by, when we expect a literal, but have ShouldEmit::Nothing, we don't delay a bug, instead we do nothing. This is correct because if the macro indeed turns into a literal, the second parse might very well be correct. We shouldn't have delayed a bug at all.

Specifically for crate_name, we parse with ShouldEmit::EarlyFatal so when it cares about this being a literal, it will error about it as it should.

if let ExprKind::Err(_) = expr.kind {
err.downgrade_to_delayed_bug();
}

err.emit_unless_delay(!should_emit.should_emit());
let err = psess.dcx().struct_span_err(span, msg);
should_emit.emit_err(err);
None
}
}
Expand Down Expand Up @@ -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.emit_err(
self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
);
}

Ok(lit)
Expand Down Expand Up @@ -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(e);
None
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,9 @@ pub(crate) struct EmptyAttributeList {
#[diag(attr_parsing_invalid_target_lint)]
#[warning]
#[help]
pub(crate) struct InvalidTargetLint<'a> {
pub name: &'a AttrPath,
pub target: &'a str,
pub(crate) struct InvalidTargetLint {
pub name: AttrPath,
pub target: &'static str,
pub applied: DiagArgValue,
pub only: &'static str,
#[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")]
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_errors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ rustc_error_codes = { path = "../rustc_error_codes" }
rustc_error_messages = { path = "../rustc_error_messages" }
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
rustc_hashes = { path = "../rustc_hashes" }
rustc_hir_id = { path = "../rustc_hir_id" }
rustc_index = { path = "../rustc_index" }
rustc_lexer = { path = "../rustc_lexer" }
rustc_lint_defs = { path = "../rustc_lint_defs" }
Expand Down
23 changes: 23 additions & 0 deletions compiler/rustc_errors/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
Loading
Loading