Ports #[macro_use] and #[macro_escape] to the new attribute parsing infrastructure

This commit is contained in:
Jonathan Brouwer
2025-07-12 17:42:39 +02:00
parent a7a1618e6c
commit a460b46d0f
15 changed files with 206 additions and 65 deletions

View File

@@ -0,0 +1,115 @@
use rustc_attr_data_structures::{AttributeKind, MacroUseArgs};
use rustc_errors::DiagArgValue;
use rustc_feature::{AttributeTemplate, template};
use rustc_span::{Span, Symbol, sym};
use thin_vec::ThinVec;
use crate::attributes::{AcceptMapping, AttributeParser, NoArgsAttributeParser, OnDuplicate};
use crate::context::{AcceptContext, FinalizeContext, Stage};
use crate::parser::ArgParser;
use crate::session_diagnostics;
pub(crate) struct MacroEscapeParser;
impl<S: Stage> NoArgsAttributeParser<S> for MacroEscapeParser {
const PATH: &[Symbol] = &[sym::macro_escape];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const CREATE: fn(Span) -> AttributeKind = AttributeKind::MacroEscape;
}
/// `#[macro_use]` attributes can either:
/// - Use all macros from a crate, if provided without arguments
/// - Use specific macros from a crate, if provided with arguments `#[macro_use(macro1, macro2)]`
/// A warning should be provided if an use all is combined with specific uses, or if multiple use-alls are used.
#[derive(Default)]
pub(crate) struct MacroUseParser {
state: MacroUseArgs,
/// Spans of all `#[macro_use]` arguments with arguments, used for linting
uses_attr_spans: ThinVec<Span>,
/// If `state` is `UseSpecific`, stores the span of the first `#[macro_use]` argument, used as the span for this attribute
/// If `state` is `UseAll`, stores the span of the first `#[macro_use]` arguments without arguments
first_span: Option<Span>,
}
const MACRO_USE_TEMPLATE: AttributeTemplate = template!(Word, List: "name1, name2, ...");
impl<S: Stage> AttributeParser<S> for MacroUseParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[sym::macro_use],
MACRO_USE_TEMPLATE,
|group: &mut Self, cx: &mut AcceptContext<'_, '_, S>, args| {
let span = cx.attr_span;
group.first_span.get_or_insert(span);
match args {
ArgParser::NoArgs => {
match group.state {
MacroUseArgs::UseAll => {
let first_span = group.first_span.expect(
"State is UseAll is some so this is not the first attribute",
);
// Since there is a `#[macro_use]` import already, give a warning
cx.warn_unused_duplicate(first_span, span);
}
MacroUseArgs::UseSpecific(_) => {
group.state = MacroUseArgs::UseAll;
group.first_span = Some(span);
// If there is a `#[macro_use]` attribute, warn on all `#[macro_use(...)]` attributes since everything is already imported
for specific_use in group.uses_attr_spans.drain(..) {
cx.warn_unused_duplicate(span, specific_use);
}
}
}
}
ArgParser::List(list) => {
if list.is_empty() {
cx.warn_empty_attribute(list.span);
return;
}
match &mut group.state {
MacroUseArgs::UseAll => {
let first_span = group.first_span.expect(
"State is UseAll is some so this is not the first attribute",
);
cx.warn_unused_duplicate(first_span, span);
}
MacroUseArgs::UseSpecific(arguments) => {
// Store here so if we encounter a `UseAll` later we can still lint this attribute
group.uses_attr_spans.push(cx.attr_span);
for item in list.mixed() {
let Some(item) = item.meta_item() else {
cx.expected_identifier(item.span());
continue;
};
if let Err(err_span) = item.args().no_args() {
cx.expected_no_args(err_span);
continue;
}
let Some(item) = item.path().word() else {
cx.expected_identifier(item.span());
continue;
};
arguments.push(item);
}
}
}
}
ArgParser::NameValue(_) => {
let suggestions = MACRO_USE_TEMPLATE.suggestions(false, sym::macro_use);
cx.emit_err(session_diagnostics::IllFormedAttributeInputLint {
num_suggestions: suggestions.len(),
suggestions: DiagArgValue::StrListSepByAnd(
suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
),
span,
});
}
}
},
)];
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
Some(AttributeKind::MacroUse { span: self.first_span?, arguments: self.state })
}
}

