From a863312fa59c57f5b5e4685865539a657d39f06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Sun, 24 Aug 2025 11:21:18 +0200 Subject: [PATCH 1/4] port `#[feature]` to the new attribute parsing infrastructure --- compiler/rustc_attr_parsing/messages.ftl | 4 + .../src/attributes/crate_level.rs | 57 +++++++- compiler/rustc_attr_parsing/src/context.rs | 6 +- .../src/session_diagnostics.rs | 11 ++ .../rustc_hir/src/attrs/data_structures.rs | 3 + .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 138 +++++++++--------- 7 files changed, 152 insertions(+), 68 deletions(-) diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 81ec17077c13c..510a065d6a85f 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -259,3 +259,7 @@ attr_parsing_whole_archive_needs_static = attr_parsing_limit_invalid = `limit` must be a non-negative integer .label = {$error_str} + +attr_parsing_feature_single_word = + rust features are always a single identifier, not paths with multiple segments + .help = did you maybe mean `{$first_segment}`? diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 0a340cd5e9330..eb8467a93afb9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -3,7 +3,7 @@ use std::num::IntErrorKind; use rustc_hir::limit::Limit; use super::prelude::*; -use crate::session_diagnostics::LimitInvalid; +use crate::session_diagnostics::{FeatureExpectedSingleWord, LimitInvalid}; impl AcceptContext<'_, '_, S> { fn parse_limit_int(&self, nv: &NameValueParser) -> Option { @@ -183,3 +183,58 @@ impl NoArgsAttributeParser for RustcCoherenceIsCoreParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel; const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcCoherenceIsCore; } + +pub(crate) struct FeatureParser; + +impl CombineAttributeParser for FeatureParser { + const PATH: &[Symbol] = &[sym::feature]; + type Item = Ident; + const CONVERT: ConvertFn = AttributeKind::Feature; + + // FIXME: recursion limit 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); + const TEMPLATE: AttributeTemplate = template!(List: &["feature1, feature2, ..."]); + + fn extend<'c>( + cx: &'c mut AcceptContext<'_, '_, S>, + args: &'c ArgParser<'_>, + ) -> impl IntoIterator + 'c { + let ArgParser::List(list) = args else { + cx.expected_list(cx.attr_span); + return Vec::new(); + }; + + if list.is_empty() { + cx.warn_empty_attribute(cx.attr_span); + } + + let mut res = Vec::new(); + + for elem in list.mixed() { + let Some(elem) = elem.meta_item() else { + cx.expected_identifier(elem.span()); + continue; + }; + if let Err(arg_span) = elem.args().no_args() { + cx.expected_no_args(arg_span); + continue; + } + + let path = elem.path(); + let Some(ident) = path.word() else { + let first_segment = elem.path().segments().next().expect("at least one segment"); + cx.emit_err(FeatureExpectedSingleWord { + span: path.span(), + first_segment_span: first_segment.span, + first_segment: first_segment.name, + }); + continue; + }; + + res.push(ident); + } + + res + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index ee5b7322b0247..360bb443888a0 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -25,8 +25,9 @@ use crate::attributes::codegen_attrs::{ }; use crate::attributes::confusables::ConfusablesParser; use crate::attributes::crate_level::{ - CrateNameParser, MoveSizeLimitParser, NoCoreParser, NoStdParser, PatternComplexityLimitParser, - RecursionLimitParser, RustcCoherenceIsCoreParser, TypeLengthLimitParser, + CrateNameParser, FeatureParser, MoveSizeLimitParser, NoCoreParser, NoStdParser, + PatternComplexityLimitParser, RecursionLimitParser, RustcCoherenceIsCoreParser, + TypeLengthLimitParser, }; use crate::attributes::deprecation::DeprecationParser; use crate::attributes::dummy::DummyParser; @@ -163,6 +164,7 @@ attribute_parsers!( // tidy-alphabetical-start Combine, Combine, + Combine, Combine, Combine, Combine, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 2c2b14c8a68bc..30fb078ef4070 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -968,3 +968,14 @@ pub(crate) struct LimitInvalid<'a> { pub value_span: Span, pub error_str: &'a str, } + +#[derive(Diagnostic)] +#[diag(attr_parsing_feature_single_word)] +pub(crate) struct FeatureExpectedSingleWord { + #[primary_span] + pub span: Span, + + #[help] + pub first_segment_span: Span, + pub first_segment: Symbol, +} diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 0784675b177a0..4a39802035aaf 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -511,6 +511,9 @@ pub enum AttributeKind { /// Represents `#[export_stable]`. ExportStable, + /// Represents `#[feature(...)]` + Feature(ThinVec, Span), + /// Represents `#[ffi_const]`. FfiConst(Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 563e7a58c6d5e..51b1c3eeb298b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -44,6 +44,7 @@ impl AttributeKind { Dummy => No, ExportName { .. } => Yes, ExportStable => No, + Feature(..) => No, FfiConst(..) => No, FfiPure(..) => No, Fundamental { .. } => Yes, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 4d5a8447695be..bcb147b5fae39 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -278,6 +278,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::ObjcClass { .. } | AttributeKind::ObjcSelector { .. } | AttributeKind::RustcCoherenceIsCore(..) + | AttributeKind::Feature(..) ) => { /* do nothing */ } Attribute::Unparsed(attr_item) => { style = Some(attr_item.style); @@ -1895,75 +1896,82 @@ impl<'tcx> CheckAttrVisitor<'tcx> { fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option) { // Warn on useless empty attributes. // FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute` - let note = if attr.has_any_name(&[ - sym::allow, - sym::expect, - sym::warn, - sym::deny, - sym::forbid, - sym::feature, - ]) && attr.meta_item_list().is_some_and(|list| list.is_empty()) - { - errors::UnusedNote::EmptyList { name: attr.name().unwrap() } - } else if attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect]) - && let Some(meta) = attr.meta_item_list() - && let [meta] = meta.as_slice() - && let Some(item) = meta.meta_item() - && let MetaItemKind::NameValue(_) = &item.kind - && item.path == sym::reason - { - errors::UnusedNote::NoLints { name: attr.name().unwrap() } - } else if attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect]) - && let Some(meta) = attr.meta_item_list() - && meta.iter().any(|meta| { - meta.meta_item().map_or(false, |item| item.path == sym::linker_messages) - }) - { - if hir_id != CRATE_HIR_ID { - match style { - Some(ast::AttrStyle::Outer) => { - let attr_span = attr.span(); - let bang_position = self - .tcx - .sess - .source_map() - .span_until_char(attr_span, '[') - .shrink_to_hi(); - - self.tcx.emit_node_span_lint( + let note = + if attr.has_any_name(&[sym::allow, sym::expect, sym::warn, sym::deny, sym::forbid]) + && attr.meta_item_list().is_some_and(|list| list.is_empty()) + { + errors::UnusedNote::EmptyList { name: attr.name().unwrap() } + } else if attr.has_any_name(&[ + sym::allow, + sym::warn, + sym::deny, + sym::forbid, + sym::expect, + ]) && let Some(meta) = attr.meta_item_list() + && let [meta] = meta.as_slice() + && let Some(item) = meta.meta_item() + && let MetaItemKind::NameValue(_) = &item.kind + && item.path == sym::reason + { + errors::UnusedNote::NoLints { name: attr.name().unwrap() } + } else if attr.has_any_name(&[ + sym::allow, + sym::warn, + sym::deny, + sym::forbid, + sym::expect, + ]) && let Some(meta) = attr.meta_item_list() + && meta.iter().any(|meta| { + meta.meta_item().map_or(false, |item| item.path == sym::linker_messages) + }) + { + if hir_id != CRATE_HIR_ID { + match style { + Some(ast::AttrStyle::Outer) => { + let attr_span = attr.span(); + let bang_position = self + .tcx + .sess + .source_map() + .span_until_char(attr_span, '[') + .shrink_to_hi(); + + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr_span, + errors::OuterCrateLevelAttr { + suggestion: errors::OuterCrateLevelAttrSuggestion { + bang_position, + }, + }, + ) + } + Some(ast::AttrStyle::Inner) | None => self.tcx.emit_node_span_lint( UNUSED_ATTRIBUTES, hir_id, - attr_span, - errors::OuterCrateLevelAttr { - suggestion: errors::OuterCrateLevelAttrSuggestion { bang_position }, - }, - ) - } - Some(ast::AttrStyle::Inner) | None => self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - attr.span(), - errors::InnerCrateLevelAttr, - ), - }; - return; - } else { - let never_needs_link = self - .tcx - .crate_types() - .iter() - .all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib)); - if never_needs_link { - errors::UnusedNote::LinkerMessagesBinaryCrateOnly - } else { + attr.span(), + errors::InnerCrateLevelAttr, + ), + }; return; + } else { + let never_needs_link = self + .tcx + .crate_types() + .iter() + .all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib)); + if never_needs_link { + errors::UnusedNote::LinkerMessagesBinaryCrateOnly + } else { + return; + } } - } - } else if attr.has_name(sym::default_method_body_is_const) { - errors::UnusedNote::DefaultMethodBodyConst - } else { - return; - }; + } else if attr.has_name(sym::default_method_body_is_const) { + errors::UnusedNote::DefaultMethodBodyConst + } else { + return; + }; self.tcx.emit_node_span_lint( UNUSED_ATTRIBUTES, From bceec5b2a202edb292685f883b504fa8f1495efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 16 Sep 2025 11:12:01 -0700 Subject: [PATCH 2/4] use feature parser throughout the compiler --- Cargo.lock | 1 + compiler/rustc_ast_passes/Cargo.toml | 1 + compiler/rustc_ast_passes/src/feature_gate.rs | 23 ++- .../src/attributes/crate_level.rs | 5 +- .../src/error_codes/E0556.md | 4 +- compiler/rustc_error_codes/src/lib.rs | 2 +- compiler/rustc_expand/src/config.rs | 87 +++++------- compiler/rustc_expand/src/errors.rs | 24 ---- compiler/rustc_lint/src/builtin.rs | 10 +- tests/pretty/hir-delegation.pp | 2 +- tests/pretty/hir-fn-variadic.pp | 10 +- tests/pretty/pin-ergonomics-hir.pp | 2 +- tests/ui/empty/empty-attributes.stderr | 14 +- .../feature-gate-feature-gate.rs | 2 +- .../ui/feature-gates/gated-bad-feature.stderr | 48 ++++--- .../issue-43106-gating-of-builtin-attrs.rs | 12 +- ...issue-43106-gating-of-builtin-attrs.stderr | 131 ++++++++++-------- .../type-alias-impl-trait/issue-60662.stdout | 10 +- tests/ui/unpretty/exhaustive.hir.stdout | 25 +--- 19 files changed, 192 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d39cfefea0c77..db94ff94d69d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3402,6 +3402,7 @@ dependencies = [ "rustc_errors", "rustc_feature", "rustc_fluent_macro", + "rustc_hir", "rustc_macros", "rustc_session", "rustc_span", diff --git a/compiler/rustc_ast_passes/Cargo.toml b/compiler/rustc_ast_passes/Cargo.toml index 3e04f8b11ec9d..38c9e67ca0a95 100644 --- a/compiler/rustc_ast_passes/Cargo.toml +++ b/compiler/rustc_ast_passes/Cargo.toml @@ -14,6 +14,7 @@ rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } +rustc_hir = { path = "../rustc_hir" } rustc_macros = { path = "../rustc_macros" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 9ab5b0b354726..52083875d7a0e 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -1,11 +1,14 @@ use rustc_ast as ast; use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::{NodeId, PatKind, attr, token}; +use rustc_attr_parsing::AttributeParser; use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features}; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; use rustc_session::Session; use rustc_session::parse::{feature_err, feature_warn}; use rustc_span::source_map::Spanned; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use thin_vec::ThinVec; use crate::errors; @@ -587,17 +590,27 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate) return; } let mut errored = false; - for attr in krate.attrs.iter().filter(|attr| attr.has_name(sym::feature)) { + + if let Some(Attribute::Parsed(AttributeKind::Feature(feature_idents, first_span))) = + AttributeParser::parse_limited( + sess, + &krate.attrs, + sym::feature, + DUMMY_SP, + krate.id, + Some(&features), + ) + { // `feature(...)` used on non-nightly. This is definitely an error. let mut err = errors::FeatureOnNonNightly { - span: attr.span, + span: first_span, channel: option_env!("CFG_RELEASE_CHANNEL").unwrap_or("(unknown)"), stable_features: vec![], sugg: None, }; let mut all_stable = true; - for ident in attr.meta_item_list().into_iter().flatten().flat_map(|nested| nested.ident()) { + for ident in feature_idents { let name = ident.name; let stable_since = features .enabled_lang_features() @@ -612,7 +625,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate) } } if all_stable { - err.sugg = Some(attr.span); + err.sugg = Some(first_span); } sess.dcx().emit_err(err); errored = true; diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index eb8467a93afb9..8b7801c5279f4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -190,10 +190,7 @@ impl CombineAttributeParser for FeatureParser { const PATH: &[Symbol] = &[sym::feature]; type Item = Ident; const CONVERT: ConvertFn = AttributeKind::Feature; - - // FIXME: recursion limit 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); + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel; const TEMPLATE: AttributeTemplate = template!(List: &["feature1, feature2, ..."]); fn extend<'c>( diff --git a/compiler/rustc_error_codes/src/error_codes/E0556.md b/compiler/rustc_error_codes/src/error_codes/E0556.md index 2aac8240d293a..d1eeddc3ab102 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0556.md +++ b/compiler/rustc_error_codes/src/error_codes/E0556.md @@ -1,8 +1,10 @@ +#### Note: this error code is no longer emitted by the compiler. + The `feature` attribute was badly formed. Erroneous code example: -```compile_fail,E0556 +```compile_fail #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] // error! #![feature] // error! #![feature = "foo"] // error! diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs index 0aff1c06e0a8c..adac13a315fee 100644 --- a/compiler/rustc_error_codes/src/lib.rs +++ b/compiler/rustc_error_codes/src/lib.rs @@ -338,7 +338,7 @@ E0550: 0550, E0551: 0551, E0552: 0552, E0554: 0554, -E0556: 0556, +E0556: 0556, // REMOVED: merged with other attribute error codes E0557: 0557, E0559: 0559, E0560: 0560, diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 2925e337071ca..d8ef77febf1be 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -7,8 +7,8 @@ use rustc_ast::tokenstream::{ AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree, }; use rustc_ast::{ - self as ast, AttrKind, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner, - NodeId, NormalAttr, + self as ast, AttrKind, AttrStyle, Attribute, DUMMY_NODE_ID, HasAttrs, HasTokens, MetaItem, + MetaItemInner, NodeId, NormalAttr, }; use rustc_attr_parsing as attr; use rustc_attr_parsing::validate_attr::deny_builtin_meta_unsafety; @@ -21,16 +21,16 @@ use rustc_feature::{ ACCEPTED_LANG_FEATURES, AttributeSafety, EnabledLangFeature, EnabledLibFeature, Features, REMOVED_LANG_FEATURES, UNSTABLE_LANG_FEATURES, }; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::{self as hir}; use rustc_session::Session; use rustc_session::parse::feature_err; -use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym}; -use thin_vec::ThinVec; +use rustc_span::{DUMMY_SP, STDLIB_STABLE_CRATES, Span, Symbol, sym}; use tracing::instrument; use crate::errors::{ CrateNameInCfgAttr, CrateTypeInCfgAttr, FeatureNotAllowed, FeatureRemoved, - FeatureRemovedReason, InvalidCfg, MalformedFeatureAttribute, MalformedFeatureAttributeHelp, - RemoveExprNotSupported, + FeatureRemovedReason, InvalidCfg, RemoveExprNotSupported, }; /// A folder that strips out items that do not belong in the current configuration. @@ -45,44 +45,21 @@ pub struct StripUnconfigured<'a> { } pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -> Features { - fn feature_list(attr: &Attribute) -> ThinVec { - if attr.has_name(sym::feature) - && let Some(list) = attr.meta_item_list() - { - list - } else { - ThinVec::new() - } - } - let mut features = Features::default(); - // Process all features enabled in the code. - for attr in krate_attrs { - for mi in feature_list(attr) { - let name = match mi.ident() { - Some(ident) if mi.is_word() => ident.name, - Some(ident) => { - sess.dcx().emit_err(MalformedFeatureAttribute { - span: mi.span(), - help: MalformedFeatureAttributeHelp::Suggestion { - span: mi.span(), - suggestion: ident.name, - }, - }); - continue; - } - None => { - sess.dcx().emit_err(MalformedFeatureAttribute { - span: mi.span(), - help: MalformedFeatureAttributeHelp::Label { span: mi.span() }, - }); - continue; - } - }; - + if let Some(hir::Attribute::Parsed(AttributeKind::Feature(feature_idents, _))) = + AttributeParser::parse_limited( + sess, + krate_attrs, + sym::feature, + DUMMY_SP, + DUMMY_NODE_ID, + Some(&features), + ) + { + for name in feature_idents { // If the enabled feature has been removed, issue an error. - if let Some(f) = REMOVED_LANG_FEATURES.iter().find(|f| name == f.feature.name) { + if let Some(f) = REMOVED_LANG_FEATURES.iter().find(|f| name.name == f.feature.name) { let pull_note = if let Some(pull) = f.pull { format!( "; see for more information", @@ -92,7 +69,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - "".to_owned() }; sess.dcx().emit_err(FeatureRemoved { - span: mi.span(), + span: name.span, reason: f.reason.map(|reason| FeatureRemovedReason { reason }), removed_rustc_version: f.feature.since, pull_note, @@ -101,10 +78,10 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - } // If the enabled feature is stable, record it. - if let Some(f) = ACCEPTED_LANG_FEATURES.iter().find(|f| name == f.name) { + if let Some(f) = ACCEPTED_LANG_FEATURES.iter().find(|f| name.name == f.name) { features.set_enabled_lang_feature(EnabledLangFeature { - gate_name: name, - attr_sp: mi.span(), + gate_name: name.name, + attr_sp: name.span, stable_since: Some(Symbol::intern(f.since)), }); continue; @@ -114,25 +91,25 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - // unstable and not also listed as one of the allowed features, // issue an error. if let Some(allowed) = sess.opts.unstable_opts.allow_features.as_ref() { - if allowed.iter().all(|f| name.as_str() != f) { - sess.dcx().emit_err(FeatureNotAllowed { span: mi.span(), name }); + if allowed.iter().all(|f| name.name.as_str() != f) { + sess.dcx().emit_err(FeatureNotAllowed { span: name.span, name: name.name }); continue; } } // If the enabled feature is unstable, record it. - if UNSTABLE_LANG_FEATURES.iter().find(|f| name == f.name).is_some() { + if UNSTABLE_LANG_FEATURES.iter().find(|f| name.name == f.name).is_some() { // When the ICE comes a standard library crate, there's a chance that the person // hitting the ICE may be using -Zbuild-std or similar with an untested target. // The bug is probably in the standard library and not the compiler in that case, // but that doesn't really matter - we want a bug report. - if features.internal(name) && !STDLIB_STABLE_CRATES.contains(&crate_name) { + if features.internal(name.name) && !STDLIB_STABLE_CRATES.contains(&crate_name) { sess.using_internal_features.store(true, std::sync::atomic::Ordering::Relaxed); } features.set_enabled_lang_feature(EnabledLangFeature { - gate_name: name, - attr_sp: mi.span(), + gate_name: name.name, + attr_sp: name.span, stable_since: None, }); continue; @@ -140,12 +117,14 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - // Otherwise, the feature is unknown. Enable it as a lib feature. // It will be checked later whether the feature really exists. - features - .set_enabled_lib_feature(EnabledLibFeature { gate_name: name, attr_sp: mi.span() }); + features.set_enabled_lib_feature(EnabledLibFeature { + gate_name: name.name, + attr_sp: name.span, + }); // Similar to above, detect internal lib features to suppress // the ICE message that asks for a report. - if features.internal(name) && !STDLIB_STABLE_CRATES.contains(&crate_name) { + if features.internal(name.name) && !STDLIB_STABLE_CRATES.contains(&crate_name) { sess.using_internal_features.store(true, std::sync::atomic::Ordering::Relaxed); } } diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index c37c2d88d9cd2..d54fe88e798c8 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -143,30 +143,6 @@ pub(crate) struct RecursionLimitReached { pub crate_name: Symbol, } -#[derive(Diagnostic)] -#[diag(expand_malformed_feature_attribute, code = E0556)] -pub(crate) struct MalformedFeatureAttribute { - #[primary_span] - pub span: Span, - #[subdiagnostic] - pub help: MalformedFeatureAttributeHelp, -} - -#[derive(Subdiagnostic)] -pub(crate) enum MalformedFeatureAttributeHelp { - #[label(expand_expected)] - Label { - #[primary_span] - span: Span, - }, - #[suggestion(expand_expected, code = "{suggestion}", applicability = "maybe-incorrect")] - Suggestion { - #[primary_span] - span: Span, - suggestion: Symbol, - }, -} - #[derive(Diagnostic)] #[diag(expand_remove_expr_not_supported)] pub(crate) struct RemoveExprNotSupported { diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 75a0f89321b97..f03adf700eb5d 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1133,12 +1133,10 @@ declare_lint_pass!( ); impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { - fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &hir::Attribute) { - if attr.has_name(sym::feature) - && let Some(items) = attr.meta_item_list() - { - for item in items { - cx.emit_span_lint(UNSTABLE_FEATURES, item.span(), BuiltinUnstableFeatures); + fn check_attributes(&mut self, cx: &LateContext<'_>, attrs: &[hir::Attribute]) { + if let Some(features) = find_attr!(attrs, AttributeKind::Feature(features, _) => features) { + for feature in features { + cx.emit_span_lint(UNSTABLE_FEATURES, feature.span, BuiltinUnstableFeatures); } } } diff --git a/tests/pretty/hir-delegation.pp b/tests/pretty/hir-delegation.pp index f8ad02f2fccce..813b08a18970a 100644 --- a/tests/pretty/hir-delegation.pp +++ b/tests/pretty/hir-delegation.pp @@ -3,7 +3,7 @@ //@ pp-exact:hir-delegation.pp #![allow(incomplete_features)] -#![feature(fn_delegation)] +#![attr = Feature([fn_delegation#0])] #[attr = MacroUse {arguments: UseAll}] extern crate std; #[prelude_import] diff --git a/tests/pretty/hir-fn-variadic.pp b/tests/pretty/hir-fn-variadic.pp index c0f5b7069a2d7..2d5766aeef463 100644 --- a/tests/pretty/hir-fn-variadic.pp +++ b/tests/pretty/hir-fn-variadic.pp @@ -1,12 +1,12 @@ -//@ pretty-compare-only -//@ pretty-mode:hir -//@ pp-exact:hir-fn-variadic.pp - -#![feature(c_variadic)] +#![attr = Feature([c_variadic#0])] #[attr = MacroUse {arguments: UseAll}] extern crate std; #[prelude_import] use ::std::prelude::rust_2015::*; +//@ pretty-compare-only +//@ pretty-mode:hir +//@ pp-exact:hir-fn-variadic.pp + extern "C" { unsafe fn foo(x: i32, va1: ...); diff --git a/tests/pretty/pin-ergonomics-hir.pp b/tests/pretty/pin-ergonomics-hir.pp index beca5988017dc..8ba1726947be6 100644 --- a/tests/pretty/pin-ergonomics-hir.pp +++ b/tests/pretty/pin-ergonomics-hir.pp @@ -2,8 +2,8 @@ //@ pretty-mode:hir //@ pp-exact:pin-ergonomics-hir.pp -#![feature(pin_ergonomics)] #![allow(dead_code, incomplete_features)] +#![attr = Feature([pin_ergonomics#0])] #[attr = MacroUse {arguments: UseAll}] extern crate std; #[prelude_import] diff --git a/tests/ui/empty/empty-attributes.stderr b/tests/ui/empty/empty-attributes.stderr index f0be56ddc6aa4..bfb0d2098aecb 100644 --- a/tests/ui/empty/empty-attributes.stderr +++ b/tests/ui/empty/empty-attributes.stderr @@ -43,14 +43,6 @@ LL | #![forbid()] | = note: attribute `forbid` with an empty list has no effect -error: unused attribute - --> $DIR/empty-attributes.rs:7:1 - | -LL | #![feature()] - | ^^^^^^^^^^^^^ help: remove this attribute - | - = note: attribute `feature` with an empty list has no effect - error: unused attribute --> $DIR/empty-attributes.rs:9:1 | @@ -63,5 +55,11 @@ error: unused attribute LL | #[target_feature()] | ^^^^^^^^^^^^^^^^^^^ help: remove this attribute +error: unused attribute + --> $DIR/empty-attributes.rs:7:1 + | +LL | #![feature()] + | ^^^^^^^^^^^^^ help: remove this attribute + error: aborting due to 8 previous errors diff --git a/tests/ui/feature-gates/feature-gate-feature-gate.rs b/tests/ui/feature-gates/feature-gate-feature-gate.rs index 3c98e16a136a8..b5b6a8da91bd7 100644 --- a/tests/ui/feature-gates/feature-gate-feature-gate.rs +++ b/tests/ui/feature-gates/feature-gate-feature-gate.rs @@ -1,4 +1,4 @@ #![forbid(unstable_features)] #![feature(intrinsics)] //~ ERROR unstable feature -fn main() { } +fn main() {} diff --git a/tests/ui/feature-gates/gated-bad-feature.stderr b/tests/ui/feature-gates/gated-bad-feature.stderr index e0e84d842352d..268d6511025ff 100644 --- a/tests/ui/feature-gates/gated-bad-feature.stderr +++ b/tests/ui/feature-gates/gated-bad-feature.stderr @@ -1,15 +1,3 @@ -error[E0556]: malformed `feature` attribute input - --> $DIR/gated-bad-feature.rs:1:25 - | -LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] - | ^^^^^^^^ help: expected just one word: `foo` - -error[E0556]: malformed `feature` attribute input - --> $DIR/gated-bad-feature.rs:1:35 - | -LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] - | ^^^^^^^^^^^ help: expected just one word: `foo` - error[E0557]: feature has been removed --> $DIR/gated-bad-feature.rs:8:12 | @@ -18,17 +6,41 @@ LL | #![feature(test_removed_feature)] | = note: removed in 1.0.0 -error: malformed `feature` attribute input +error[E0565]: malformed `feature` attribute input + --> $DIR/gated-bad-feature.rs:1:1 + | +LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^^ + | | | + | | didn't expect any arguments here + | help: must be of the form: `#![feature(feature1, feature2, ...)]` + +error[E0565]: malformed `feature` attribute input + --> $DIR/gated-bad-feature.rs:1:1 + | +LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^^^^^^ + | | | + | | didn't expect any arguments here + | help: must be of the form: `#![feature(feature1, feature2, ...)]` + +error[E0539]: malformed `feature` attribute input --> $DIR/gated-bad-feature.rs:6:1 | LL | #![feature] - | ^^^^^^^^^^^ help: must be of the form: `#![feature(name1, name2, ...)]` + | ^^^^^^^^^^^ + | | + | expected this to be a list + | help: must be of the form: `#![feature(feature1, feature2, ...)]` -error: malformed `feature` attribute input +error[E0539]: malformed `feature` attribute input --> $DIR/gated-bad-feature.rs:7:1 | LL | #![feature = "foo"] - | ^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#![feature(name1, name2, ...)]` + | ^^^^^^^^^^^^^^^^^^^ + | | + | expected this to be a list + | help: must be of the form: `#![feature(feature1, feature2, ...)]` error[E0635]: unknown feature `foo_bar_baz` --> $DIR/gated-bad-feature.rs:1:12 @@ -44,5 +56,5 @@ LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] error: aborting due to 7 previous errors -Some errors have detailed explanations: E0556, E0557, E0635. -For more information about an error, try `rustc --explain E0556`. +Some errors have detailed explanations: E0539, E0557, E0565, E0635. +For more information about an error, try `rustc --explain E0539`. diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.rs b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.rs index 546aa4052d333..87f4f4eba7bc7 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.rs +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.rs @@ -844,26 +844,26 @@ mod crate_type { #[feature(x0600)] //~^ WARN crate-level attribute should be an inner attribute -//~| HELP add a `!` mod feature { +//~^ NOTE This attribute does not have an `!`, which means it is applied to this module mod inner { #![feature(x0600)] } -//~^ WARN crate-level attribute should be in the root module + //~^ WARN the `#![feature]` attribute can only be used at the crate root #[feature(x0600)] fn f() { } //~^ WARN crate-level attribute should be an inner attribute - //~| HELP add a `!` + //~| NOTE This attribute does not have an `!`, which means it is applied to this function #[feature(x0600)] struct S; //~^ WARN crate-level attribute should be an inner attribute - //~| HELP add a `!` + //~| NOTE This attribute does not have an `!`, which means it is applied to this struct #[feature(x0600)] type T = S; //~^ WARN crate-level attribute should be an inner attribute - //~| HELP add a `!` + //~| NOTE This attribute does not have an `!`, which means it is applied to this type alias #[feature(x0600)] impl S { } //~^ WARN crate-level attribute should be an inner attribute - //~| HELP add a `!` + //~| NOTE This attribute does not have an `!`, which means it is applied to this implementation block } diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr index 3c835be5cffab..18d14a87c1c52 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr @@ -247,17 +247,6 @@ help: add a `!` LL | #![crate_type = "0800"] | + -warning: crate-level attribute should be an inner attribute - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:845:1 - | -LL | #[feature(x0600)] - | ^^^^^^^^^^^^^^^^^ - | -help: add a `!` - | -LL | #![feature(x0600)] - | + - warning: crate-level attribute should be an inner attribute --> $DIR/issue-43106-gating-of-builtin-attrs.rs:870:1 | @@ -524,56 +513,6 @@ help: add a `!` LL | #![crate_type = "0800"] impl S { } | + -warning: crate-level attribute should be in the root module - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:849:17 - | -LL | mod inner { #![feature(x0600)] } - | ^^^^^^^^^^^^^^^^^^ - -warning: crate-level attribute should be an inner attribute - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:852:5 - | -LL | #[feature(x0600)] fn f() { } - | ^^^^^^^^^^^^^^^^^ - | -help: add a `!` - | -LL | #![feature(x0600)] fn f() { } - | + - -warning: crate-level attribute should be an inner attribute - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:856:5 - | -LL | #[feature(x0600)] struct S; - | ^^^^^^^^^^^^^^^^^ - | -help: add a `!` - | -LL | #![feature(x0600)] struct S; - | + - -warning: crate-level attribute should be an inner attribute - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:860:5 - | -LL | #[feature(x0600)] type T = S; - | ^^^^^^^^^^^^^^^^^ - | -help: add a `!` - | -LL | #![feature(x0600)] type T = S; - | + - -warning: crate-level attribute should be an inner attribute - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:864:5 - | -LL | #[feature(x0600)] impl S { } - | ^^^^^^^^^^^^^^^^^ - | -help: add a `!` - | -LL | #![feature(x0600)] impl S { } - | + - warning: crate-level attribute should be in the root module --> $DIR/issue-43106-gating-of-builtin-attrs.rs:874:17 | @@ -1363,6 +1302,76 @@ note: This attribute does not have an `!`, which means it is applied to this imp LL | #[crate_name = "0900"] impl S { } | ^^^^^^^^^^ +warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![feature]` + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:845:1 + | +LL | #[feature(x0600)] + | ^^^^^^^^^^^^^^^^^ + | +note: This attribute does not have an `!`, which means it is applied to this module + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:847:1 + | +LL | / mod feature { +LL | | +LL | | mod inner { #![feature(x0600)] } +... | +LL | | } + | |_^ + +warning: the `#![feature]` attribute can only be used at the crate root + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:849:17 + | +LL | mod inner { #![feature(x0600)] } + | ^^^^^^^^^^^^^^^^^^ + +warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![feature]` + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:852:5 + | +LL | #[feature(x0600)] fn f() { } + | ^^^^^^^^^^^^^^^^^ + | +note: This attribute does not have an `!`, which means it is applied to this function + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:852:23 + | +LL | #[feature(x0600)] fn f() { } + | ^^^^^^^^^^ + +warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![feature]` + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:856:5 + | +LL | #[feature(x0600)] struct S; + | ^^^^^^^^^^^^^^^^^ + | +note: This attribute does not have an `!`, which means it is applied to this struct + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:856:23 + | +LL | #[feature(x0600)] struct S; + | ^^^^^^^^^ + +warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![feature]` + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:860:5 + | +LL | #[feature(x0600)] type T = S; + | ^^^^^^^^^^^^^^^^^ + | +note: This attribute does not have an `!`, which means it is applied to this type alias + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:860:23 + | +LL | #[feature(x0600)] type T = S; + | ^^^^^^^^^^^ + +warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![feature]` + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:864:5 + | +LL | #[feature(x0600)] impl S { } + | ^^^^^^^^^^^^^^^^^ + | +note: This attribute does not have an `!`, which means it is applied to this implementation block + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:864:23 + | +LL | #[feature(x0600)] impl S { } + | ^^^^^^^^^^ + warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![recursion_limit]` --> $DIR/issue-43106-gating-of-builtin-attrs.rs:918:1 | diff --git a/tests/ui/type-alias-impl-trait/issue-60662.stdout b/tests/ui/type-alias-impl-trait/issue-60662.stdout index 7ad29c88bcfe5..a93ddaadf7021 100644 --- a/tests/ui/type-alias-impl-trait/issue-60662.stdout +++ b/tests/ui/type-alias-impl-trait/issue-60662.stdout @@ -1,12 +1,12 @@ -//@ check-pass -//@ compile-flags: -Z unpretty=hir -//@ edition: 2015 - -#![feature(type_alias_impl_trait)] +#![attr = Feature([type_alias_impl_trait#0])] #[attr = MacroUse {arguments: UseAll}] extern crate std; #[prelude_import] use ::std::prelude::rust_2015::*; +//@ check-pass +//@ compile-flags: -Z unpretty=hir +//@ edition: 2015 + trait Animal { } diff --git a/tests/ui/unpretty/exhaustive.hir.stdout b/tests/ui/unpretty/exhaustive.hir.stdout index 96d85d1e7c14f..c6451279449be 100644 --- a/tests/ui/unpretty/exhaustive.hir.stdout +++ b/tests/ui/unpretty/exhaustive.hir.stdout @@ -8,27 +8,12 @@ // Note: the HIR revision includes a `.stderr` file because there are some // errors that only occur once we get past the AST. -#![feature(auto_traits)] -#![feature(box_patterns)] -#![feature(builtin_syntax)] -#![feature(const_trait_impl)] -#![feature(coroutines)] -#![feature(decl_macro)] -#![feature(deref_patterns)] -#![feature(explicit_tail_calls)] -#![feature(gen_blocks)] -#![feature(more_qualified_paths)] -#![feature(never_patterns)] -#![feature(never_type)] -#![feature(pattern_types)] -#![feature(pattern_type_macro)] -#![feature(prelude_import)] -#![feature(specialization)] -#![feature(trace_macros)] -#![feature(trait_alias)] -#![feature(try_blocks)] -#![feature(yeet_expr)] #![allow(incomplete_features)] +#![attr = Feature([auto_traits#0, box_patterns#0, builtin_syntax#0, +const_trait_impl#0, coroutines#0, decl_macro#0, deref_patterns#0, +explicit_tail_calls#0, gen_blocks#0, more_qualified_paths#0, never_patterns#0, +never_type#0, pattern_types#0, pattern_type_macro#0, prelude_import#0, +specialization#0, trace_macros#0, trait_alias#0, try_blocks#0, yeet_expr#0])] #[attr = MacroUse {arguments: UseAll}] extern crate std; #[prelude_import] From 548489e25040d65c82ce9e2a52b2619616304708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 16 Sep 2025 14:33:08 -0700 Subject: [PATCH 3/4] fix bug in proc macro derive diagnostics --- .../src/attributes/proc_macro_attrs.rs | 8 ++- compiler/rustc_attr_parsing/src/context.rs | 15 ++++ .../src/session_diagnostics.rs | 7 ++ tests/ui/proc-macro/attribute.rs | 71 ++++++++++++++----- tests/ui/proc-macro/attribute.stderr | 39 +++++----- 5 files changed, 102 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs index b9929d6f1f8ee..d980233cb1818 100644 --- a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs @@ -92,9 +92,15 @@ fn parse_derive_like( return None; }; + // updated if we see `attributes(...)` to keep track of the last + // argument we did accept for the final diagnostic + let mut last = trait_ident.span; + // Parse optional attributes let mut attributes = ThinVec::new(); if let Some(attrs) = items.next() { + last = attrs.span(); + let Some(attr_list) = attrs.meta_item() else { cx.expected_list(attrs.span()); return None; @@ -132,7 +138,7 @@ fn parse_derive_like( // If anything else is specified, we should reject it if let Some(next) = items.next() { - cx.expected_no_args(next.span()); + cx.expected_end_of_list(last, next.span()); } Some((Some(trait_ident.name), attributes)) diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 360bb443888a0..7b0332ef7e7bd 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -511,6 +511,21 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { }) } + /// Expected the end of an argument list. + /// + /// Note: only useful when arguments in an attribute are ordered and we've seen the last one we expected. + /// Most attributes shouldn't care about their argument order. + pub(crate) fn expected_end_of_list(&self, last_item_span: Span, span: Span) -> ErrorGuaranteed { + self.emit_err(AttributeParseError { + span, + attr_span: self.attr_span, + template: self.template.clone(), + attribute: self.attr_path.clone(), + reason: AttributeParseErrorReason::ExpectedEnd { last: last_item_span }, + attr_style: self.attr_style, + }) + } + pub(crate) fn expected_single_argument(&self, span: Span) -> ErrorGuaranteed { self.emit_err(AttributeParseError { span, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 30fb078ef4070..c4dacae891ff5 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -605,6 +605,9 @@ pub(crate) enum AttributeParseErrorReason<'a> { list: bool, }, ExpectedIdentifier, + ExpectedEnd { + last: Span, + }, } pub(crate) struct AttributeParseError<'a> { @@ -744,6 +747,10 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { AttributeParseErrorReason::ExpectedIdentifier => { diag.span_label(self.span, "expected a valid identifier here"); } + AttributeParseErrorReason::ExpectedEnd { last } => { + diag.span_label(last, "expected no more arguments after this"); + diag.span_label(self.span, "remove this argument"); + } } if let Some(link) = self.template.docs { diff --git a/tests/ui/proc-macro/attribute.rs b/tests/ui/proc-macro/attribute.rs index dfb267383775e..1fd1c919afed5 100644 --- a/tests/ui/proc-macro/attribute.rs +++ b/tests/ui/proc-macro/attribute.rs @@ -10,97 +10,132 @@ use proc_macro::*; //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected this to be a list //~| NOTE for more information, visit -pub fn foo1(input: TokenStream) -> TokenStream { input } +pub fn foo1(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive = ""] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected this to be a list //~| NOTE for more information, visit -pub fn foo2(input: TokenStream) -> TokenStream { input } +pub fn foo2(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d3, a, b)] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE the only valid argument here is `attributes` //~| NOTE for more information, visit -pub fn foo3(input: TokenStream) -> TokenStream { input } +pub fn foo3(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d4, attributes(a), b)] //~^ ERROR malformed `proc_macro_derive` attribute -//~| NOTE didn't expect any arguments here +//~| NOTE expected no more arguments after this +//~| NOTE remove this argument //~| NOTE for more information, visit -pub fn foo4(input: TokenStream) -> TokenStream { input } +pub fn foo4(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive("a")] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE didn't expect a literal here //~| NOTE for more information, visit -pub fn foo5(input: TokenStream) -> TokenStream { input } +pub fn foo5(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d6 = "")] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE didn't expect any arguments here //~| NOTE for more information, visit -pub fn foo6(input: TokenStream) -> TokenStream { input } +pub fn foo6(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(m::d7)] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected a valid identifier here //~| NOTE for more information, visit -pub fn foo7(input: TokenStream) -> TokenStream { input } +pub fn foo7(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d8(a))] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE didn't expect any arguments here //~| NOTE for more information, visit -pub fn foo8(input: TokenStream) -> TokenStream { input } +pub fn foo8(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(self)] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected a valid identifier here //~| NOTE for more information, visit -pub fn foo9(input: TokenStream) -> TokenStream { input } +pub fn foo9(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(PartialEq)] // OK -pub fn foo10(input: TokenStream) -> TokenStream { input } +pub fn foo10(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d11, a)] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE the only valid argument here is `attributes` //~| NOTE for more information, visit -pub fn foo11(input: TokenStream) -> TokenStream { input } +pub fn foo11(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d12, attributes)] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected this to be a list //~| NOTE for more information, visit -pub fn foo12(input: TokenStream) -> TokenStream { input } +pub fn foo12(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d13, attributes("a"))] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected a valid identifier here //~| NOTE for more information, visit -pub fn foo13(input: TokenStream) -> TokenStream { input } +pub fn foo13(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d14, attributes(a = ""))] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE didn't expect any arguments here //~| NOTE for more information, visit -pub fn foo14(input: TokenStream) -> TokenStream { input } +pub fn foo14(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d15, attributes(m::a))] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected a valid identifier here //~| NOTE for more information, visit -pub fn foo15(input: TokenStream) -> TokenStream { input } +pub fn foo15(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d16, attributes(a(b)))] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE didn't expect any arguments here //~| NOTE for more information, visit -pub fn foo16(input: TokenStream) -> TokenStream { input } +pub fn foo16(input: TokenStream) -> TokenStream { + input +} #[proc_macro_derive(d17, attributes(self))] //~^ ERROR malformed `proc_macro_derive` attribute //~| NOTE expected a valid identifier here //~| NOTE for more information, visit -pub fn foo17(input: TokenStream) -> TokenStream { input } +pub fn foo17(input: TokenStream) -> TokenStream { + input +} diff --git a/tests/ui/proc-macro/attribute.stderr b/tests/ui/proc-macro/attribute.stderr index e7127c8ef1d2e..4ff0794287581 100644 --- a/tests/ui/proc-macro/attribute.stderr +++ b/tests/ui/proc-macro/attribute.stderr @@ -13,7 +13,7 @@ LL | #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | ++++++++++++++++++++++++++++++++++++++++++ error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:15:1 + --> $DIR/attribute.rs:17:1 | LL | #[proc_macro_derive = ""] | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected this to be a list @@ -29,7 +29,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:21:1 + --> $DIR/attribute.rs:25:1 | LL | #[proc_macro_derive(d3, a, b)] | ^^^^^^^^^^^^^^^^^^^^^^^^-^^^^^ @@ -46,13 +46,14 @@ LL - #[proc_macro_derive(d3, a, b)] LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | -error[E0565]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:27:1 +error[E0539]: malformed `proc_macro_derive` attribute input + --> $DIR/attribute.rs:33:1 | LL | #[proc_macro_derive(d4, attributes(a), b)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^ - | | - | didn't expect any arguments here + | ^^^^^^^^^^^^^^^^^^^^^^^^-------------^^-^^ + | | | + | | remove this argument + | expected no more arguments after this | = note: for more information, visit help: try changing it to one of the following valid forms of the attribute @@ -65,7 +66,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0565]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:33:1 + --> $DIR/attribute.rs:42:1 | LL | #[proc_macro_derive("a")] | ^^^^^^^^^^^^^^^^^^^^---^^ @@ -83,7 +84,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0565]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:39:1 + --> $DIR/attribute.rs:50:1 | LL | #[proc_macro_derive(d6 = "")] | ^^^^^^^^^^^^^^^^^^^^^^^----^^ @@ -101,7 +102,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:45:1 + --> $DIR/attribute.rs:58:1 | LL | #[proc_macro_derive(m::d7)] | ^^^^^^^^^^^^^^^^^^^^-----^^ @@ -119,7 +120,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0565]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:51:1 + --> $DIR/attribute.rs:66:1 | LL | #[proc_macro_derive(d8(a))] | ^^^^^^^^^^^^^^^^^^^^^^---^^ @@ -137,7 +138,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:57:1 + --> $DIR/attribute.rs:74:1 | LL | #[proc_macro_derive(self)] | ^^^^^^^^^^^^^^^^^^^^----^^ @@ -155,7 +156,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:66:1 + --> $DIR/attribute.rs:87:1 | LL | #[proc_macro_derive(d11, a)] | ^^^^^^^^^^^^^^^^^^^^^^^^^-^^ @@ -173,7 +174,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:72:1 + --> $DIR/attribute.rs:95:1 | LL | #[proc_macro_derive(d12, attributes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^----------^^ @@ -191,7 +192,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:78:1 + --> $DIR/attribute.rs:103:1 | LL | #[proc_macro_derive(d13, attributes("a"))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---^^^ @@ -209,7 +210,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0565]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:84:1 + --> $DIR/attribute.rs:111:1 | LL | #[proc_macro_derive(d14, attributes(a = ""))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----^^^ @@ -227,7 +228,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:90:1 + --> $DIR/attribute.rs:119:1 | LL | #[proc_macro_derive(d15, attributes(m::a))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----^^^ @@ -245,7 +246,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0565]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:96:1 + --> $DIR/attribute.rs:127:1 | LL | #[proc_macro_derive(d16, attributes(a(b)))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---^^^ @@ -263,7 +264,7 @@ LL + #[proc_macro_derive(TraitName, attributes(name1, name2, ...))] | error[E0539]: malformed `proc_macro_derive` attribute input - --> $DIR/attribute.rs:102:1 + --> $DIR/attribute.rs:135:1 | LL | #[proc_macro_derive(d17, attributes(self))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----^^^ From 399254aa2af87cc209ac39c06cdb6ae235978c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 16 Sep 2025 12:52:25 -0700 Subject: [PATCH 4/4] better diagnostics for arguments --- .../src/attributes/codegen_attrs.rs | 2 +- .../src/attributes/crate_level.rs | 2 +- .../src/attributes/macro_attrs.rs | 5 +- .../rustc_attr_parsing/src/attributes/mod.rs | 2 +- .../src/attributes/proc_macro_attrs.rs | 4 +- .../src/attributes/rustc_internal.rs | 2 +- .../src/attributes/traits.rs | 2 +- compiler/rustc_attr_parsing/src/context.rs | 4 +- .../src/session_diagnostics.rs | 62 +++++++++++++++---- tests/ui/attributes/invalid-macro-use.stderr | 14 ++++- .../rustc_skip_during_method_dispatch.stderr | 16 ++++- .../unsafe/proc-unsafe-attributes.stderr | 7 ++- .../cfg-attr-syntax-validation.rs | 5 +- .../cfg-attr-syntax-validation.stderr | 3 +- .../ui/feature-gates/gated-bad-feature.stderr | 32 ++++++++-- tests/ui/macros/macro-use-bad-args-1.stderr | 7 ++- tests/ui/macros/macro-use-bad-args-2.stderr | 7 ++- tests/ui/proc-macro/attribute.stderr | 28 +++++++-- 18 files changed, 160 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 262b8213977b3..95fb8f18e61c8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -226,7 +226,7 @@ impl AttributeParser for NakedParser { const ATTRIBUTES: AcceptMapping = &[(&[sym::naked], template!(Word), |this, cx, args| { if let Err(span) = args.no_args() { - cx.expected_no_args(span); + cx.expected_no_args(&cx.attr_path, span); return; } diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 8b7801c5279f4..0c00f3b08027c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -214,7 +214,7 @@ impl CombineAttributeParser for FeatureParser { continue; }; if let Err(arg_span) = elem.args().no_args() { - cx.expected_no_args(arg_span); + cx.expected_no_args(&elem.path().get_attribute_path(), arg_span); continue; } diff --git a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs index 180130c7be4fc..c78162ce96e25 100644 --- a/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs @@ -88,7 +88,10 @@ impl AttributeParser for MacroUseParser { continue; }; if let Err(err_span) = item.args().no_args() { - cx.expected_no_args(err_span); + cx.expected_no_args( + &item.path().get_attribute_path(), + err_span, + ); continue; } let Some(item) = item.path().word() else { diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 4ed13d239b9d6..c5c680320ba23 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -280,7 +280,7 @@ impl, S: Stage> SingleAttributeParser for Without fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { if let Err(span) = args.no_args() { - cx.expected_no_args(span); + cx.expected_no_args(&cx.attr_path, span); } Some(T::CREATE(cx.attr_span)) } diff --git a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs index d980233cb1818..560a7030310c2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/proc_macro_attrs.rs @@ -88,7 +88,7 @@ fn parse_derive_like( return None; } if let Err(e) = trait_attr.args().no_args() { - cx.expected_no_args(e); + cx.expected_no_args(&trait_attr.path().get_attribute_path(), e); return None; }; @@ -121,7 +121,7 @@ fn parse_derive_like( return None; }; if let Err(e) = attr.args().no_args() { - cx.expected_no_args(e); + cx.expected_no_args(&attr.path().get_attribute_path(), e); return None; }; let Some(ident) = attr.path().word() else { diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index a995549fc7c83..5e895d1e1a51c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -42,7 +42,7 @@ impl SingleAttributeParser for RustcObjectLifetimeDefaultParser { fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { if let Err(span) = args.no_args() { - cx.expected_no_args(span); + cx.expected_no_args(&cx.attr_path, span); return None; } diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index ced3bcad2293a..f7168582a4397 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -35,7 +35,7 @@ impl SingleAttributeParser for SkipDuringMethodDispatchParser { continue; }; if let Err(span) = arg.args().no_args() { - cx.expected_no_args(span); + cx.expected_no_args(&arg.path().get_attribute_path(), span); } let path = arg.path(); let (key, skip): (Symbol, &mut bool) = match path.word_sym() { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 7b0332ef7e7bd..e41b1461303b5 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -450,13 +450,13 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { }) } - pub(crate) fn expected_no_args(&self, args_span: Span) -> ErrorGuaranteed { + pub(crate) fn expected_no_args(&self, path: &AttrPath, args_span: Span) -> ErrorGuaranteed { self.emit_err(AttributeParseError { span: args_span, attr_span: self.attr_span, template: self.template.clone(), attribute: self.attr_path.clone(), - reason: AttributeParseErrorReason::ExpectedNoArgs, + reason: AttributeParseErrorReason::ExpectedNoArgs { path }, attr_style: self.attr_style, }) } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index c4dacae891ff5..2aff0d46146e9 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -587,7 +587,9 @@ pub(crate) struct LinkOrdinalOutOfRange { } pub(crate) enum AttributeParseErrorReason<'a> { - ExpectedNoArgs, + ExpectedNoArgs { + path: &'a AttrPath, + }, ExpectedStringLiteral { byte_string: Option, }, @@ -619,6 +621,18 @@ pub(crate) struct AttributeParseError<'a> { pub(crate) reason: AttributeParseErrorReason<'a>, } +/// based on the attribute's template we add relevant suggestions to the error automatically. +enum DefaultSuggestionStyle { + /// give a hint about the valid forms of the attribute. + /// Useful if there's already a better suggestion given than the automatic ones can provide + /// but we'd still like to show which syntax forms are valid. + Hint, + /// Use the template to suggest changes to the attribute + Suggestion, + /// Don't show any default suggestions + None, +} + impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { let name = self.attribute.to_string(); @@ -626,6 +640,9 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { let mut diag = Diag::new(dcx, level, format!("malformed `{name}` attribute input")); diag.span(self.attr_span); diag.code(E0539); + + let mut show_default_suggestions = DefaultSuggestionStyle::Suggestion; + match self.reason { AttributeParseErrorReason::ExpectedStringLiteral { byte_string } => { if let Some(start_point_span) = byte_string { @@ -637,7 +654,7 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { ); diag.note("expected a normal string literal, not a byte string literal"); - return diag; + show_default_suggestions = DefaultSuggestionStyle::None; } else { diag.span_label(self.span, "expected a string literal here"); } @@ -663,9 +680,19 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { diag.span_label(self.span, "didn't expect a literal here"); diag.code(E0565); } - AttributeParseErrorReason::ExpectedNoArgs => { + AttributeParseErrorReason::ExpectedNoArgs { path } => { diag.span_label(self.span, "didn't expect any arguments here"); diag.code(E0565); + + if path.span != self.attribute.span { + diag.span_suggestion( + path.span.to(self.span), + "remove this argument", + path, + Applicability::MachineApplicable, + ); + show_default_suggestions = DefaultSuggestionStyle::Hint; + } } AttributeParseErrorReason::ExpectedNameValue(None) => { // If the span is the entire attribute, the suggestion we add below this match already contains enough information @@ -756,18 +783,27 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { if let Some(link) = self.template.docs { diag.note(format!("for more information, visit <{link}>")); } + let suggestions = self.template.suggestions(self.attr_style, &name); + let text = match show_default_suggestions { + DefaultSuggestionStyle::Hint => { + if suggestions.len() == 1 { + "the only valid form of the attribute is" + } else { + "these are the valid forms of the attribute" + } + } + DefaultSuggestionStyle::Suggestion => { + if suggestions.len() == 1 { + "must be of the form" + } else { + "try changing it to one of the following valid forms of the attribute" + } + } + DefaultSuggestionStyle::None => return diag, + }; - diag.span_suggestions( - self.attr_span, - if suggestions.len() == 1 { - "must be of the form" - } else { - "try changing it to one of the following valid forms of the attribute" - }, - suggestions, - Applicability::HasPlaceholders, - ); + diag.span_suggestions(self.attr_span, text, suggestions, Applicability::HasPlaceholders); diag } diff --git a/tests/ui/attributes/invalid-macro-use.stderr b/tests/ui/attributes/invalid-macro-use.stderr index ff3ed6196d3d3..90afabdc1cf36 100644 --- a/tests/ui/attributes/invalid-macro-use.stderr +++ b/tests/ui/attributes/invalid-macro-use.stderr @@ -43,7 +43,12 @@ LL | #[macro_use(a = "b")] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[macro_use(a = "b")] +LL + #[macro_use(a)] + | +help: these are the valid forms of the attribute | LL - #[macro_use(a = "b")] LL + #[macro_use(name1, name2, ...)] @@ -61,7 +66,12 @@ LL | #[macro_use(a(b))] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[macro_use(a(b))] +LL + #[macro_use(a)] + | +help: these are the valid forms of the attribute | LL - #[macro_use(a(b))] LL + #[macro_use(name1, name2, ...)] diff --git a/tests/ui/attributes/rustc_skip_during_method_dispatch.stderr b/tests/ui/attributes/rustc_skip_during_method_dispatch.stderr index 094987e944fdf..1cef0c0121c3a 100644 --- a/tests/ui/attributes/rustc_skip_during_method_dispatch.stderr +++ b/tests/ui/attributes/rustc_skip_during_method_dispatch.stderr @@ -48,9 +48,19 @@ error[E0565]: malformed `rustc_skip_during_method_dispatch` attribute input | LL | #[rustc_skip_during_method_dispatch(array = true)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------^^ - | | | - | | didn't expect any arguments here - | help: must be of the form: `#[rustc_skip_during_method_dispatch(array, boxed_slice)]` + | | + | didn't expect any arguments here + | +help: remove this argument + | +LL - #[rustc_skip_during_method_dispatch(array = true)] +LL + #[rustc_skip_during_method_dispatch(array)] + | +help: the only valid form of the attribute is + | +LL - #[rustc_skip_during_method_dispatch(array = true)] +LL + #[rustc_skip_during_method_dispatch(array, boxed_slice)] + | error[E0565]: malformed `rustc_skip_during_method_dispatch` attribute input --> $DIR/rustc_skip_during_method_dispatch.rs:27:1 diff --git a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr index 94edb263a6afe..e186601fb7502 100644 --- a/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr +++ b/tests/ui/attributes/unsafe/proc-unsafe-attributes.stderr @@ -123,7 +123,12 @@ LL | #[proc_macro_derive(unsafe(Foo))] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[proc_macro_derive(unsafe(Foo))] +LL + #[proc_macro_derive(r#unsafe)] + | +help: these are the valid forms of the attribute | LL - #[proc_macro_derive(unsafe(Foo))] LL + #[proc_macro_derive(TraitName)] diff --git a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs index 2c84a966f904d..32796fc38686d 100644 --- a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs +++ b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.rs @@ -36,8 +36,9 @@ struct S7; //~| NOTE for more information, visit struct S8; -#[cfg(a = b"hi")] //~ ERROR malformed `cfg` attribute input +#[cfg(a = b"hi")] //~ ERROR malformed `cfg` attribute input //~^ NOTE expected a normal string literal, not a byte string literal +//~| NOTE: for more information, visit struct S9; macro_rules! generate_s10 { @@ -45,7 +46,7 @@ macro_rules! generate_s10 { #[cfg(feature = $expr)] //~^ ERROR expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `expr` metavariable struct S10; - } + }; } generate_s10!(concat!("nonexistent")); diff --git a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr index 59ff611e0661e..3e8cd1bc016b3 100644 --- a/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr +++ b/tests/ui/conditional-compilation/cfg-attr-syntax-validation.stderr @@ -80,9 +80,10 @@ LL | #[cfg(a = b"hi")] | help: consider removing the prefix | = note: expected a normal string literal, not a byte string literal + = note: for more information, visit error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `expr` metavariable - --> $DIR/cfg-attr-syntax-validation.rs:45:25 + --> $DIR/cfg-attr-syntax-validation.rs:46:25 | LL | #[cfg(feature = $expr)] | ^^^^^ diff --git a/tests/ui/feature-gates/gated-bad-feature.stderr b/tests/ui/feature-gates/gated-bad-feature.stderr index 268d6511025ff..f1b21e15ff5d3 100644 --- a/tests/ui/feature-gates/gated-bad-feature.stderr +++ b/tests/ui/feature-gates/gated-bad-feature.stderr @@ -11,18 +11,38 @@ error[E0565]: malformed `feature` attribute input | LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^^ - | | | - | | didn't expect any arguments here - | help: must be of the form: `#![feature(feature1, feature2, ...)]` + | | + | didn't expect any arguments here + | +help: remove this argument + | +LL - #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] +LL + #![feature(foo_bar_baz, foo, foo = "baz", foo)] + | +help: the only valid form of the attribute is + | +LL - #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] +LL + #![feature(feature1, feature2, ...)] + | error[E0565]: malformed `feature` attribute input --> $DIR/gated-bad-feature.rs:1:1 | LL | #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^^^^^^ - | | | - | | didn't expect any arguments here - | help: must be of the form: `#![feature(feature1, feature2, ...)]` + | | + | didn't expect any arguments here + | +help: remove this argument + | +LL - #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] +LL + #![feature(foo_bar_baz, foo(bar), foo, foo)] + | +help: the only valid form of the attribute is + | +LL - #![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] +LL + #![feature(feature1, feature2, ...)] + | error[E0539]: malformed `feature` attribute input --> $DIR/gated-bad-feature.rs:6:1 diff --git a/tests/ui/macros/macro-use-bad-args-1.stderr b/tests/ui/macros/macro-use-bad-args-1.stderr index 542b4ae2b7a5e..0ed2e7c8090f3 100644 --- a/tests/ui/macros/macro-use-bad-args-1.stderr +++ b/tests/ui/macros/macro-use-bad-args-1.stderr @@ -7,7 +7,12 @@ LL | #[macro_use(foo(bar))] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[macro_use(foo(bar))] +LL + #[macro_use(foo)] + | +help: these are the valid forms of the attribute | LL - #[macro_use(foo(bar))] LL + #[macro_use(name1, name2, ...)] diff --git a/tests/ui/macros/macro-use-bad-args-2.stderr b/tests/ui/macros/macro-use-bad-args-2.stderr index 2db9ffe50b042..8a7c713afaa8d 100644 --- a/tests/ui/macros/macro-use-bad-args-2.stderr +++ b/tests/ui/macros/macro-use-bad-args-2.stderr @@ -7,7 +7,12 @@ LL | #[macro_use(foo="bar")] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[macro_use(foo="bar")] +LL + #[macro_use(foo)] + | +help: these are the valid forms of the attribute | LL - #[macro_use(foo="bar")] LL + #[macro_use(name1, name2, ...)] diff --git a/tests/ui/proc-macro/attribute.stderr b/tests/ui/proc-macro/attribute.stderr index 4ff0794287581..adcabca0e05b0 100644 --- a/tests/ui/proc-macro/attribute.stderr +++ b/tests/ui/proc-macro/attribute.stderr @@ -92,7 +92,12 @@ LL | #[proc_macro_derive(d6 = "")] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[proc_macro_derive(d6 = "")] +LL + #[proc_macro_derive(d6)] + | +help: these are the valid forms of the attribute | LL - #[proc_macro_derive(d6 = "")] LL + #[proc_macro_derive(TraitName)] @@ -128,7 +133,12 @@ LL | #[proc_macro_derive(d8(a))] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[proc_macro_derive(d8(a))] +LL + #[proc_macro_derive(d8)] + | +help: these are the valid forms of the attribute | LL - #[proc_macro_derive(d8(a))] LL + #[proc_macro_derive(TraitName)] @@ -218,7 +228,12 @@ LL | #[proc_macro_derive(d14, attributes(a = ""))] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[proc_macro_derive(d14, attributes(a = ""))] +LL + #[proc_macro_derive(d14, attributes(a))] + | +help: these are the valid forms of the attribute | LL - #[proc_macro_derive(d14, attributes(a = ""))] LL + #[proc_macro_derive(TraitName)] @@ -254,7 +269,12 @@ LL | #[proc_macro_derive(d16, attributes(a(b)))] | didn't expect any arguments here | = note: for more information, visit -help: try changing it to one of the following valid forms of the attribute +help: remove this argument + | +LL - #[proc_macro_derive(d16, attributes(a(b)))] +LL + #[proc_macro_derive(d16, attributes(a))] + | +help: these are the valid forms of the attribute | LL - #[proc_macro_derive(d16, attributes(a(b)))] LL + #[proc_macro_derive(TraitName)]