about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_attr_data_structures/src/attributes.rs2
-rw-r--r--compiler/rustc_attr_data_structures/src/lints.rs1
-rw-r--r--compiler/rustc_attr_parsing/messages.ftl5
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/confusables.rs63
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/deprecation.rs6
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/inline.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs25
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/repr.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs70
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/transparency.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs96
-rw-r--r--compiler/rustc_attr_parsing/src/lints.rs15
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs83
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs16
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs40
-rw-r--r--compiler/rustc_lint/src/lints.rs1
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs10
-rw-r--r--compiler/rustc_passes/src/check_attr.rs14
19 files changed, 352 insertions, 107 deletions
diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs
index 44fe7d58240..be7657bac58 100644
--- a/compiler/rustc_attr_data_structures/src/attributes.rs
+++ b/compiler/rustc_attr_data_structures/src/attributes.rs
@@ -235,8 +235,6 @@ pub enum AttributeKind {
 
     /// Represents [`#[repr]`](https://doc.rust-lang.org/stable/reference/type-layout.html#representations).
     Repr(ThinVec<(ReprAttr, Span)>),
-    /// Represents `#[rustc_force_inline]`
-    RustcForceInline(Span, Option<Symbol>),
     /// Represents `#[stable]`, `#[unstable]` and `#[rustc_allowed_through_unstable_modules]`.
     Stability {
         stability: Stability,
diff --git a/compiler/rustc_attr_data_structures/src/lints.rs b/compiler/rustc_attr_data_structures/src/lints.rs
index 7e3664b2263..e34c54c6d32 100644
--- a/compiler/rustc_attr_data_structures/src/lints.rs
+++ b/compiler/rustc_attr_data_structures/src/lints.rs
@@ -11,4 +11,5 @@ pub struct AttributeLint<Id> {
 #[derive(Clone, Debug, HashStable_Generic)]
 pub enum AttributeLintKind {
     UnusedDuplicate { this: Span, other: Span, warning: bool },
+    IllFormedAttributeInput { suggestions: Vec<String> },
 }
diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl
index c9443feb021..2687bfdc716 100644
--- a/compiler/rustc_attr_parsing/messages.ftl
+++ b/compiler/rustc_attr_parsing/messages.ftl
@@ -23,6 +23,10 @@ attr_parsing_expects_feature_list =
 attr_parsing_expects_features =
     `{$name}` expects feature names
 
+attr_parsing_ill_formed_attribute_input = {$num_suggestions ->
+        [1] attribute must be of the form {$suggestions}
+        *[other] valid forms for the attribute are {$suggestions}
+    }
 attr_parsing_incorrect_meta_item = expected a quoted string literal
 attr_parsing_incorrect_meta_item_suggestion = consider surrounding this with quotes
 
@@ -136,6 +140,7 @@ attr_parsing_unused_duplicate =
     .suggestion = remove this attribute
     .note = attribute also specified here
     .warn = {-passes_previously_accepted}
+
 attr_parsing_unused_multiple =
     multiple `{$name}` attributes
     .suggestion = remove this attribute
diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
index 81192f902a2..21b01a8d071 100644
--- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs
@@ -1,6 +1,7 @@
 use std::iter;
 
 use rustc_attr_data_structures::AttributeKind;
+use rustc_feature::{AttributeTemplate, template};
 use rustc_span::{Span, Symbol, sym};
 
 use super::{CombineAttributeParser, ConvertFn};
@@ -13,6 +14,7 @@ impl<S: Stage> CombineAttributeParser<S> for AllowInternalUnstableParser {
     const PATH: &[Symbol] = &[sym::allow_internal_unstable];
     type Item = (Symbol, Span);
     const CONVERT: ConvertFn<Self::Item> = AttributeKind::AllowInternalUnstable;
+    const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ...");
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
@@ -29,6 +31,7 @@ impl<S: Stage> CombineAttributeParser<S> for AllowConstFnUnstableParser {
     const PATH: &[Symbol] = &[sym::rustc_allow_const_fn_unstable];
     type Item = Symbol;
     const CONVERT: ConvertFn<Self::Item> = AttributeKind::AllowConstFnUnstable;
+    const TEMPLATE: AttributeTemplate = template!(Word, List: "feat1, feat2, ...");
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
diff --git a/compiler/rustc_attr_parsing/src/attributes/confusables.rs b/compiler/rustc_attr_parsing/src/attributes/confusables.rs
index afd3c012f05..0be9d690c32 100644
--- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs
@@ -1,4 +1,5 @@
 use rustc_attr_data_structures::AttributeKind;
+use rustc_feature::template;
 use rustc_span::{Span, Symbol, sym};
 use thin_vec::ThinVec;
 
@@ -13,37 +14,41 @@ pub(crate) struct ConfusablesParser {
 }
 
 impl<S: Stage> AttributeParser<S> for ConfusablesParser {
-    const ATTRIBUTES: AcceptMapping<Self, S> = &[(&[sym::rustc_confusables], |this, cx, args| {
-        let Some(list) = args.list() else {
-            // FIXME(jdonszelmann): error when not a list? Bring validation code here.
-            //       NOTE: currently subsequent attributes are silently ignored using
-            //       tcx.get_attr().
-            return;
-        };
-
-        if list.is_empty() {
-            cx.emit_err(session_diagnostics::EmptyConfusables { span: cx.attr_span });
-        }
-
-        for param in list.mixed() {
-            let span = param.span();
-
-            let Some(lit) = param.lit() else {
-                cx.emit_err(session_diagnostics::IncorrectMetaItem {
-                    span,
-                    suggestion: Some(session_diagnostics::IncorrectMetaItemSuggestion {
-                        lo: span.shrink_to_lo(),
-                        hi: span.shrink_to_hi(),
-                    }),
-                });
-                continue;
+    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
+        &[sym::rustc_confusables],
+        template!(List: r#""name1", "name2", ..."#),
+        |this, cx, args| {
+            let Some(list) = args.list() else {
+                // FIXME(jdonszelmann): error when not a list? Bring validation code here.
+                //       NOTE: currently subsequent attributes are silently ignored using
+                //       tcx.get_attr().
+                return;
             };
 
-            this.confusables.push(lit.symbol);
-        }
-
-        this.first_span.get_or_insert(cx.attr_span);
-    })];
+            if list.is_empty() {
+                cx.emit_err(session_diagnostics::EmptyConfusables { span: cx.attr_span });
+            }
+
+            for param in list.mixed() {
+                let span = param.span();
+
+                let Some(lit) = param.lit() else {
+                    cx.emit_err(session_diagnostics::IncorrectMetaItem {
+                        span,
+                        suggestion: Some(session_diagnostics::IncorrectMetaItemSuggestion {
+                            lo: span.shrink_to_lo(),
+                            hi: span.shrink_to_hi(),
+                        }),
+                    });
+                    continue;
+                };
+
+                this.confusables.push(lit.symbol);
+            }
+
+            this.first_span.get_or_insert(cx.attr_span);
+        },
+    )];
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         if self.confusables.is_empty() {
diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
index 1faee41c2a9..692f587ba03 100644
--- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs
@@ -1,4 +1,5 @@
 use rustc_attr_data_structures::{AttributeKind, DeprecatedSince, Deprecation};
+use rustc_feature::{AttributeTemplate, template};
 use rustc_span::{Span, Symbol, sym};
 
 use super::util::parse_version;
@@ -45,6 +46,11 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
     const PATH: &[Symbol] = &[sym::deprecated];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const TEMPLATE: AttributeTemplate = template!(
+        Word,
+        List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#,
+        NameValueStr: "reason"
+    );
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let features = cx.features();
diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs
index fecddde930a..da56656e3ed 100644
--- a/compiler/rustc_attr_parsing/src/attributes/inline.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs
@@ -11,7 +11,6 @@ use super::{AcceptContext, AttributeOrder, OnDuplicate};
 use crate::attributes::SingleAttributeParser;
 use crate::context::Stage;
 use crate::parser::ArgParser;
-use crate::session_diagnostics::IncorrectMetaItem;
 
 pub(crate) struct InlineParser;
 
@@ -30,7 +29,7 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
                     return None;
                 };
 
-                match l.meta_item().and_then(|i| i.word_without_args().map(|i| i.name)) {
+                match l.meta_item().and_then(|i| i.path().word().map(|i| i.name)) {
                     Some(sym::always) => {
                         Some(AttributeKind::Inline(InlineAttr::Always, cx.attr_span))
                     }
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index 0b9e9adcfbc..fa2a6087506 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -18,6 +18,7 @@ use std::marker::PhantomData;
 
 use rustc_attr_data_structures::AttributeKind;
 use rustc_attr_data_structures::lints::AttributeLintKind;
+use rustc_feature::AttributeTemplate;
 use rustc_span::{Span, Symbol};
 use thin_vec::ThinVec;
 
@@ -37,7 +38,7 @@ pub(crate) mod transparency;
 pub(crate) mod util;
 
 type AcceptFn<T, S> = for<'sess> fn(&mut T, &mut AcceptContext<'_, 'sess, S>, &ArgParser<'_>);
-type AcceptMapping<T, S> = &'static [(&'static [Symbol], AcceptFn<T, S>)];
+type AcceptMapping<T, S> = &'static [(&'static [Symbol], AttributeTemplate, AcceptFn<T, S>)];
 
 /// An [`AttributeParser`] is a type which searches for syntactic attributes.
 ///
@@ -89,6 +90,9 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
     const ATTRIBUTE_ORDER: AttributeOrder;
     const ON_DUPLICATE: OnDuplicate<S>;
 
+    /// The template this attribute parser should implement. Used for diagnostics.
+    const TEMPLATE: AttributeTemplate;
+
     /// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`]
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind>;
 }
@@ -105,8 +109,10 @@ impl<T: SingleAttributeParser<S>, S: Stage> Default for Single<T, S> {
 }
 
 impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S> {
-    const ATTRIBUTES: AcceptMapping<Self, S> =
-        &[(T::PATH, |group: &mut Single<T, S>, cx, args| {
+    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
+        T::PATH,
+        <T as SingleAttributeParser<S>>::TEMPLATE,
+        |group: &mut Single<T, S>, cx, args| {
             if let Some(pa) = T::convert(cx, args) {
                 match T::ATTRIBUTE_ORDER {
                     // keep the first and report immediately. ignore this attribute
@@ -127,7 +133,8 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
 
                 group.1 = Some((pa, cx.attr_span));
             }
-        })];
+        },
+    )];
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         Some(self.1?.0)
@@ -224,6 +231,9 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
     type Item;
     const CONVERT: ConvertFn<Self::Item>;
 
+    /// The template this attribute parser should implement. Used for diagnostics.
+    const TEMPLATE: AttributeTemplate;
+
     /// Converts a single syntactical attribute to a number of elements of the semantic attribute, or [`AttributeKind`]
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
@@ -243,8 +253,11 @@ impl<T: CombineAttributeParser<S>, S: Stage> Default for Combine<T, S> {
 }
 
 impl<T: CombineAttributeParser<S>, S: Stage> AttributeParser<S> for Combine<T, S> {
-    const ATTRIBUTES: AcceptMapping<Self, S> =
-        &[(T::PATH, |group: &mut Combine<T, S>, cx, args| group.1.extend(T::extend(cx, args)))];
+    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
+        T::PATH,
+        <T as CombineAttributeParser<S>>::TEMPLATE,
+        |group: &mut Combine<T, S>, cx, args| group.1.extend(T::extend(cx, args)),
+    )];
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         if self.1.is_empty() { None } else { Some(T::CONVERT(self.1)) }
diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs
index 753b2366b41..6fb618541e3 100644
--- a/compiler/rustc_attr_parsing/src/attributes/repr.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs
@@ -1,6 +1,7 @@
 use rustc_abi::Align;
 use rustc_ast::{IntTy, LitIntType, LitKind, UintTy};
 use rustc_attr_data_structures::{AttributeKind, IntType, ReprAttr};
+use rustc_feature::{AttributeTemplate, template};
 use rustc_span::{DUMMY_SP, Span, Symbol, sym};
 
 use super::{CombineAttributeParser, ConvertFn};
@@ -23,6 +24,8 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
     type Item = (ReprAttr, Span);
     const PATH: &[Symbol] = &[sym::repr];
     const CONVERT: ConvertFn<Self::Item> = AttributeKind::Repr;
+    // FIXME(jdonszelmann): never used
+    const TEMPLATE: AttributeTemplate = template!(List: "C");
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 6589a51db2b..fba725ae164 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -5,6 +5,7 @@ use rustc_attr_data_structures::{
     StableSince, UnstableReason, VERSION_PLACEHOLDER,
 };
 use rustc_errors::ErrorGuaranteed;
+use rustc_feature::{AttributeTemplate, template};
 use rustc_span::{Span, Symbol, sym};
 
 use super::util::parse_version;
@@ -43,26 +44,39 @@ impl StabilityParser {
 
 impl<S: Stage> AttributeParser<S> for StabilityParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[
-        (&[sym::stable], |this, cx, args| {
-            reject_outside_std!(cx);
-            if !this.check_duplicate(cx)
-                && let Some((feature, level)) = parse_stability(cx, args)
-            {
-                this.stability = Some((Stability { level, feature }, cx.attr_span));
-            }
-        }),
-        (&[sym::unstable], |this, cx, args| {
-            reject_outside_std!(cx);
-            if !this.check_duplicate(cx)
-                && let Some((feature, level)) = parse_unstability(cx, args)
-            {
-                this.stability = Some((Stability { level, feature }, cx.attr_span));
-            }
-        }),
-        (&[sym::rustc_allowed_through_unstable_modules], |this, cx, args| {
-            reject_outside_std!(cx);
-            this.allowed_through_unstable_modules = args.name_value().and_then(|i| i.value_as_str())
-        }),
+        (
+            &[sym::stable],
+            template!(List: r#"feature = "name", since = "version""#),
+            |this, cx, args| {
+                reject_outside_std!(cx);
+                if !this.check_duplicate(cx)
+                    && let Some((feature, level)) = parse_stability(cx, args)
+                {
+                    this.stability = Some((Stability { level, feature }, cx.attr_span));
+                }
+            },
+        ),
+        (
+            &[sym::unstable],
+            template!(List: r#"feature = "name", reason = "...", issue = "N""#),
+            |this, cx, args| {
+                reject_outside_std!(cx);
+                if !this.check_duplicate(cx)
+                    && let Some((feature, level)) = parse_unstability(cx, args)
+                {
+                    this.stability = Some((Stability { level, feature }, cx.attr_span));
+                }
+            },
+        ),
+        (
+            &[sym::rustc_allowed_through_unstable_modules],
+            template!(NameValueStr: "deprecation message"),
+            |this, cx, args| {
+                reject_outside_std!(cx);
+                this.allowed_through_unstable_modules =
+                    args.name_value().and_then(|i| i.value_as_str())
+            },
+        ),
     ];
 
     fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
@@ -96,8 +110,10 @@ pub(crate) struct BodyStabilityParser {
 }
 
 impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
-    const ATTRIBUTES: AcceptMapping<Self, S> =
-        &[(&[sym::rustc_default_body_unstable], |this, cx, args| {
+    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
+        &[sym::rustc_default_body_unstable],
+        template!(List: r#"feature = "name", reason = "...", issue = "N""#),
+        |this, cx, args| {
             reject_outside_std!(cx);
             if this.stability.is_some() {
                 cx.dcx()
@@ -105,7 +121,8 @@ impl<S: Stage> AttributeParser<S> for BodyStabilityParser {
             } else if let Some((feature, level)) = parse_unstability(cx, args) {
                 this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span));
             }
-        })];
+        },
+    )];
 
     fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
         let (stability, span) = self.stability?;
@@ -120,6 +137,7 @@ impl<S: Stage> SingleAttributeParser<S> for ConstStabilityIndirectParser {
     const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect];
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Ignore;
+    const TEMPLATE: AttributeTemplate = template!(Word);
 
     fn convert(_cx: &mut AcceptContext<'_, '_, S>, _args: &ArgParser<'_>) -> Option<AttributeKind> {
         Some(AttributeKind::ConstStabilityIndirect)
@@ -146,7 +164,7 @@ impl ConstStabilityParser {
 
 impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
     const ATTRIBUTES: AcceptMapping<Self, S> = &[
-        (&[sym::rustc_const_stable], |this, cx, args| {
+        (&[sym::rustc_const_stable], template!(List: r#"feature = "name""#), |this, cx, args| {
             reject_outside_std!(cx);
 
             if !this.check_duplicate(cx)
@@ -158,7 +176,7 @@ impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
                 ));
             }
         }),
-        (&[sym::rustc_const_unstable], |this, cx, args| {
+        (&[sym::rustc_const_unstable], template!(List: r#"feature = "name""#), |this, cx, args| {
             reject_outside_std!(cx);
             if !this.check_duplicate(cx)
                 && let Some((feature, level)) = parse_unstability(cx, args)
@@ -169,7 +187,7 @@ impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
                 ));
             }
         }),
-        (&[sym::rustc_promotable], |this, cx, _| {
+        (&[sym::rustc_promotable], template!(Word), |this, cx, _| {
             reject_outside_std!(cx);
             this.promotable = true;
         }),
diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
index 16ad9d03e50..b0f5dac7fe2 100644
--- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
@@ -1,4 +1,5 @@
 use rustc_attr_data_structures::AttributeKind;
+use rustc_feature::{AttributeTemplate, template};
 use rustc_span::hygiene::Transparency;
 use rustc_span::{Symbol, sym};
 
@@ -17,6 +18,8 @@ impl<S: Stage> SingleAttributeParser<S> for TransparencyParser {
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Custom(|cx, used, unused| {
         cx.dcx().span_err(vec![used, unused], "multiple macro transparency attributes");
     });
+    const TEMPLATE: AttributeTemplate =
+        template!(NameValueStr: "transparent|semitransparent|opaque");
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         match args.name_value().and_then(|nv| nv.value_as_str()) {
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 3e1ef688733..ca95c67b418 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -10,7 +10,7 @@ use rustc_ast::NodeId;
 use rustc_attr_data_structures::AttributeKind;
 use rustc_attr_data_structures::lints::{AttributeLint, AttributeLintKind};
 use rustc_errors::{DiagCtxtHandle, Diagnostic};
-use rustc_feature::Features;
+use rustc_feature::{AttributeTemplate, Features};
 use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, HirId};
 use rustc_session::Session;
 use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
@@ -27,11 +27,12 @@ use crate::attributes::stability::{
 use crate::attributes::transparency::TransparencyParser;
 use crate::attributes::{AttributeParser as _, Combine, Single};
 use crate::parser::{ArgParser, MetaItemParser};
+use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason};
 
 macro_rules! group_type {
     ($stage: ty) => {
          LazyLock<(
-            BTreeMap<&'static [Symbol], Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $stage>, &ArgParser<'a>) + Send + Sync>>,
+            BTreeMap<&'static [Symbol], Vec<(AttributeTemplate, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $stage>, &ArgParser<'a>) + Send + Sync>)>>,
             Vec<Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, $stage>) -> Option<AttributeKind>>>
         )>
     };
@@ -60,7 +61,7 @@ macro_rules! attribute_parsers {
         @[$ty: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?];
     ) => {
         pub(crate) static $name: group_type!($ty) = LazyLock::new(|| {
-            let mut accepts = BTreeMap::<_, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $ty>, &ArgParser<'a>) + Send + Sync>>::new();
+            let mut accepts = BTreeMap::<_, Vec<(AttributeTemplate, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $ty>, &ArgParser<'a>) + Send + Sync>)>>::new();
             let mut finalizes = Vec::<Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, $ty>) -> Option<AttributeKind>>>::new();
             $(
                 {
@@ -68,13 +69,12 @@ macro_rules! attribute_parsers {
                         static STATE_OBJECT: RefCell<$names> = RefCell::new(<$names>::default());
                     };
 
-                    for (k, v) in <$names>::ATTRIBUTES {
-                        let old = accepts.insert(*k, Box::new(|cx, args| {
+                    for (path, template, accept_fn) in <$names>::ATTRIBUTES {
+                        accepts.entry(*path).or_default().push((*template, Box::new(|cx, args| {
                             STATE_OBJECT.with_borrow_mut(|s| {
-                                v(s, cx, args)
+                                accept_fn(s, cx, args)
                             })
-                        }));
-                        assert!(old.is_none());
+                        })));
                     }
 
                     finalizes.push(Box::new(|cx| {
@@ -168,6 +168,14 @@ pub(crate) struct AcceptContext<'f, 'sess, S: Stage> {
     pub(crate) finalize_cx: FinalizeContext<'f, 'sess, S>,
     /// The span of the attribute currently being parsed
     pub(crate) attr_span: Span,
+
+    /// The expected structure of the attribute.
+    ///
+    /// Used in reporting errors to give a hint to users what the attribute *should* look like.
+    pub(crate) template: &'f AttributeTemplate,
+
+    /// The name of the attribute we're currently accepting.
+    pub(crate) attr_path: AttrPath,
 }
 
 impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
@@ -175,10 +183,54 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
         S::emit_err(&self.sess, diag)
     }
 
+    /// Emit a lint. This method is somewhat special, since lints emitted during attribute parsing
+    /// must be delayed until after HIR is built. This method will take care of the details of
+    /// that.
     pub(crate) fn emit_lint(&mut self, lint: AttributeLintKind, span: Span) {
         let id = self.target_id;
         (self.emit_lint)(AttributeLint { id, span, kind: lint });
     }
+
+    pub(crate) fn expected_string_literal(&self, span: Span) -> ErrorGuaranteed {
+        // 539?
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedStringLiteral,
+        })
+    }
+
+    // pub(crate) fn expected_any_arguments(&self, span: Span) -> ErrorGuaranteed {
+    //
+    // }
+
+    pub(crate) fn expected_single_argument(&self, span: Span) -> ErrorGuaranteed {
+        // E534?
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedSingleArgument,
+        })
+    }
+
+    pub(crate) fn expected_specific_argument(
+        &self,
+        span: Span,
+        options: Vec<&'static str>,
+    ) -> ErrorGuaranteed {
+        // E535?
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedSpecificArgument(options),
+        })
+    }
 }
 
 impl<'f, 'sess, S: Stage> Deref for AcceptContext<'f, 'sess, S> {
@@ -377,18 +429,22 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
                     let args = parser.args();
                     let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
 
-                    if let Some(accept) = S::parsers().0.get(parts.as_slice()) {
-                        let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
-                            finalize_cx: FinalizeContext {
-                                cx: self,
-                                target_span,
-                                target_id,
-                                emit_lint: &mut emit_lint,
-                            },
-                            attr_span: lower_span(attr.span),
-                        };
-
-                        accept(&mut cx, args)
+                    if let Some(accepts) = S::parsers().0.get(parts.as_slice()) {
+                        for (template, accept) in accepts {
+                            let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
+                                finalize_cx: FinalizeContext {
+                                    cx: self,
+                                    target_span,
+                                    target_id,
+                                    emit_lint: &mut emit_lint,
+                                },
+                                attr_span: lower_span(attr.span),
+                                template,
+                                attr_path: path.get_attribute_path(),
+                            };
+
+                            accept(&mut cx, args)
+                        }
                     } else {
                         // If we're here, we must be compiling a tool attribute... Or someone
                         // forgot to parse their fancy new attribute. Let's warn them in any case.
diff --git a/compiler/rustc_attr_parsing/src/lints.rs b/compiler/rustc_attr_parsing/src/lints.rs
index d0d112446b4..fee22293b47 100644
--- a/compiler/rustc_attr_parsing/src/lints.rs
+++ b/compiler/rustc_attr_parsing/src/lints.rs
@@ -1,5 +1,5 @@
 use rustc_attr_data_structures::lints::{AttributeLint, AttributeLintKind};
-use rustc_errors::LintEmitter;
+use rustc_errors::{DiagArgValue, LintEmitter};
 use rustc_hir::HirId;
 
 use crate::session_diagnostics;
@@ -15,5 +15,18 @@ pub fn emit_attribute_lint<L: LintEmitter>(lint: &AttributeLint<HirId>, lint_emi
                 *span,
                 session_diagnostics::UnusedDuplicate { this, other, warning },
             ),
+        AttributeLintKind::IllFormedAttributeInput { suggestions } => {
+            lint_emitter.emit_node_span_lint(
+                rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT,
+                *id,
+                *span,
+                session_diagnostics::IllFormedAttributeInput {
+                    num_suggestions: suggestions.len(),
+                    suggestions: DiagArgValue::StrListSepByAnd(
+                        suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(),
+                    ),
+                },
+            );
+        }
     }
 }
diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
index 7f847d3dd4c..08cd8b5df2d 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -2,7 +2,11 @@ use std::num::IntErrorKind;
 
 use rustc_ast as ast;
 use rustc_errors::codes::*;
-use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level};
+use rustc_errors::{
+    Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
+};
+use rustc_feature::AttributeTemplate;
+use rustc_hir::AttrPath;
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_span::{Span, Symbol};
 
@@ -462,6 +466,14 @@ pub(crate) struct UnusedDuplicate {
     pub warning: bool,
 }
 
+// FIXME(jdonszelmann): duplicated in rustc_lints, should be moved here completely.
+#[derive(LintDiagnostic)]
+#[diag(attr_parsing_ill_formed_attribute_input)]
+pub(crate) struct IllFormedAttributeInput {
+    pub num_suggestions: usize,
+    pub suggestions: DiagArgValue,
+}
+
 #[derive(Diagnostic)]
 #[diag(attr_parsing_stability_outside_std, code = E0734)]
 pub(crate) struct StabilityOutsideStd {
@@ -490,3 +502,72 @@ pub(crate) struct UnrecognizedReprHint {
     #[primary_span]
     pub span: Span,
 }
+
+pub(crate) enum AttributeParseErrorReason {
+    ExpectedStringLiteral,
+    ExpectedSingleArgument,
+    ExpectedSpecificArgument(Vec<&'static str>),
+}
+
+pub(crate) struct AttributeParseError {
+    pub(crate) span: Span,
+    pub(crate) attr_span: Span,
+    pub(crate) template: AttributeTemplate,
+    pub(crate) attribute: AttrPath,
+    pub(crate) reason: AttributeParseErrorReason,
+}
+
+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();
+
+        let mut diag = Diag::new(dcx, level, format!("malformed `{name}` attribute input"));
+        diag.span(self.attr_span);
+        diag.code(E0539);
+        match self.reason {
+            AttributeParseErrorReason::ExpectedStringLiteral => {
+                diag.span_note(self.span, "expected a string literal here");
+            }
+            AttributeParseErrorReason::ExpectedSingleArgument => {
+                diag.span_note(self.span, "expected a single argument here");
+            }
+            AttributeParseErrorReason::ExpectedSpecificArgument(possibilities) => {
+                match possibilities.as_slice() {
+                    &[] => {}
+                    &[x] => {
+                        diag.span_note(self.span, format!("the only valid argument here is `{x}`"));
+                    }
+                    [first, second] => {
+                        diag.span_note(
+                            self.span,
+                            format!("valid arguments are `{first}` or `{second}`"),
+                        );
+                    }
+                    [first @ .., second_to_last, last] => {
+                        let mut res = String::new();
+                        for i in first {
+                            res.push_str(&format!("`{i}`, "));
+                        }
+                        res.push_str(&format!("`{second_to_last}` or `{last}`"));
+
+                        diag.span_note(self.span, format!("valid arguments are {res}"));
+                    }
+                }
+            }
+        }
+
+        let suggestions = self.template.suggestions(false, &name);
+        diag.span_suggestions(
+            self.attr_span,
+            if suggestions.len() == 1 {
+                "must be of the form"
+            } else {
+                "the following are possible correct uses"
+            },
+            suggestions,
+            Applicability::HasPlaceholders,
+        );
+
+        diag
+    }
+}
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 8388fd267f5..188a9a98ce7 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -1,9 +1,7 @@
 use std::str::FromStr;
 
 use rustc_abi::ExternAbi;
-use rustc_ast::expand::autodiff_attrs::{
-    AutoDiffAttrs, DiffActivity, DiffMode, valid_input_activity, valid_ret_activity,
-};
+use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode};
 use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr};
 use rustc_attr_data_structures::ReprAttr::ReprAlign;
 use rustc_attr_data_structures::{
@@ -86,7 +84,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
 
     let rust_target_features = tcx.rust_target_features(LOCAL_CRATE);
 
-    let mut inline_span = None;
     let mut link_ordinal_span = None;
     let mut no_sanitize_span = None;
     let mut mixed_export_name_no_mangle_lint_state = MixedExportNameAndNoMangleState::default();
@@ -452,8 +449,14 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
 
     mixed_export_name_no_mangle_lint_state.lint_if_mixed(tcx);
 
-    codegen_fn_attrs.inline =
-        find_attr!(attrs, AttributeKind::Inline(i, _) => *i).unwrap_or(InlineAttr::None);
+    let inline_span;
+    (codegen_fn_attrs.inline, inline_span) = if let Some((inline_attr, span)) =
+        find_attr!(attrs, AttributeKind::Inline(i, span) => (*i, *span))
+    {
+        (inline_attr, Some(span))
+    } else {
+        (InlineAttr::None, None)
+    };
 
     // naked function MUST NOT be inlined! This attribute is required for the rust compiler itself,
     // but not for the code generation backend because at that point the naked function will just be
@@ -474,7 +477,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
             return OptimizeAttr::Default;
         };
 
