Skip to content
Merged
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
4 changes: 2 additions & 2 deletions compiler/rustc_attr_parsing/src/attributes/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ pub fn parse_cfg<S: Stage>(
}
}

adcx.expected_single_argument(list.span);
adcx.expected_single_argument(list.span, list.len());
return None;
};
parse_cfg_entry(cx, single).ok()
Expand All @@ -93,7 +93,7 @@ pub fn parse_cfg_entry<S: Stage>(
ArgParser::List(list) => match meta.path().word_sym() {
Some(sym::not) => {
let Some(single) = list.single() else {
return Err(cx.adcx().expected_single_argument(list.span));
return Err(cx.adcx().expected_single_argument(list.span, list.len()));
};
CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
}
Expand Down
28 changes: 5 additions & 23 deletions compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,7 @@ impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]);

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

let Some(single) = list.single() else {
cx.adcx().expected_single_argument(list.span);
return None;
};
let single = cx.single_element_list(args, cx.attr_span)?;

let res = match single.meta_item().and_then(|i| i.path().word().map(|i| i.name)) {
Some(sym::size) => OptimizeAttr::Size,
Expand Down Expand Up @@ -84,22 +75,13 @@ impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]);

fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(args) = args.list() else {
let attr_span = cx.attr_span;
cx.adcx().expected_specific_argument_and_list(attr_span, &[sym::on, sym::off]);
return None;
};

let Some(arg) = args.single() else {
cx.adcx().expected_single_argument(args.span);
return None;
};
let arg = cx.single_element_list(args, cx.attr_span)?;

let mut fail_incorrect_argument =
|span| cx.adcx().expected_specific_argument(span, &[sym::on, sym::off]);

let Some(arg) = arg.meta_item() else {
fail_incorrect_argument(args.span);
fail_incorrect_argument(arg.span());
return None;
};