View File

@@ -36,6 +36,7 @@ pub(crate) mod inline;
pub(crate) mod link_attrs;
pub(crate) mod lint_helpers;
pub(crate) mod loop_match;
pub(crate) mod macro_attrs;
pub(crate) mod must_use;
pub(crate) mod no_implicit_prelude;
pub(crate) mod non_exhaustive;

View File

@@ -34,7 +34,7 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
ArgParser::List(_) => {
let suggestions =
<Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "must_use");
cx.emit_err(session_diagnostics::MustUseIllFormedAttributeInput {
cx.emit_err(session_diagnostics::IllFormedAttributeInputLint {
num_suggestions: suggestions.len(),
suggestions: DiagArgValue::StrListSepByAnd(
suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),

View File

@@ -33,6 +33,7 @@ use crate::attributes::lint_helpers::{
AsPtrParser, AutomaticallyDerivedParser, PassByValueParser, PubTransparentParser,
};
use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser};
use crate::attributes::macro_attrs::{MacroEscapeParser, MacroUseParser};
use crate::attributes::must_use::MustUseParser;
use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser;
use crate::attributes::non_exhaustive::NonExhaustiveParser;
@@ -126,6 +127,7 @@ attribute_parsers!(
BodyStabilityParser,
ConfusablesParser,
ConstStabilityParser,
MacroUseParser,
NakedParser,
StabilityParser,
UsedParser,
@@ -174,6 +176,7 @@ attribute_parsers!(
Single<WithoutArgs<FfiPureParser>>,
Single<WithoutArgs<FundamentalParser>>,
Single<WithoutArgs<LoopMatchParser>>,
Single<WithoutArgs<MacroEscapeParser>>,
Single<WithoutArgs<MarkerParser>>,
Single<WithoutArgs<MayDangleParser>>,
Single<WithoutArgs<NoImplicitPreludeParser>>,
@@ -386,6 +389,17 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
})
}
/// emit an error that a `name` was expected here
pub(crate) fn expected_identifier(&self, span: Span) -> ErrorGuaranteed {
self.emit_err(AttributeParseError {
span,
attr_span: self.attr_span,
template: self.template.clone(),
attribute: self.attr_path.clone(),
reason: AttributeParseErrorReason::ExpectedIdentifier,
})
}
/// emit an error that a `name = value` pair was expected at this span. The symbol can be given for
/// a nicer error message talking about the specific name that was found lacking a value.
pub(crate) fn expected_name_value(&self, span: Span, name: Option<Symbol>) -> ErrorGuaranteed {

View File

@@ -438,7 +438,7 @@ pub(crate) struct IllFormedAttributeInput {
#[derive(Diagnostic)]
#[diag(attr_parsing_ill_formed_attribute_input)]
pub(crate) struct MustUseIllFormedAttributeInput {
pub(crate) struct IllFormedAttributeInputLint {
#[primary_span]
pub span: Span,
pub num_suggestions: usize,
@@ -549,6 +549,7 @@ pub(crate) enum AttributeParseErrorReason {
/// Should we tell the user to write a list when they didn't?
list: bool,
},
ExpectedIdentifier,
}
pub(crate) struct AttributeParseError {
@@ -600,11 +601,11 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
diag.code(E0538);
}
AttributeParseErrorReason::UnexpectedLiteral => {
diag.span_label(self.span, format!("didn't expect a literal here"));
diag.span_label(self.span, "didn't expect a literal here");
diag.code(E0565);
}
AttributeParseErrorReason::ExpectedNoArgs => {
diag.span_label(self.span, format!("didn't expect any arguments here"));
diag.span_label(self.span, "didn't expect any arguments here");
diag.code(E0565);
}
AttributeParseErrorReason::ExpectedNameValue(None) => {
@@ -684,6 +685,9 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
}
}
}
AttributeParseErrorReason::ExpectedIdentifier => {
diag.span_label(self.span, "expected a valid identifier here");
}
}
let suggestions = self.template.suggestions(false, &name);