-        inline_span = Some(attr.span());
         let [item] = &items[..] else {
             tcx.dcx().emit_err(errors::ExpectedOneArgumentOptimize { span: attr.span() });
             return OptimizeAttr::Default;
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 7d6e471e7e9..b5218ec267c 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -111,6 +111,7 @@ pub enum AttributeGate {
     Ungated,
 }
 
+// FIXME(jdonszelmann): move to rustc_attr_data_structures
 /// A template that the attribute input must match.
 /// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
 #[derive(Clone, Copy, Default)]
@@ -127,6 +128,26 @@ pub struct AttributeTemplate {
     pub name_value_str: Option<&'static str>,
 }
 
+impl AttributeTemplate {
+    pub fn suggestions(&self, inner: bool, name: impl std::fmt::Display) -> Vec<String> {
+        let mut suggestions = vec![];
+        let inner = if inner { "!" } else { "" };
+        if self.word {
+            suggestions.push(format!("#{inner}[{name}]"));
+        }
+        if let Some(descr) = self.list {
+            suggestions.push(format!("#{inner}[{name}({descr})]"));
+        }
+        suggestions.extend(self.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
+        if let Some(descr) = self.name_value_str {
+            suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
+        }
+        suggestions.sort();
+
+        suggestions
+    }
+}
+
 /// How to handle multiple duplicate attributes on the same item.
 #[derive(Clone, Copy, Default)]
 pub enum AttributeDuplicates {
@@ -181,20 +202,21 @@ pub enum AttributeDuplicates {
 /// A convenience macro for constructing attribute templates.
 /// E.g., `template!(Word, List: "description")` means that the attribute
 /// supports forms `#[attr]` and `#[attr(description)]`.
+#[macro_export]
 macro_rules! template {
-    (Word) => { template!(@ true, None, &[], None) };
-    (List: $descr: expr) => { template!(@ false, Some($descr), &[], None) };
-    (OneOf: $one_of: expr) => { template!(@ false, None, $one_of, None) };
-    (NameValueStr: $descr: expr) => { template!(@ false, None, &[], Some($descr)) };
-    (Word, List: $descr: expr) => { template!(@ true, Some($descr), &[], None) };
-    (Word, NameValueStr: $descr: expr) => { template!(@ true, None, &[], Some($descr)) };
+    (Word) => { $crate::template!(@ true, None, &[], None) };
+    (List: $descr: expr) => { $crate::template!(@ false, Some($descr), &[], None) };
+    (OneOf: $one_of: expr) => { $crate::template!(@ false, None, $one_of, None) };
+    (NameValueStr: $descr: expr) => { $crate::template!(@ false, None, &[], Some($descr)) };
+    (Word, List: $descr: expr) => { $crate::template!(@ true, Some($descr), &[], None) };
+    (Word, NameValueStr: $descr: expr) => { $crate::template!(@ true, None, &[], Some($descr)) };
     (List: $descr1: expr, NameValueStr: $descr2: expr) => {
-        template!(@ false, Some($descr1), &[], Some($descr2))
+        $crate::template!(@ false, Some($descr1), &[], Some($descr2))
     };
     (Word, List: $descr1: expr, NameValueStr: $descr2: expr) => {
-        template!(@ true, Some($descr1), &[], Some($descr2))
+        $crate::template!(@ true, Some($descr1), &[], Some($descr2))
     };
-    (@ $word: expr, $list: expr, $one_of: expr, $name_value_str: expr) => { AttributeTemplate {
+    (@ $word: expr, $list: expr, $one_of: expr, $name_value_str: expr) => { $crate::AttributeTemplate {
         word: $word, list: $list, one_of: $one_of, name_value_str: $name_value_str
     } };
 }
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 3d17dfbc451..0a6e771a4d6 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -2618,6 +2618,7 @@ pub(crate) struct UnusedCrateDependency {
     pub local_crate: Symbol,
 }
 
+// FIXME(jdonszelmann): duplicated in rustc_attr_parsing, should be moved there completely.
 #[derive(LintDiagnostic)]
 #[diag(lint_ill_formed_attribute_input)]
 pub(crate) struct IllFormedAttributeInput {
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index 555ab3cdb2b..1c12e8c189d 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -282,11 +282,15 @@ fn emit_malformed_attribute(
     name: Symbol,
     template: AttributeTemplate,
 ) {
+    // attrs with new parsers are locally validated so excluded here
+    if matches!(name, sym::inline | sym::rustc_force_inline) {
+        return;
+    }
+
     // Some of previously accepted forms were used in practice,
     // report them as warnings for now.
-    let should_warn = |name| {
-        matches!(name, sym::doc | sym::ignore | sym::inline | sym::link | sym::test | sym::bench)
-    };
+    let should_warn =
+        |name| matches!(name, sym::doc | sym::ignore | sym::link | sym::test | sym::bench);
 
     let error_msg = format!("malformed `{name}` attribute input");
     let mut suggestions = vec![];
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 24a32feb2c3..d543abb5411 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -124,6 +124,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                     AttributeKind::Stability { span, .. }
                     | AttributeKind::ConstStability { span, .. },
                 ) => self.check_stability_promotable(*span, target),
+                Attribute::Parsed(AttributeKind::Inline(InlineAttr::Force { .. }, ..)) => {} // handled separately below
                 Attribute::Parsed(AttributeKind::Inline(kind, attr_span)) => {
                     self.check_inline(hir_id, *attr_span, span, kind, target)
                 }
@@ -682,6 +683,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         ) => {
                             continue;
                         }
+                        Attribute::Parsed(AttributeKind::Inline(.., span)) => {
+                            self.dcx().emit_err(errors::NakedFunctionIncompatibleAttribute {
+                                span: *span,
+                                naked_span: attr.span(),
+                                attr: sym::inline.to_string(),
+                            });
+
+                            return;
+                        }
+                        // FIXME(jdonszelmann): make exhaustive
                         _ => {}
                     }
 
@@ -2902,7 +2913,8 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
 fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) {
     let attrs = tcx.hir_attrs(item.hir_id());
 
-    if let Some(attr_span) = find_attr!(attrs, AttributeKind::Inline(_, span) => *span) {
+    if let Some(attr_span) = find_attr!(attrs, AttributeKind::Inline(i, span) if !matches!(i, InlineAttr::Force{..}) => *span)
+    {
         tcx.dcx().emit_err(errors::NonExportedMacroInvalidAttrs { attr_span });
     }
 }