Expand Down Expand Up @@ -394,7 +376,7 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
ArgParser::NoArgs => UsedBy::Default,
ArgParser::List(list) => {
let Some(l) = list.single() else {
cx.adcx().expected_single_argument(list.span);
cx.adcx().expected_single_argument(list.span, list.len());
return;
};

Expand Down Expand Up @@ -755,7 +737,7 @@ impl<S: Stage> SingleAttributeParser<S> for PatchableFunctionEntryParser {
let mut entry = None;

if meta_item_list.len() == 0 {
cx.adcx().expected_list(meta_item_list.span, args);
cx.adcx().expected_at_least_one_argument(meta_item_list.span);
return None;
}

Expand Down
10 changes: 1 addition & 9 deletions compiler/rustc_attr_parsing/src/attributes/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@ impl<S: Stage> CombineAttributeParser<S> for DebuggerViualizerParser {
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let Some(l) = args.list() else {
let attr_span = cx.attr_span;
cx.adcx().expected_list(attr_span, args);
return None;
};
let Some(single) = l.single() else {
cx.adcx().expected_single_argument(l.span);
return None;
};
let single = cx.single_element_list(args, cx.attr_span)?;
let Some(mi) = single.meta_item() else {
cx.adcx().expected_name_value(single.span(), None);
return None;
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_attr_parsing/src/attributes/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
ArgParser::NoArgs => Some(AttributeKind::Inline(InlineAttr::Hint, cx.attr_span)),
ArgParser::List(list) => {
let Some(l) = list.single() else {
cx.adcx().expected_single_argument(list.span);
cx.adcx().expected_single_argument(list.span, list.len());
return None;
};

Expand Down Expand Up @@ -80,7 +80,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
ArgParser::NoArgs => None,
ArgParser::List(list) => {
let Some(l) = list.single() else {
cx.adcx().expected_single_argument(list.span);
cx.adcx().expected_single_argument(list.span, list.len());
return None;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ impl<S: Stage> SingleAttributeParser<S> for InstructionSetParser {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
const POSSIBLE_SYMBOLS: &[Symbol] = &[sym::arm_a32, sym::arm_t32];
const POSSIBLE_ARM_SYMBOLS: &[Symbol] = &[sym::a32, sym::t32];
let Some(maybe_meta_item) = args.list().and_then(MetaItemListParser::single) else {
let attr_span = cx.attr_span;
cx.adcx().expected_specific_argument(attr_span, POSSIBLE_SYMBOLS);
return None;
};
let maybe_meta_item = cx.single_element_list(args, cx.attr_span)?;

let Some(meta_item) = maybe_meta_item.meta_item() else {
cx.adcx().expected_specific_argument(maybe_meta_item.span(), POSSIBLE_SYMBOLS);
Expand Down
7 changes: 1 addition & 6 deletions compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,7 @@ impl LinkParser {
cx.adcx().duplicate_key(item.span(), sym::cfg);
return true;
}
let Some(link_cfg) = item.args().list() else {
cx.adcx().expected_list(item.span(), item.args());
return true;
};
let Some(link_cfg) = link_cfg.single() else {
cx.adcx().expected_single_argument(item.span());
let Some(link_cfg) = cx.single_element_list(item.args(), item.span()) else {
return true;
};
if !features.link_cfg() {
Expand Down
10 changes: 1 addition & 9 deletions compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,7 @@ impl<S: Stage> SingleAttributeParser<S> for CollapseDebugInfoParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::MacroDef)]);

fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
let attr_span = cx.attr_span;
cx.adcx().expected_list(attr_span, args);
return None;
};
let Some(single) = list.single() else {
cx.adcx().expected_single_argument(list.span);
return None;
};
let single = cx.single_element_list(args, cx.attr_span)?;
let Some(mi) = single.meta_item() else {
cx.adcx().expected_not_literal(single.span());
return None;
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_attr_parsing/src/attributes/prototype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fn extract_value<S: Stage>(
}

let Some(val) = arg.name_value() else {
cx.adcx().expected_single_argument(arg.span().unwrap_or(span));
cx.adcx().expected_name_value(span, Some(key));
*failed = true;
return;
};
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_attr_parsing/src/attributes/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ impl RustcAlignParser {
}
ArgParser::List(list) => {
let Some(align) = list.single() else {
cx.adcx().expected_single_argument(list.span);
cx.adcx().expected_single_argument(list.span, list.len());
return;
};

Expand Down
27 changes: 5 additions & 22 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLintOptDenyFieldAccessParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Field)]);
const TEMPLATE: AttributeTemplate = template!(Word);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(arg) = args.list().and_then(MetaItemListParser::single) else {
let attr_span = cx.attr_span;
cx.adcx().expected_single_argument(attr_span);
return None;
};
let arg = cx.single_element_list(args, cx.attr_span)?;

let MetaItemOrLitParser::Lit(MetaItemLit { kind: LitKind::Str(lint_message, _), .. }) = arg
else {
Expand Down Expand Up @@ -375,19 +371,10 @@ impl<S: Stage> SingleAttributeParser<S> for RustcDeprecatedSafe2024Parser {
const TEMPLATE: AttributeTemplate = template!(List: &[r#"audit_that = "...""#]);

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

let Some(single) = args.single() else {
cx.adcx().expected_single_argument(args.span);
return None;
};
let single = cx.single_element_list(args, cx.attr_span)?;

let Some(arg) = single.meta_item() else {
cx.adcx().expected_name_value(args.span, None);
cx.adcx().expected_name_value(single.span(), None);
return None;
};

Expand Down Expand Up @@ -1022,7 +1009,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcIfThisChangedParser {
ArgParser::List(list) => {
let Some(item) = list.single() else {
let attr_span = cx.attr_span;
cx.adcx().expected_single_argument(attr_span);
cx.adcx().expected_single_argument(attr_span, list.len());
return None;
};
let Some(ident) = item.meta_item().and_then(|item| item.ident()) else {
Expand Down Expand Up @@ -1082,11 +1069,7 @@ impl<S: Stage> CombineAttributeParser<S> for RustcThenThisWouldNeedParser {
if !cx.cx.sess.opts.unstable_opts.query_dep_graph {
cx.emit_err(AttributeRequiresOpt { span: cx.attr_span, opt: "-Z query-dep-graph" });
}
let Some(item) = args.list().and_then(|l| l.single()) else {
let inner_span = cx.inner_span;
cx.adcx().expected_single_argument(inner_span);
return None;
};
let item = cx.single_element_list(args, cx.attr_span)?;
let Some(ident) = item.meta_item().and_then(|item| item.ident()) else {
cx.adcx().expected_identifier(item.span());
return None;
Expand Down
15 changes: 3 additions & 12 deletions compiler/rustc_attr_parsing/src/attributes/test_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<S: Stage> SingleAttributeParser<S> for ShouldPanicParser {
}
ArgParser::List(list) => {
let Some(single) = list.single() else {
cx.adcx().expected_single_argument(list.span);
cx.adcx().expected_single_argument(list.span, list.len());
return None;
};
let Some(single) = single.meta_item() else {
Expand Down Expand Up @@ -150,7 +150,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcAbiParser {

let Some(arg) = args.single() else {
let attr_span = cx.attr_span;
cx.adcx().expected_single_argument(attr_span);
cx.adcx().expected_single_argument(attr_span, args.len());
return None;
};

Expand Down Expand Up @@ -208,16 +208,7 @@ impl<S: Stage> SingleAttributeParser<S> for TestRunnerParser {
const TEMPLATE: AttributeTemplate = template!(List: &["path"]);

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

let Some(single) = list.single() else {
cx.adcx().expected_single_argument(list.span);
return None;
};
let single = cx.single_element_list(args, cx.attr_span)?;

let Some(meta) = single.meta_item() else {
cx.adcx().expected_not_literal(single.span());
Expand Down
10 changes: 1 addition & 9 deletions compiler/rustc_attr_parsing/src/attributes/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,7 @@ pub(crate) fn parse_single_integer<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> Option<u128> {
let Some(list) = args.list() else {
let attr_span = cx.attr_span;
cx.adcx().expected_list(attr_span, args);
return None;
};
let Some(single) = list.single() else {
cx.adcx().expected_single_argument(list.span);
return None;
};
let single = cx.single_element_list(args, cx.attr_span)?;
let Some(lit) = single.lit() else {
cx.adcx().expected_integer_literal(single.span());
return None;
Expand Down
54 changes: 50 additions & 4 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use crate::attributes::test_attrs::*;
use crate::attributes::traits::*;
use crate::attributes::transparency::*;
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
use crate::parser::{ArgParser, RefPathParser};
use crate::parser::{ArgParser, MetaItemOrLitParser, RefPathParser};
use crate::session_diagnostics::{
AttributeParseError, AttributeParseErrorReason, AttributeParseErrorSuggestions,
ParsedDescription,
Expand Down Expand Up @@ -502,6 +502,36 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
pub(crate) fn adcx(&mut self) -> AttributeDiagnosticContext<'_, 'f, 'sess, S> {
AttributeDiagnosticContext { ctx: self, custom_suggestions: Vec::new() }
}

/// Asserts that this MetaItem is a list that contains a single element. Emits an error and
/// returns `None` if it is not the case.
///
/// Some examples:
///
/// - In `#[allow(warnings)]`, `warnings` is returned
/// - In `#[cfg_attr(docsrs, doc = "foo")]`, `None` is returned, "expected a single argument
/// here" is emitted.
/// - In `#[cfg()]`, `None` is returned, "expected an argument here" is emitted.
///
/// The provided span is used as a fallback for diagnostic generation in case `arg` does not
/// contain any. It should be the span of the node that contains `arg`.
pub(crate) fn single_element_list<'arg>(
&mut self,
arg: &'arg ArgParser,
span: Span,
) -> Option<&'arg MetaItemOrLitParser> {
let ArgParser::List(l) = arg else {
self.adcx().expected_list(span, arg);
return None;
};

let Some(single) = l.single() else {
Copy link
Copy Markdown
Contributor

@jdonszelmann jdonszelmann Apr 10, 2026

Choose a reason for hiding this comment

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

we should probably put a #[deprecated = "use single_element_list instead"] on .single().

Copy link
Copy Markdown
Contributor Author

@scrabsha scrabsha Apr 10, 2026

Choose a reason for hiding this comment

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

I tried this locally and I'm not sure it's a good idea. There are some patterns in which calling single() is still legit. Here are a couple I found:

In test_attrs:74:

match args {
    // Many match args for all kinds of ArgParser

    ArgParser::List(list) => {
        let Some(single) = list.single() else { /* ... /* };
    }
}

There is a similar pattern in inline.rs:40, cfg.rs:95, attributes/mod.rs:407 and many others.

In cfg.rs:52:

let ArgParser::List(list) = args else {
    let attr_span = cx.attr_span;
    cx.adcx().expected_list(attr_span, args);
    return None;
};

let Some(single) = list.single() else {
    // Lots and lots of code generating cute error messages.
};

I don't really see any replacement for these patterns. If you want, I can review exhaustively review every use of .single() and see what I can do, but I honestly doubt we'd find anything to improve. What do you think?

@rustbot ready

self.adcx().expected_single_argument(l.span, l.len());
return None;
};

Some(single)
}
}

impl<'f, 'sess, S: Stage> Deref for AcceptContext<'f, 'sess, S> {
Expand Down Expand Up @@ -689,6 +719,8 @@ where
)
}

/// The provided span is used as a fallback in case `args` does not contain any. It should be
/// the span of the node that contains `args`.
pub(crate) fn expected_list(&mut self, span: Span, args: &ArgParser) -> ErrorGuaranteed {
let span = match args {
ArgParser::NoArgs => span,
Expand Down Expand Up @@ -745,14 +777,28 @@ where
self.emit_parse_error(span, AttributeParseErrorReason::DuplicateKey(key))
}

/// An error that should be emitted when a [`MetaItemOrLitParser`](crate::parser::MetaItemOrLitParser)
/// An error that should be emitted when a [`MetaItemOrLitParser`]
/// was expected *not* to be a literal, but instead a meta item.
pub(crate) fn expected_not_literal(&mut self, span: Span) -> ErrorGuaranteed {
self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNotLiteral)
}

pub(crate) fn expected_single_argument(&mut self, span: Span) -> ErrorGuaranteed {
self.emit_parse_error(span, AttributeParseErrorReason::ExpectedSingleArgument)
/// Signals that we expected exactly one argument and that we got either zero or two or more.
/// The `provided_arguments` argument allows distinguishing between "expected an argument here"
/// (when zero arguments are provided) and "expect a single argument here" (when two or more
/// arguments are provided).
pub(crate) fn expected_single_argument(
&mut self,
span: Span,
provided_arguments: usize,
) -> ErrorGuaranteed {
let reason = if provided_arguments == 0 {
AttributeParseErrorReason::ExpectedArgument
} else {
AttributeParseErrorReason::ExpectedSingleArgument
};

self.emit_parse_error(span, reason)
}

pub(crate) fn expected_at_least_one_argument(&mut self, span: Span) -> ErrorGuaranteed {
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ pub(crate) enum AttributeParseErrorReason<'a> {
upper_bound: isize,
},
ExpectedAtLeastOneArgument,
ExpectedArgument,
ExpectedSingleArgument,
ExpectedList,
ExpectedListOrNoArgs,
Expand Down Expand Up @@ -777,6 +778,10 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
diag.span_label(self.span, "expected a single argument here");
diag.code(E0805);
}
AttributeParseErrorReason::ExpectedArgument => {
diag.span_label(self.span, "expected an argument here");
diag.code(E0805);
}
AttributeParseErrorReason::ExpectedAtLeastOneArgument => {
diag.span_label(self.span, "expected at least 1 argument here");
}
Expand Down
Loading
Loading