about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-06-18 06:25:21 +0000
committerbors <bors@rust-lang.org>2025-06-18 06:25:21 +0000
commit1bb335244c311a07cee165c28c553c869e6f64a9 (patch)
treeab7ce41f86ed7f64188504f84acb3fb137ca1fc9
parent27733d46d79f4eb92e240fbba502c43022665735 (diff)
parent81f8b570b9dd5d7bef27e1f1391dc73eb8fa4ff6 (diff)
downloadrust-1bb335244c311a07cee165c28c553c869e6f64a9.tar.gz
rust-1bb335244c311a07cee165c28c553c869e6f64a9.zip
Auto merge of #138165 - jdonszelmann:inline, r=oli-obk
Rewrite `inline` attribute parser to use new infrastructure and improve diagnostics for all parsed attributes

r? `@oli-obk`

This PR:
- creates a new parser for inline attributes
- creates consistent error messages and error codes between attribute parsers; inline and others
- as such changes a few error messages for other attributes to be (in my eyes) much more consistent
- tests ast-lowering lints introduced by rust-lang/rust#138164 since this is now useful for the first time
- Coalesce some useless error codes

Builds on top of rust-lang/rust#138164

Closes rust-lang/rust#137950
-rw-r--r--compiler/rustc_attr_data_structures/src/attributes.rs5
-rw-r--r--compiler/rustc_attr_data_structures/src/lints.rs1
-rw-r--r--compiler/rustc_attr_parsing/messages.ftl14
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/confusables.rs53
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/deprecation.rs111
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/inline.rs8
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs4
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs26
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/repr.rs4
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/stability.rs113
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/transparency.rs16
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs181
-rw-r--r--compiler/rustc_attr_parsing/src/lints.rs15
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs166
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs57
-rw-r--r--compiler/rustc_codegen_ssa/src/errors.rs15
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0534.md8
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0535.md9
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0539.md29
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0565.md7
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0805.md26
-rw-r--r--compiler/rustc_error_codes/src/lib.rs1
-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.rs17
-rw-r--r--compiler/rustc_passes/src/check_attr.rs107
-rw-r--r--src/rustdoc-json-types/lib.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs/inline_always.rs25
-rw-r--r--src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_inline.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs14
-rw-r--r--tests/rustdoc-json/attrs/inline.rs6
-rw-r--r--tests/ui/attributes/multiple-invalid.stderr18
-rw-r--r--tests/ui/attributes/rustc_confusables.rs4
-rw-r--r--tests/ui/attributes/rustc_confusables.stderr29
-rw-r--r--tests/ui/deprecation/deprecation-sanity.rs14
-rw-r--r--tests/ui/deprecation/deprecation-sanity.stderr128
-rw-r--r--tests/ui/deprecation/invalid-literal.stderr19
-rw-r--r--tests/ui/error-codes/E0534.rs6
-rw-r--r--tests/ui/error-codes/E0534.stderr9
-rw-r--r--tests/ui/error-codes/E0539.rs (renamed from tests/ui/error-codes/E0565-2.rs)2
-rw-r--r--tests/ui/error-codes/E0539.stderr13
-rw-r--r--tests/ui/error-codes/E0540.rs6
-rw-r--r--tests/ui/error-codes/E0540.stderr19
-rw-r--r--tests/ui/error-codes/E0565-1.stderr20
-rw-r--r--tests/ui/error-codes/E0565-2.stderr11
-rw-r--r--tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr20
-rw-r--r--tests/ui/force-inlining/invalid.rs1
-rw-r--r--tests/ui/force-inlining/invalid.stderr132
-rw-r--r--tests/ui/invalid/invalid-inline.rs4
-rw-r--r--tests/ui/invalid/invalid-inline.stderr31
-rw-r--r--tests/ui/issues/issue-43988.rs4
-rw-r--r--tests/ui/issues/issue-43988.stderr76
-rw-r--r--tests/ui/lint/unused/unused-attr-duplicate.stderr26
-rw-r--r--tests/ui/macros/genercs-in-path-with-prettry-hir.stdout2
-rw-r--r--tests/ui/malformed/malformed-regressions.stderr18
-rw-r--r--tests/ui/repr/repr.stderr22
-rw-r--r--tests/ui/span/E0535.rs6
-rw-r--r--tests/ui/span/E0535.stderr11
-rw-r--r--tests/ui/span/E0539.rs6
-rw-r--r--tests/ui/span/E0539.stderr20
-rw-r--r--tests/ui/stability-attribute/stability-attribute-sanity-2.rs2
-rw-r--r--tests/ui/stability-attribute/stability-attribute-sanity-2.stderr9
-rw-r--r--tests/ui/stability-attribute/stability-attribute-sanity.rs8
-rw-r--r--tests/ui/stability-attribute/stability-attribute-sanity.stderr36
66 files changed, 1189 insertions, 647 deletions
diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs
index 8f95a017809..d4c43456049 100644
--- a/compiler/rustc_attr_data_structures/src/attributes.rs
+++ b/compiler/rustc_attr_data_structures/src/attributes.rs
@@ -8,7 +8,7 @@ use thin_vec::ThinVec;
 
 use crate::{DefaultBodyStability, PartialConstStability, PrintAttribute, RustcVersion, Stability};
 
-#[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
+#[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic, PrintAttribute)]
 pub enum InlineAttr {
     None,
     Hint,
@@ -221,6 +221,9 @@ pub enum AttributeKind {
     /// Represents [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
     DocComment { style: AttrStyle, kind: CommentKind, span: Span, comment: Symbol },
 
+    /// Represents `#[inline]` and `#[rustc_force_inline]`.
+    Inline(InlineAttr, Span),
+
     /// Represents `#[rustc_macro_transparency]`.
     MacroTransparency(Transparency),
 
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..b9b386635f6 100644
--- a/compiler/rustc_attr_parsing/messages.ftl
+++ b/compiler/rustc_attr_parsing/messages.ftl
@@ -23,8 +23,10 @@ attr_parsing_expects_feature_list =
 attr_parsing_expects_features =
     `{$name}` expects feature names
 
-attr_parsing_incorrect_meta_item = expected a quoted string literal
-attr_parsing_incorrect_meta_item_suggestion = consider surrounding this with quotes
+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_repr_format_align_one_arg =
     incorrect `repr(align)` attribute format: `align` takes exactly one argument in parentheses
@@ -81,9 +83,6 @@ attr_parsing_missing_note =
 attr_parsing_missing_since =
     missing 'since'
 
-attr_parsing_multiple_item =
-    multiple '{$item}' items
-
 attr_parsing_multiple_stability_levels =
     multiple stability levels
 
@@ -122,10 +121,6 @@ attr_parsing_unsupported_literal_cfg_boolean =
     literal in `cfg` predicate value must be a boolean
 attr_parsing_unsupported_literal_cfg_string =
     literal in `cfg` predicate value must be a string
-attr_parsing_unsupported_literal_deprecated_kv_pair =
-    item in `deprecated` must be a key/value pair
-attr_parsing_unsupported_literal_deprecated_string =
-    literal in `deprecated` value must be a string
 attr_parsing_unsupported_literal_generic =
     unsupported literal
 attr_parsing_unsupported_literal_suggestion =
@@ -136,6 +131,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..c911908dfb3 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,33 @@ 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 {
+                cx.expected_list(cx.attr_span);
+                return;
             };
 
-            this.confusables.push(lit.symbol);
-        }
+            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().and_then(|i| i.value_str()) else {
+                    cx.expected_string_literal(span, param.lit());
+                    continue;
+                };
+
+                this.confusables.push(lit);
+            }
 
-        this.first_span.get_or_insert(cx.attr_span);
-    })];
+            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..702ad66f578 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;
@@ -6,7 +7,6 @@ use super::{AttributeOrder, OnDuplicate, SingleAttributeParser};
 use crate::context::{AcceptContext, Stage};
 use crate::parser::ArgParser;
 use crate::session_diagnostics;
-use crate::session_diagnostics::UnsupportedLiteralReason;
 
 pub(crate) struct DeprecationParser;
 
@@ -18,25 +18,18 @@ fn get<S: Stage>(
     item: &Option<Symbol>,
 ) -> Option<Symbol> {
     if item.is_some() {
-        cx.emit_err(session_diagnostics::MultipleItem { span: param_span, item: name.to_string() });
+        cx.duplicate_key(param_span, name);
         return None;
     }
     if let Some(v) = arg.name_value() {
         if let Some(value_str) = v.value_as_str() {
             Some(value_str)
         } else {
-            let lit = v.value_as_lit();
-            cx.emit_err(session_diagnostics::UnsupportedLiteral {
-                span: v.value_span,
-                reason: UnsupportedLiteralReason::DeprecatedString,
-                is_bytestr: lit.kind.is_bytestr(),
-                start_point_span: cx.sess().source_map().start_point(lit.span),
-            });
+            cx.expected_string_literal(v.value_span, Some(&v.value_as_lit()));
             None
         }
     } else {
-        // FIXME(jdonszelmann): suggestion?
-        cx.emit_err(session_diagnostics::IncorrectMetaItem { span: param_span, suggestion: None });
+        cx.expected_name_value(param_span, Some(name));
         None
     }
 }
@@ -45,6 +38,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();
@@ -55,57 +53,60 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
 
         let is_rustc = features.staged_api();
 
-        if let Some(value) = args.name_value()
-            && let Some(value_str) = value.value_as_str()
-        {
-            note = Some(value_str)
-        } else if let Some(list) = args.list() {
-            for param in list.mixed() {
-                let param_span = param.span();
-                let Some(param) = param.meta_item() else {
-                    cx.emit_err(session_diagnostics::UnsupportedLiteral {
-                        span: param_span,
-                        reason: UnsupportedLiteralReason::DeprecatedKvPair,
-                        is_bytestr: false,
-                        start_point_span: cx.sess().source_map().start_point(param_span),
-                    });
-                    return None;
-                };
+        match args {
+            ArgParser::NoArgs => {
+                // ok
+            }
+            ArgParser::List(list) => {
+                for param in list.mixed() {
+                    let Some(param) = param.meta_item() else {
+                        cx.unexpected_literal(param.span());
+                        return None;
+                    };
 
-                let ident_name = param.path().word_sym();
+                    let ident_name = param.path().word_sym();
 
-                match ident_name {
-                    Some(name @ sym::since) => {
-                        since = Some(get(cx, name, param_span, param.args(), &since)?);
-                    }
-                    Some(name @ sym::note) => {
-                        note = Some(get(cx, name, param_span, param.args(), &note)?);
-                    }
-                    Some(name @ sym::suggestion) => {
-                        if !features.deprecated_suggestion() {
-                            cx.emit_err(session_diagnostics::DeprecatedItemSuggestion {
-                                span: param_span,
-                                is_nightly: cx.sess().is_nightly_build(),
-                                details: (),
-                            });
+                    match ident_name {
+                        Some(name @ sym::since) => {
+                            since = Some(get(cx, name, param.span(), param.args(), &since)?);
+                        }
+                        Some(name @ sym::note) => {
+                            note = Some(get(cx, name, param.span(), param.args(), &note)?);
                         }
+                        Some(name @ sym::suggestion) => {
+                            if !features.deprecated_suggestion() {
+                                cx.emit_err(session_diagnostics::DeprecatedItemSuggestion {
+                                    span: param.span(),
+                                    is_nightly: cx.sess().is_nightly_build(),
+                                    details: (),
+                                });
+                            }
 
-                        suggestion = Some(get(cx, name, param_span, param.args(), &suggestion)?);
-                    }
-                    _ => {
-                        cx.emit_err(session_diagnostics::UnknownMetaItem {
-                            span: param_span,
-                            item: param.path().to_string(),
-                            expected: if features.deprecated_suggestion() {
-                                &["since", "note", "suggestion"]
-                            } else {
-                                &["since", "note"]
-                            },
-                        });
-                        return None;
+                            suggestion =
+                                Some(get(cx, name, param.span(), param.args(), &suggestion)?);
+                        }
+                        _ => {
+                            cx.unknown_key(
+                                param.span(),
+                                param.path().to_string(),
+                                if features.deprecated_suggestion() {
+                                    &["since", "note", "suggestion"]
+                                } else {
+                                    &["since", "note"]
+                                },
+                            );
+                            return None;
+                        }
                     }
                 }
             }
+            ArgParser::NameValue(v) => {
+                let Some(value) = v.value_as_str() else {
+                    cx.expected_string_literal(v.value_span, Some(v.value_as_lit()));
+                    return None;
+                };
+                note = Some(value);
+            }
         }
 
         let since = if let Some(since) = since {
diff --git a/compiler/rustc_attr_parsing/src/attributes/inline.rs b/compiler/rustc_attr_parsing/src/attributes/inline.rs
index c7f82082c2e..25efc3ae49b 100644
--- a/compiler/rustc_attr_parsing/src/attributes/inline.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/inline.rs
@@ -29,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_sym()) {
                     Some(sym::always) => {
                         Some(AttributeKind::Inline(InlineAttr::Always, cx.attr_span))
                     }
@@ -63,7 +63,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
     const TEMPLATE: AttributeTemplate = template!(Word, List: "reason", NameValueStr: "reason");
 
-    fn convert(cx: &AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
         let reason = match args {
             ArgParser::NoArgs => None,
             ArgParser::List(list) => {
@@ -73,7 +73,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
                 };
 
                 let Some(reason) = l.lit().and_then(|i| i.kind.str()) else {
-                    cx.expected_string_literal(l.span());
+                    cx.expected_string_literal(l.span(), l.lit());
                     return None;
                 };
 
@@ -81,7 +81,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
             }
             ArgParser::NameValue(v) => {
                 let Some(reason) = v.value_as_str() else {
-                    cx.expected_string_literal(v.value_span);
+                    cx.expected_string_literal(v.value_span, Some(v.value_as_lit()));
                     return None;
                 };
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
index 32a20d4c5b5..d4c846de56e 100644
--- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs
@@ -1,4 +1,5 @@
 use rustc_attr_data_structures::AttributeKind;
+use rustc_feature::{AttributeTemplate, template};
 use rustc_span::{Symbol, sym};
 
 use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
@@ -9,10 +10,9 @@ pub(crate) struct AsPtrParser;
 
 impl<S: Stage> SingleAttributeParser<S> for AsPtrParser {
     const PATH: &[Symbol] = &[sym::rustc_as_ptr];
-
     const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst;
-
     const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
+    const TEMPLATE: AttributeTemplate = template!(Word);
 
     fn convert(cx: &mut AcceptContext<'_, '_, S>, _args: &ArgParser<'_>) -> Option<AttributeKind> {
         // FIXME: check that there's no args (this is currently checked elsewhere)
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index df488c89a34..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;
 
@@ -29,6 +30,7 @@ pub(crate) mod allow_unstable;
 pub(crate) mod cfg;
 pub(crate) mod confusables;
 pub(crate) mod deprecation;
+pub(crate) mod inline;
 pub(crate) mod lint_helpers;
 pub(crate) mod repr;
 pub(crate) mod stability;
@@ -36,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.
 ///
@@ -88,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>;
 }
@@ -104,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
@@ -126,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)
@@ -223,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>,
@@ -242,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..ae9e7871874 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>,
@@ -31,6 +34,7 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
         let mut reprs = Vec::new();
 
         let Some(list) = args.list() else {
+            cx.expected_list(cx.attr_span);
             return reprs;
         };
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs
index 6589a51db2b..6871ff4ec9f 100644
--- a/compiler/rustc_attr_parsing/src/attributes/stability.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs
@@ -5,7 +5,8 @@ use rustc_attr_data_structures::{
     StableSince, UnstableReason, VERSION_PLACEHOLDER,
 };
 use rustc_errors::ErrorGuaranteed;
-use rustc_span::{Span, Symbol, sym};
+use rustc_feature::{AttributeTemplate, template};
+use rustc_span::{Ident, Span, Symbol, sym};
 
 use super::util::parse_version;
 use super::{AcceptMapping, AttributeOrder, AttributeParser, OnDuplicate, SingleAttributeParser};
@@ -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;
         }),
@@ -199,12 +217,10 @@ fn insert_value_into_option_or_error<S: Stage>(
     cx: &AcceptContext<'_, '_, S>,
     param: &MetaItemParser<'_>,
     item: &mut Option<Symbol>,
+    name: Ident,
 ) -> Option<()> {
     if item.is_some() {
-        cx.emit_err(session_diagnostics::MultipleItem {
-            span: param.span(),
-            item: param.path().to_string(),
-        });
+        cx.duplicate_key(name.span, name.name);
         None
     } else if let Some(v) = param.args().name_value()
         && let Some(s) = v.value_as_str()
@@ -212,10 +228,7 @@ fn insert_value_into_option_or_error<S: Stage>(
         *item = Some(s);
         Some(())
     } else {
-        cx.emit_err(session_diagnostics::IncorrectMetaItem {
-            span: param.span(),
-            suggestion: None,
-        });
+        cx.expected_name_value(param.span(), Some(name.name));
         None
     }
 }
@@ -241,9 +254,14 @@ pub(crate) fn parse_stability<S: Stage>(
             return None;
         };
 
-        match param.path().word_sym() {
-            Some(sym::feature) => insert_value_into_option_or_error(cx, &param, &mut feature)?,
-            Some(sym::since) => insert_value_into_option_or_error(cx, &param, &mut since)?,
+        let word = param.path().word();
+        match word.map(|i| i.name) {
+            Some(sym::feature) => {
+                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
+            }
+            Some(sym::since) => {
+                insert_value_into_option_or_error(cx, &param, &mut since, word.unwrap())?
+            }
             _ => {
                 cx.emit_err(session_diagnostics::UnknownMetaItem {
                     span: param_span,
@@ -310,11 +328,16 @@ pub(crate) fn parse_unstability<S: Stage>(
             return None;
         };
 
-        match param.path().word_sym() {
-            Some(sym::feature) => insert_value_into_option_or_error(cx, &param, &mut feature)?,
-            Some(sym::reason) => insert_value_into_option_or_error(cx, &param, &mut reason)?,
+        let word = param.path().word();
+        match word.map(|i| i.name) {
+            Some(sym::feature) => {
+                insert_value_into_option_or_error(cx, &param, &mut feature, word.unwrap())?
+            }
+            Some(sym::reason) => {
+                insert_value_into_option_or_error(cx, &param, &mut reason, word.unwrap())?
+            }
             Some(sym::issue) => {
-                insert_value_into_option_or_error(cx, &param, &mut issue)?;
+                insert_value_into_option_or_error(cx, &param, &mut issue, word.unwrap())?;
 
                 // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item
                 // is a name/value pair string literal.
@@ -344,9 +367,11 @@ pub(crate) fn parse_unstability<S: Stage>(
                 is_soft = true;
             }
             Some(sym::implied_by) => {
-                insert_value_into_option_or_error(cx, &param, &mut implied_by)?
+                insert_value_into_option_or_error(cx, &param, &mut implied_by, word.unwrap())?
+            }
+            Some(sym::old_name) => {
+                insert_value_into_option_or_error(cx, &param, &mut old_name, word.unwrap())?
             }
-            Some(sym::old_name) => insert_value_into_option_or_error(cx, &param, &mut old_name)?,
             _ => {
                 cx.emit_err(session_diagnostics::UnknownMetaItem {
                     span: param.span(),
diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs
index 16ad9d03e50..ce5ceb9139a 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,14 +18,23 @@ 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()) {
+        let Some(nv) = args.name_value() else {
+            cx.expected_name_value(cx.attr_span, None);
+            return None;
+        };
+        match nv.value_as_str() {
             Some(sym::transparent) => Some(Transparency::Transparent),
             Some(sym::semiopaque | sym::semitransparent) => Some(Transparency::SemiOpaque),
             Some(sym::opaque) => Some(Transparency::Opaque),
-            Some(other) => {
-                cx.dcx().span_err(cx.attr_span, format!("unknown macro transparency: `{other}`"));
+            Some(_) => {
+                cx.expected_specific_argument_strings(
+                    nv.value_span,
+                    vec!["transparent", "semitransparent", "opaque"],
+                );
                 None
             }
             None => None,
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 3193d8975e9..51c1760da30 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -5,12 +5,11 @@ use std::ops::{Deref, DerefMut};
 use std::sync::LazyLock;
 
 use private::Sealed;
-use rustc_ast as ast;
-use rustc_ast::NodeId;
+use rustc_ast::{self as ast, MetaItemLit, 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};
@@ -18,6 +17,7 @@ use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
 use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser};
 use crate::attributes::confusables::ConfusablesParser;
 use crate::attributes::deprecation::DeprecationParser;
+use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
 use crate::attributes::lint_helpers::AsPtrParser;
 use crate::attributes::repr::ReprParser;
 use crate::attributes::stability::{
@@ -26,11 +26,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, UnknownMetaItem};
 
 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>>>
         )>
     };
@@ -59,7 +60,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();
             $(
                 {
@@ -67,13 +68,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| {
@@ -106,6 +106,8 @@ attribute_parsers!(
         Single<AsPtrParser>,
         Single<ConstStabilityIndirectParser>,
         Single<DeprecationParser>,
+        Single<InlineParser>,
+        Single<RustcForceInlineParser>,
         Single<TransparencyParser>,
         // tidy-alphabetical-end
     ];
@@ -165,6 +167,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> {
@@ -172,10 +182,133 @@ 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 unknown_key(
+        &self,
+        span: Span,
+        found: String,
+        options: &'static [&'static str],
+    ) -> ErrorGuaranteed {
+        self.emit_err(UnknownMetaItem { span, item: found, expected: options })
+    }
+
+    /// error that a string literal was expected.
+    /// You can optionally give the literal you did find (which you found not to be a string literal)
+    /// which can make better errors. For example, if the literal was a byte string it will suggest
+    /// removing the `b` prefix.
+    pub(crate) fn expected_string_literal(
+        &self,
+        span: Span,
+        actual_literal: Option<&MetaItemLit>,
+    ) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedStringLiteral {
+                byte_string: actual_literal.and_then(|i| {
+                    i.kind.is_bytestr().then(|| self.sess().source_map().start_point(i.span))
+                }),
+            },
+        })
+    }
+
+    pub(crate) fn expected_list(&self, span: Span) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedList,
+        })
+    }
+
+    /// emit an error that a `name = value` pair was expected at this span. The symbol can be given for
+    /// a nicer error message talking about the specific name that was found lacking a value.
+    pub(crate) fn expected_name_value(&self, span: Span, name: Option<Symbol>) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedNameValue(name),
+        })
+    }
+
+    /// emit an error that a `name = value` pair was found where that name was already seen.
+    pub(crate) fn duplicate_key(&self, span: Span, key: Symbol) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::DuplicateKey(key),
+        })
+    }
+
+    /// an error that should be emitted when a [`MetaItemOrLitParser`](crate::parser::MetaItemOrLitParser)
+    /// was expected *not* to be a literal, but instead a meta item.
+    pub(crate) fn unexpected_literal(&self, span: Span) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::UnexpectedLiteral,
+        })
+    }
+
+    pub(crate) fn expected_single_argument(&self, span: Span) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedSingleArgument,
+        })
+    }
+
+    pub(crate) fn expected_specific_argument(
+        &self,
+        span: Span,
+        possibilities: Vec<&'static str>,
+    ) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedSpecificArgument {
+                possibilities,
+                strings: false,
+            },
+        })
+    }
+
+    pub(crate) fn expected_specific_argument_strings(
+        &self,
+        span: Span,
+        possibilities: Vec<&'static str>,
+    ) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedSpecificArgument {
+                possibilities,
+                strings: true,
+            },
+        })
+    }
 }
 
 impl<'f, 'sess, S: Stage> Deref for AcceptContext<'f, 'sess, S> {
@@ -374,18 +507,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..57ac92a0ca1 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};
 
@@ -12,8 +16,6 @@ pub(crate) enum UnsupportedLiteralReason {
     Generic,
     CfgString,
     CfgBoolean,
-    DeprecatedString,
-    DeprecatedKvPair,
 }
 
 #[derive(Diagnostic)]
@@ -32,37 +34,6 @@ pub(crate) struct InvalidPredicate {
     pub predicate: String,
 }
 
-#[derive(Diagnostic)]
-#[diag(attr_parsing_multiple_item, code = E0538)]
-pub(crate) struct MultipleItem {
-    #[primary_span]
-    pub span: Span,
-
-    pub item: String,
-}
-
-#[derive(Diagnostic)]
-#[diag(attr_parsing_incorrect_meta_item, code = E0539)]
-pub(crate) struct IncorrectMetaItem {
-    #[primary_span]
-    pub span: Span,
-
-    #[subdiagnostic]
-    pub suggestion: Option<IncorrectMetaItemSuggestion>,
-}
-
-#[derive(Subdiagnostic)]
-#[multipart_suggestion(
-    attr_parsing_incorrect_meta_item_suggestion,
-    applicability = "maybe-incorrect"
-)]
-pub(crate) struct IncorrectMetaItemSuggestion {
-    #[suggestion_part(code = "\"")]
-    pub lo: Span,
-    #[suggestion_part(code = "\"")]
-    pub hi: Span,
-}
-
 /// Error code: E0541
 pub(crate) struct UnknownMetaItem<'a> {
     pub span: Span,
@@ -217,6 +188,7 @@ pub(crate) struct InvalidReprHintNoValue {
 }
 
 /// Error code: E0565
+// FIXME(jdonszelmann): slowly phased out
 pub(crate) struct UnsupportedLiteral {
     pub span: Span,
     pub reason: UnsupportedLiteralReason,
@@ -239,12 +211,6 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for UnsupportedLiteral {
                 UnsupportedLiteralReason::CfgBoolean => {
                     fluent::attr_parsing_unsupported_literal_cfg_boolean
                 }
-                UnsupportedLiteralReason::DeprecatedString => {
-                    fluent::attr_parsing_unsupported_literal_deprecated_string
-                }
-                UnsupportedLiteralReason::DeprecatedKvPair => {
-                    fluent::attr_parsing_unsupported_literal_deprecated_kv_pair
-                }
             },
         );
         diag.span(self.span);
@@ -462,6 +428,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 +464,115 @@ pub(crate) struct UnrecognizedReprHint {
     #[primary_span]
     pub span: Span,
 }
+
+pub(crate) enum AttributeParseErrorReason {
+    ExpectedStringLiteral { byte_string: Option<Span> },
+    ExpectedSingleArgument,
+    ExpectedList,
+    UnexpectedLiteral,
+    ExpectedNameValue(Option<Symbol>),
+    DuplicateKey(Symbol),
+    ExpectedSpecificArgument { possibilities: Vec<&'static str>, strings: bool },
+}
+
+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 { byte_string } => {
+                if let Some(start_point_span) = byte_string {
+                    diag.span_suggestion(
+                        start_point_span,
+                        fluent::attr_parsing_unsupported_literal_suggestion,
+                        "",
+                        Applicability::MaybeIncorrect,
+                    );
+                    diag.note("expected a normal string literal, not a byte string literal");
+
+                    return diag;
+                } else {
+                    diag.span_label(self.span, "expected a string literal here");
+                }
+            }
+            AttributeParseErrorReason::ExpectedSingleArgument => {
+                diag.span_label(self.span, "expected a single argument here");
+                diag.code(E0805);
+            }
+            AttributeParseErrorReason::ExpectedList => {
+                diag.span_label(self.span, "expected this to be a list");
+            }
+            AttributeParseErrorReason::DuplicateKey(key) => {
+                diag.span_label(self.span, format!("found `{key}` used as a key more than once"));
+                diag.code(E0538);
+            }
+            AttributeParseErrorReason::UnexpectedLiteral => {
+                diag.span_label(self.span, format!("didn't expect a literal here"));
+                diag.code(E0565);
+            }
+            AttributeParseErrorReason::ExpectedNameValue(None) => {
+                diag.span_label(
+                    self.span,
+                    format!("expected this to be of the form `{name} = \"...\"`"),
+                );
+            }
+            AttributeParseErrorReason::ExpectedNameValue(Some(name)) => {
+                diag.span_label(
+                    self.span,
+                    format!("expected this to be of the form `{name} = \"...\"`"),
+                );
+            }
+            AttributeParseErrorReason::ExpectedSpecificArgument { possibilities, strings } => {
+                let quote = if strings { '"' } else { '`' };
+                match possibilities.as_slice() {
+                    &[] => {}
+                    &[x] => {
+                        diag.span_label(
+                            self.span,
+                            format!("the only valid argument here is {quote}{x}{quote}"),
+                        );
+                    }
+                    [first, second] => {
+                        diag.span_label(self.span, format!("valid arguments are {quote}{first}{quote} or {quote}{second}{quote}"));
+                    }
+                    [first @ .., second_to_last, last] => {
+                        let mut res = String::new();
+                        for i in first {
+                            res.push_str(&format!("{quote}{i}{quote}, "));
+                        }
+                        res.push_str(&format!(
+                            "{quote}{second_to_last}{quote} or {quote}{last}{quote}"
+                        ));
+
+                        diag.span_label(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 {
+                "try changing it to one of the following valid forms of the attribute"
+            },
+            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 0b31fa8fa88..188a9a98ce7 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -4,7 +4,9 @@ use rustc_abi::ExternAbi;
 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::{AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr};
+use rustc_attr_data_structures::{
+    AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr, find_attr,
+};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
@@ -21,7 +23,6 @@ use rustc_session::parse::feature_err;
 use rustc_session::{Session, lint};
 use rustc_span::{Ident, Span, sym};
 use rustc_target::spec::SanitizerSet;
-use tracing::debug;
 
 use crate::errors;
 use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature_attr};
@@ -83,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();
@@ -449,48 +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 = attrs.iter().fold(InlineAttr::None, |ia, attr| {
-        if !attr.has_name(sym::inline) {
-            return ia;
-        }
-
-        if attr.is_word() {
-            return InlineAttr::Hint;
-        }
-        let Some(ref items) = attr.meta_item_list() else {
-            return ia;
-        };
-        inline_span = Some(attr.span());
-
-        let [item] = &items[..] else {
-            tcx.dcx().emit_err(errors::ExpectedOneArgument { span: attr.span() });
-            return InlineAttr::None;
-        };
-
-        if item.has_name(sym::always) {
-            InlineAttr::Always
-        } else if item.has_name(sym::never) {
-            InlineAttr::Never
-        } else {
-            tcx.dcx().emit_err(errors::InvalidArgument { span: items[0].span() });
-
-            InlineAttr::None
-        }
-    });
-    codegen_fn_attrs.inline = attrs.iter().fold(codegen_fn_attrs.inline, |ia, attr| {
-        if !attr.has_name(sym::rustc_force_inline) || !tcx.features().rustc_attrs() {
-            return ia;
-        }
-
-        if attr.is_word() {
-            InlineAttr::Force { attr_span: attr.span(), reason: None }
-        } else if let Some(val) = attr.value_str() {
-            InlineAttr::Force { attr_span: attr.span(), reason: Some(val) }
-        } else {
-            debug!("`rustc_force_inline` not checked by attribute validation");
-            ia
-        }
-    });
+    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
@@ -511,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_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index bac02bdf983..5387b2a7f81 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -209,13 +209,6 @@ pub(crate) struct OutOfRangeInteger {
 }
 
 #[derive(Diagnostic)]
-#[diag(codegen_ssa_expected_one_argument, code = E0534)]
-pub(crate) struct ExpectedOneArgument {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
 #[diag(codegen_ssa_expected_one_argument, code = E0722)]
 pub(crate) struct ExpectedOneArgumentOptimize {
     #[primary_span]
@@ -223,14 +216,6 @@ pub(crate) struct ExpectedOneArgumentOptimize {
 }
 
 #[derive(Diagnostic)]
-#[diag(codegen_ssa_invalid_argument, code = E0535)]
-#[help]
-pub(crate) struct InvalidArgument {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
 #[diag(codegen_ssa_invalid_argument, code = E0722)]
 pub(crate) struct InvalidArgumentOptimize {
     #[primary_span]
diff --git a/compiler/rustc_error_codes/src/error_codes/E0534.md b/compiler/rustc_error_codes/src/error_codes/E0534.md
index 1ca9411b8d4..023c38c730c 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0534.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0534.md
@@ -1,8 +1,14 @@
+#### Note: this error code is no longer emitted by the compiler
+
+This is because it was too specific to the `inline` attribute.
+Similar diagnostics occur for other attributes too.
+The example here will now emit `E0805`
+
 The `inline` attribute was malformed.
 
 Erroneous code example:
 
-```compile_fail,E0534
+```compile_fail,E0805
 #[inline()] // error: expected one argument
 pub fn something() {}
 
diff --git a/compiler/rustc_error_codes/src/error_codes/E0535.md b/compiler/rustc_error_codes/src/error_codes/E0535.md
index 0cf3118b02c..93e2ba53826 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0535.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0535.md
@@ -1,8 +1,13 @@
-An unknown argument was given to the `inline` attribute.
+#### Note: this error code is no longer emitted by the compiler
+
+This is because it was too specific to the `inline` attribute.
+Similar diagnostics occur for other attributes too.
+The example here will now emit `E0539`
+
 
 Erroneous code example:
 
-```compile_fail,E0535
+```compile_fail,E0539
 #[inline(unknown)] // error: invalid argument
 pub fn something() {}
 
diff --git a/compiler/rustc_error_codes/src/error_codes/E0539.md b/compiler/rustc_error_codes/src/error_codes/E0539.md
index 6b2e23ba2d8..c76b60ac108 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0539.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0539.md
@@ -24,8 +24,7 @@ struct Stable;
 const fn stable_fn() {}
 ```
 
-Meta items are the key-value pairs inside of an attribute.
-To fix these issues you need to give required key-value pairs.
+To fix the above example, you can write the following:
 
 ```
 #![feature(staged_api)]
@@ -49,3 +48,29 @@ struct Stable;
 #[rustc_const_stable(feature = "stable_fn", since = "1.39.0")] // ok!
 const fn stable_fn() {}
 ```
+
+Several causes of this are,
+an attribute may have expected you to give a list but you gave a
+`name = value` pair:
+
+```compile_fail,E0539
+// wrong, should be `#[repr(C)]`
+#[repr = "C"]
+struct Foo {}
+```
+
+Or a `name = value` pair, but you gave a list:
+
+```compile_fail,E0539
+// wrong, should be `note = "reason"`
+#[deprecated(since = "1.0.0", note("reason"))]
+struct Foo {}
+```
+
+Or it expected some specific word but you gave an unexpected one:
+
+```compile_fail,E0539
+// should be `always` or `never`
+#[inline(maybe_if_you_feel_like_it)]
+fn foo() {}
+```
diff --git a/compiler/rustc_error_codes/src/error_codes/E0565.md b/compiler/rustc_error_codes/src/error_codes/E0565.md
index d5bba941c1d..34152eb7cfe 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0565.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0565.md
@@ -9,10 +9,9 @@ struct Repr {}
 fn main() {}
 ```
 
-Literals in attributes are new and largely unsupported in built-in attributes.
-Work to support literals where appropriate is ongoing. Try using an unquoted
-name instead:
-
+Not all attributes support literals in their input,
+and in some cases they expect an identifier instead.
+That would be the solution in the case of `repr`:
 ```
 #[repr(C)] // ok!
 struct Repr {}
diff --git a/compiler/rustc_error_codes/src/error_codes/E0805.md b/compiler/rustc_error_codes/src/error_codes/E0805.md
new file mode 100644
index 00000000000..b1ed3a11d48
--- /dev/null
+++ b/compiler/rustc_error_codes/src/error_codes/E0805.md
@@ -0,0 +1,26 @@
+An attribute was given an invalid number of arguments
+
+Erroneous code example:
+
+```compile_fail,E0805
+#[inline()] // error! should either have a single argument, or no parentheses
+fn foo() {}
+
+#[inline(always, never)] // error! should have only one argument, not two
+fn bar() {}
+```
+
+To fix this, either give the right number of arguments the attribute needs.
+In the case of inline, this could be none at all:
+
+```
+#[inline]
+fn foo() {}
+```
+
+or only one:
+
+```
+#[inline(always)]
+fn foo() {}
+```
diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs
index 2488d870899..6f5e4829802 100644
--- a/compiler/rustc_error_codes/src/lib.rs
+++ b/compiler/rustc_error_codes/src/lib.rs
@@ -547,6 +547,7 @@ E0801: 0801,
 E0802: 0802,
 E0803: 0803,
 E0804: 0804,
+E0805: 0805,
         );
     )
 }
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 0b8c68404f1..ae65cefda77 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -2626,6 +2626,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..b3096e46b09 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -282,11 +282,22 @@ 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
+            | sym::rustc_confusables
+            | sym::repr
+            | sym::deprecated
+    ) {
+        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 dddbf65db72..5ce803aa1f8 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -10,7 +10,7 @@ use std::collections::hash_map::Entry;
 
 use rustc_abi::{Align, ExternAbi, Size};
 use rustc_ast::{AttrStyle, LitKind, MetaItemInner, MetaItemKind, MetaItemLit, ast};
-use rustc_attr_data_structures::{AttributeKind, ReprAttr, find_attr};
+use rustc_attr_data_structures::{AttributeKind, InlineAttr, ReprAttr, find_attr};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{Applicability, DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey};
 use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
@@ -124,6 +124,10 @@ 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)
+                }
                 Attribute::Parsed(AttributeKind::AllowInternalUnstable(syms)) => self
                     .check_allow_internal_unstable(
                         hir_id,
@@ -158,7 +162,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         [sym::diagnostic, sym::on_unimplemented, ..] => {
                             self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target)
                         }
-                        [sym::inline, ..] => self.check_inline(hir_id, attr, span, target),
                         [sym::coverage, ..] => self.check_coverage(attr, span, target),
                         [sym::optimize, ..] => self.check_optimize(hir_id, attr, span, target),
                         [sym::no_sanitize, ..] => {
@@ -367,11 +370,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         self.check_rustc_force_inline(hir_id, attrs, span, target);
     }
 
-    fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) {
+    fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr_span: Span, sym: &str) {
         self.tcx.emit_node_span_lint(
             UNUSED_ATTRIBUTES,
             hir_id,
-            attr.span(),
+            attr_span,
             errors::IgnoredAttrWithMacro { sym },
         );
     }
@@ -431,7 +434,14 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if an `#[inline]` is applied to a function or a closure.
-    fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_inline(
+        &self,
+        hir_id: HirId,
+        attr_span: Span,
+        defn_span: Span,
+        kind: &InlineAttr,
+        target: Target,
+    ) {
         match target {
             Target::Fn
             | Target::Closure
@@ -440,7 +450,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 self.tcx.emit_node_span_lint(
                     UNUSED_ATTRIBUTES,
                     hir_id,
-                    attr.span(),
+                    attr_span,
                     errors::IgnoredInlineAttrFnProto,
                 )
             }
@@ -451,25 +461,22 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             Target::AssocConst => self.tcx.emit_node_span_lint(
                 UNUSED_ATTRIBUTES,
                 hir_id,
-                attr.span(),
+                attr_span,
                 errors::IgnoredInlineAttrConstants,
             ),
             // FIXME(#80564): Same for fields, arms, and macro defs
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "inline")
+                self.inline_attr_str_error_with_macro_def(hir_id, attr_span, "inline")
             }
             _ => {
-                self.dcx().emit_err(errors::InlineNotFnOrClosure {
-                    attr_span: attr.span(),
-                    defn_span: span,
-                });
+                self.dcx().emit_err(errors::InlineNotFnOrClosure { attr_span, defn_span });
             }
         }
 
         // `#[inline]` is ignored if the symbol must be codegened upstream because it's exported.
         if let Some(did) = hir_id.as_owner()
             && self.tcx.def_kind(did).has_codegen_attrs()
-            && !matches!(attr.meta_item_list().as_deref(), Some([item]) if item.has_name(sym::never))
+            && kind != &InlineAttr::Never
         {
             let attrs = self.tcx.codegen_fn_attrs(did);
             // Not checking naked as `#[inline]` is forbidden for naked functions anyways.
@@ -477,7 +484,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 self.tcx.emit_node_span_lint(
                     UNUSED_ATTRIBUTES,
                     hir_id,
-                    attr.span(),
+                    attr_span,
                     errors::InlineIgnoredForExported {},
                 );
             }
@@ -676,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
                         _ => {}
                     }
 
@@ -787,7 +804,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
                 for attr in attrs {
-                    self.inline_attr_str_error_with_macro_def(hir_id, attr, "track_caller");
+                    self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "track_caller");
                 }
             }
             _ => {
@@ -830,7 +847,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "non_exhaustive");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "non_exhaustive");
             }
             _ => {
                 self.dcx().emit_err(errors::NonExhaustiveWrongLocation {
@@ -850,7 +867,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "marker");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "marker");
             }
             _ => {
                 self.dcx().emit_err(errors::AttrShouldBeAppliedToTrait {
@@ -904,7 +921,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "target_feature");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "target_feature");
             }
             _ => {
                 self.dcx().emit_err(errors::AttrShouldBeAppliedToFn {
@@ -1619,7 +1636,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "cold");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "cold");
             }
             _ => {
                 // FIXME: #[cold] was previously allowed on non-functions and some crates used
@@ -1661,7 +1678,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_name");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "link_name");
             }
             _ => {
                 // FIXME: #[cold] was previously allowed on non-functions/statics and some crates
@@ -1695,7 +1712,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_link");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "no_link");
             }
             _ => {
                 self.dcx().emit_err(errors::NoLink { attr_span: attr.span(), span });
@@ -1717,7 +1734,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "export_name");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "export_name");
             }
             _ => {
                 self.dcx().emit_err(errors::ExportName { attr_span: attr.span(), span });
@@ -1891,7 +1908,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_section");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "link_section");
             }
             _ => {
                 // FIXME: #[link_section] was previously allowed on non-functions/statics and some
@@ -1916,7 +1933,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_mangle");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "no_mangle");
             }
             // FIXME: #[no_mangle] was previously allowed on non-functions/statics, this should be an error
             // The error should specify that the item that is wrong is specifically a *foreign* fn/static
@@ -2263,9 +2280,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // `#[allow_internal_unstable]` attribute with just a lint, because we previously
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
-            Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr, "allow_internal_unstable")
-            }
+            Target::Field | Target::Arm | Target::MacroDef => self
+                .inline_attr_str_error_with_macro_def(
+                    hir_id,
+                    attr.span(),
+                    "allow_internal_unstable",
+                ),
             _ => {
                 self.tcx
                     .dcx()
@@ -2638,8 +2658,10 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         span: Span,
         target: Target,
     ) {
-        let force_inline_attr = attrs.iter().find(|attr| attr.has_name(sym::rustc_force_inline));
-        match (target, force_inline_attr) {
+        match (
+            target,
+            find_attr!(attrs, AttributeKind::Inline(InlineAttr::Force { attr_span, .. }, _) => *attr_span),
+        ) {
             (Target::Closure, None) => {
                 let is_coro = matches!(
                     self.tcx.hir_expect_expr(hir_id).kind,
@@ -2651,20 +2673,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 );
                 let parent_did = self.tcx.hir_get_parent_item(hir_id).to_def_id();
                 let parent_span = self.tcx.def_span(parent_did);
-                let parent_force_inline_attr =
-                    self.tcx.get_attr(parent_did, sym::rustc_force_inline);
-                if let Some(attr) = parent_force_inline_attr
-                    && is_coro
+
+                if let Some(attr_span) = find_attr!(
+                    self.tcx.get_all_attrs(parent_did),
+                    AttributeKind::Inline(InlineAttr::Force { attr_span, .. }, _) => *attr_span
+                ) && is_coro
                 {
-                    self.dcx().emit_err(errors::RustcForceInlineCoro {
-                        attr_span: attr.span(),
-                        span: parent_span,
-                    });
+                    self.dcx()
+                        .emit_err(errors::RustcForceInlineCoro { attr_span, span: parent_span });
                 }
             }
             (Target::Fn, _) => (),
-            (_, Some(attr)) => {
-                self.dcx().emit_err(errors::RustcForceInline { attr_span: attr.span(), span });
+            (_, Some(attr_span)) => {
+                self.dcx().emit_err(errors::RustcForceInline { attr_span, span });
             }
             (_, None) => (),
         }
@@ -2885,10 +2906,9 @@ 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());
 
-    for attr in attrs {
-        if attr.has_name(sym::inline) {
-            tcx.dcx().emit_err(errors::NonExportedMacroInvalidAttrs { attr_span: attr.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 });
     }
 }
 
@@ -2908,6 +2928,7 @@ pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers { check_mod_attrs, ..*providers };
 }
 
+// FIXME(jdonszelmann): remove, check during parsing
 fn check_duplicates(
     tcx: TyCtxt<'_>,
     attr: &Attribute,
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index 2c94b8f914f..1f93895ae07 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -37,8 +37,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
 // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
 // are deliberately not in a doc comment, because they need not be in public docs.)
 //
-// Latest feature: rustdoc JSON: Don't apply #[repr] privacy heuristics
-pub const FORMAT_VERSION: u32 = 46;
+// Latest feature: Pretty printing of inline attributes changed
+pub const FORMAT_VERSION: u32 = 48;
 
 /// The root of the emitted JSON blob.
 ///
diff --git a/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs b/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs
index cb63fadb4e2..58e51128a0d 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/inline_always.rs
@@ -1,29 +1,22 @@
 use super::INLINE_ALWAYS;
-use super::utils::is_word;
 use clippy_utils::diagnostics::span_lint;
+use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr};
 use rustc_hir::Attribute;
 use rustc_lint::LateContext;
 use rustc_span::symbol::Symbol;
-use rustc_span::{Span, sym};
+use rustc_span::Span;
 
 pub(super) fn check(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
     if span.from_expansion() {
         return;
     }
 
-    for attr in attrs {
-        if let Some(values) = attr.meta_item_list() {
-            if values.len() != 1 || !attr.has_name(sym::inline) {
-                continue;
-            }
-            if is_word(&values[0], sym::always) {
-                span_lint(
-                    cx,
-                    INLINE_ALWAYS,
-                    attr.span(),
-                    format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
-                );
-            }
-        }
+    if let Some(span) = find_attr!(attrs, AttributeKind::Inline(InlineAttr::Always, span) => *span) {
+        span_lint(
+            cx,
+            INLINE_ALWAYS,
+            span,
+            format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
+        );
     }
 }
diff --git a/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs
index da5ca5e6772..617c006795b 100644
--- a/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs
+++ b/src/tools/clippy/clippy_lints/src/inline_fn_without_body.rs
@@ -1,10 +1,10 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::sugg::DiagExt;
+use rustc_attr_data_structures::{find_attr, AttributeKind};
 use rustc_errors::Applicability;
 use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::declare_lint_pass;
-use rustc_span::sym;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -32,15 +32,19 @@ declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]);
 impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
     fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
         if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind
-            && let Some(attr) = cx.tcx.hir_attrs(item.hir_id()).iter().find(|a| a.has_name(sym::inline))
+            && let Some(attr_span) = find_attr!(cx
+                .tcx
+                .hir_attrs(item.hir_id()),
+                AttributeKind::Inline(_, span) => *span
+        )
         {
             span_lint_and_then(
                 cx,
                 INLINE_FN_WITHOUT_BODY,
-                attr.span(),
+                attr_span,
                 format!("use of `#[inline]` on trait method `{}` which has no body", item.ident),
                 |diag| {
-                    diag.suggest_remove_item(cx, attr.span(), "remove", Applicability::MachineApplicable);
+                    diag.suggest_remove_item(cx, attr_span, "remove", Applicability::MachineApplicable);
                 },
             );
         }
diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs
index 1f613171b46..f835bbb7c56 100644
--- a/src/tools/clippy/clippy_lints/src/missing_inline.rs
+++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs
@@ -1,10 +1,11 @@
 use clippy_utils::diagnostics::span_lint;
+use rustc_attr_data_structures::{find_attr, AttributeKind};
 use rustc_hir as hir;
 use rustc_hir::Attribute;
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::ty::AssocItemContainer;
 use rustc_session::declare_lint_pass;
-use rustc_span::{Span, sym};
+use rustc_span::Span;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -64,8 +65,7 @@ declare_clippy_lint! {
 }
 
 fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[Attribute], sp: Span, desc: &'static str) {
-    let has_inline = attrs.iter().any(|a| a.has_name(sym::inline));
-    if !has_inline {
+    if !find_attr!(attrs, AttributeKind::Inline(..)) {
         span_lint(
             cx,
             MISSING_INLINE_IN_PUBLIC_ITEMS,
diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs
index dadf49b64e5..e18bdfb34ac 100644
--- a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs
+++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs
@@ -3,10 +3,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet;
 use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
 use clippy_utils::{is_self, is_self_ty};
+use rustc_attr_data_structures::{find_attr, AttributeKind, InlineAttr};
+use rustc_data_structures::fx::FxHashSet;
 use core::ops::ControlFlow;
 use rustc_abi::ExternAbi;
-use rustc_ast::attr;
-use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::intravisit::FnKind;
@@ -270,11 +270,13 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
                     return;
                 }
                 let attrs = cx.tcx.hir_attrs(hir_id);
+                if find_attr!(attrs, AttributeKind::Inline(InlineAttr::Always, _)) {
+                    return;
+                }
+
                 for a in attrs {
-                    if let Some(meta_items) = a.meta_item_list()
-                        && (a.has_name(sym::proc_macro_derive)
-                            || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always)))
-                    {
+                    // FIXME(jdonszelmann): make part of the find_attr above
+                    if a.has_name(sym::proc_macro_derive) {
                         return;
                     }
                 }
diff --git a/tests/rustdoc-json/attrs/inline.rs b/tests/rustdoc-json/attrs/inline.rs
index 74f5f36f03f..b9ea6ab1d10 100644
--- a/tests/rustdoc-json/attrs/inline.rs
+++ b/tests/rustdoc-json/attrs/inline.rs
@@ -1,11 +1,11 @@
-//@ is "$.index[?(@.name=='just_inline')].attrs" '["#[inline]"]'
+//@ is "$.index[?(@.name=='just_inline')].attrs" '["#[attr = Inline(Hint)]"]'
 #[inline]
 pub fn just_inline() {}
 
-//@ is "$.index[?(@.name=='inline_always')].attrs" '["#[inline(always)]"]'
+//@ is "$.index[?(@.name=='inline_always')].attrs" '["#[attr = Inline(Always)]"]'
 #[inline(always)]
 pub fn inline_always() {}
 
-//@ is "$.index[?(@.name=='inline_never')].attrs" '["#[inline(never)]"]'
+//@ is "$.index[?(@.name=='inline_never')].attrs" '["#[attr = Inline(Never)]"]'
 #[inline(never)]
 pub fn inline_never() {}
diff --git a/tests/ui/attributes/multiple-invalid.stderr b/tests/ui/attributes/multiple-invalid.stderr
index a8dba0ba37d..f4f7dd7c4f1 100644
--- a/tests/ui/attributes/multiple-invalid.stderr
+++ b/tests/ui/attributes/multiple-invalid.stderr
@@ -1,12 +1,3 @@
-error[E0518]: attribute should be applied to function or closure
-  --> $DIR/multiple-invalid.rs:4:1
-   |
-LL | #[inline]
-   | ^^^^^^^^^
-...
-LL | const FOO: u8 = 0;
-   | ------------------ not a function or closure
-
 error: attribute should be applied to a function definition
   --> $DIR/multiple-invalid.rs:6:1
    |
@@ -16,6 +7,15 @@ LL |
 LL | const FOO: u8 = 0;
    | ------------------ not a function definition
 
+error[E0518]: attribute should be applied to function or closure
+  --> $DIR/multiple-invalid.rs:4:1
+   |
+LL | #[inline]
+   | ^^^^^^^^^
+...
+LL | const FOO: u8 = 0;
+   | ------------------ not a function or closure
+
 error: aborting due to 2 previous errors
 
 For more information about this error, try `rustc --explain E0518`.
diff --git a/tests/ui/attributes/rustc_confusables.rs b/tests/ui/attributes/rustc_confusables.rs
index 93d9a7d572c..a8095936cff 100644
--- a/tests/ui/attributes/rustc_confusables.rs
+++ b/tests/ui/attributes/rustc_confusables.rs
@@ -37,8 +37,8 @@ impl Bar {
     fn qux() {}
 
     #[rustc_confusables(invalid_meta_item)]
-    //~^ ERROR expected a quoted string literal
-    //~| HELP consider surrounding this with quotes
+    //~^ ERROR malformed `rustc_confusables` attribute input [E0539]
+    //~| HELP must be of the form
     fn quux() {}
 }
 
diff --git a/tests/ui/attributes/rustc_confusables.stderr b/tests/ui/attributes/rustc_confusables.stderr
index aba384ff8ac..3ed4efeb4db 100644
--- a/tests/ui/attributes/rustc_confusables.stderr
+++ b/tests/ui/attributes/rustc_confusables.stderr
@@ -1,25 +1,26 @@
-error: malformed `rustc_confusables` attribute input
-  --> $DIR/rustc_confusables.rs:34:5
-   |
-LL |     #[rustc_confusables]
-   |     ^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]`
-
 error: expected at least one confusable name
   --> $DIR/rustc_confusables.rs:30:5
    |
 LL |     #[rustc_confusables()]
    |     ^^^^^^^^^^^^^^^^^^^^^^
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/rustc_confusables.rs:39:25
-   |
-LL |     #[rustc_confusables(invalid_meta_item)]
-   |                         ^^^^^^^^^^^^^^^^^
+error[E0539]: malformed `rustc_confusables` attribute input
+  --> $DIR/rustc_confusables.rs:34:5
    |
-help: consider surrounding this with quotes
+LL |     #[rustc_confusables]
+   |     ^^^^^^^^^^^^^^^^^^^^
+   |     |
+   |     expected this to be a list
+   |     help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]`
+
+error[E0539]: malformed `rustc_confusables` attribute input
+  --> $DIR/rustc_confusables.rs:39:5
    |
-LL |     #[rustc_confusables("invalid_meta_item")]
-   |                         +                 +
+LL |     #[rustc_confusables(invalid_meta_item)]
+   |     ^^^^^^^^^^^^^^^^^^^^-----------------^^
+   |     |                   |
+   |     |                   expected a string literal here
+   |     help: must be of the form: `#[rustc_confusables("name1", "name2", ...)]`
 
 error: attribute should be applied to an inherent method
   --> $DIR/rustc_confusables.rs:45:1
diff --git a/tests/ui/deprecation/deprecation-sanity.rs b/tests/ui/deprecation/deprecation-sanity.rs
index d5b149b18ed..80198ab8196 100644
--- a/tests/ui/deprecation/deprecation-sanity.rs
+++ b/tests/ui/deprecation/deprecation-sanity.rs
@@ -4,22 +4,22 @@ mod bogus_attribute_types_1 {
     #[deprecated(since = "a", note = "a", reason)] //~ ERROR unknown meta item 'reason'
     fn f1() { }
 
-    #[deprecated(since = "a", note)] //~ ERROR expected a quoted string literal
+    #[deprecated(since = "a", note)] //~ ERROR malformed `deprecated` attribute input [E0539]
     fn f2() { }
 
-    #[deprecated(since, note = "a")] //~ ERROR expected a quoted string literal
+    #[deprecated(since, note = "a")] //~ ERROR malformed `deprecated` attribute input [E0539]
     fn f3() { }
 
-    #[deprecated(since = "a", note(b))] //~ ERROR expected a quoted string literal
+    #[deprecated(since = "a", note(b))] //~ ERROR malformed `deprecated` attribute input [E0539]
     fn f5() { }
 
-    #[deprecated(since(b), note = "a")] //~ ERROR expected a quoted string literal
+    #[deprecated(since(b), note = "a")] //~ ERROR malformed `deprecated` attribute input [E0539]
     fn f6() { }
 
-    #[deprecated(note = b"test")] //~ ERROR literal in `deprecated` value must be a string
+    #[deprecated(note = b"test")] //~ ERROR malformed `deprecated` attribute input [E0539]
     fn f7() { }
 
-    #[deprecated("test")] //~ ERROR item in `deprecated` must be a key/value pair
+    #[deprecated("test")] //~ ERROR malformed `deprecated` attribute input [E0565]
     fn f8() { }
 }
 
@@ -27,7 +27,7 @@ mod bogus_attribute_types_1 {
 #[deprecated(since = "a", note = "b")] //~ ERROR multiple `deprecated` attributes
 fn multiple1() { }
 
-#[deprecated(since = "a", since = "b", note = "c")] //~ ERROR multiple 'since' items
+#[deprecated(since = "a", since = "b", note = "c")] //~ ERROR malformed `deprecated` attribute input [E0538]
 fn f1() { }
 
 struct X;
diff --git a/tests/ui/deprecation/deprecation-sanity.stderr b/tests/ui/deprecation/deprecation-sanity.stderr
index 53047d40cb2..f1b4697485c 100644
--- a/tests/ui/deprecation/deprecation-sanity.stderr
+++ b/tests/ui/deprecation/deprecation-sanity.stderr
@@ -4,43 +4,115 @@ error[E0541]: unknown meta item 'reason'
 LL |     #[deprecated(since = "a", note = "a", reason)]
    |                                           ^^^^^^ expected one of `since`, `note`
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/deprecation-sanity.rs:7:31
+error[E0539]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:7:5
    |
 LL |     #[deprecated(since = "a", note)]
-   |                               ^^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^----^^
+   |                               |
+   |                               expected this to be of the form `note = "..."`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[deprecated(since = "a", note)]
+LL +     #[deprecated = "reason"]
+   |
+LL -     #[deprecated(since = "a", note)]
+LL +     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL -     #[deprecated(since = "a", note)]
+LL +     #[deprecated]
+   |
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/deprecation-sanity.rs:10:18
+error[E0539]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:10:5
    |
 LL |     #[deprecated(since, note = "a")]
-   |                  ^^^^^
+   |     ^^^^^^^^^^^^^-----^^^^^^^^^^^^^^
+   |                  |
+   |                  expected this to be of the form `since = "..."`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[deprecated(since, note = "a")]
+LL +     #[deprecated = "reason"]
+   |
+LL -     #[deprecated(since, note = "a")]
+LL +     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL -     #[deprecated(since, note = "a")]
+LL +     #[deprecated]
+   |
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/deprecation-sanity.rs:13:31
+error[E0539]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:13:5
    |
 LL |     #[deprecated(since = "a", note(b))]
-   |                               ^^^^^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^-------^^
+   |                               |
+   |                               expected this to be of the form `note = "..."`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[deprecated(since = "a", note(b))]
+LL +     #[deprecated = "reason"]
+   |
+LL -     #[deprecated(since = "a", note(b))]
+LL +     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL -     #[deprecated(since = "a", note(b))]
+LL +     #[deprecated]
+   |
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/deprecation-sanity.rs:16:18
+error[E0539]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:16:5
    |
 LL |     #[deprecated(since(b), note = "a")]
-   |                  ^^^^^^^^
+   |     ^^^^^^^^^^^^^--------^^^^^^^^^^^^^^
+   |                  |
+   |                  expected this to be of the form `since = "..."`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[deprecated(since(b), note = "a")]
+LL +     #[deprecated = "reason"]
+   |
+LL -     #[deprecated(since(b), note = "a")]
+LL +     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL -     #[deprecated(since(b), note = "a")]
+LL +     #[deprecated]
+   |
 
-error[E0565]: literal in `deprecated` value must be a string
-  --> $DIR/deprecation-sanity.rs:19:25
+error[E0539]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:19:5
    |
 LL |     #[deprecated(note = b"test")]
-   |                         -^^^^^^
+   |     ^^^^^^^^^^^^^^^^^^^^-^^^^^^^^
    |                         |
    |                         help: consider removing the prefix
+   |
+   = note: expected a normal string literal, not a byte string literal
 
-error[E0565]: item in `deprecated` must be a key/value pair
-  --> $DIR/deprecation-sanity.rs:22:18
+error[E0565]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:22:5
    |
 LL |     #[deprecated("test")]
-   |                  ^^^^^^
+   |     ^^^^^^^^^^^^^------^^
+   |                  |
+   |                  didn't expect a literal here
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[deprecated("test")]
+LL +     #[deprecated = "reason"]
+   |
+LL -     #[deprecated("test")]
+LL +     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL -     #[deprecated("test")]
+LL +     #[deprecated]
+   |
 
 error: multiple `deprecated` attributes
   --> $DIR/deprecation-sanity.rs:27:1
@@ -54,11 +126,25 @@ note: attribute also specified here
 LL | #[deprecated(since = "a", note = "b")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error[E0538]: multiple 'since' items
-  --> $DIR/deprecation-sanity.rs:30:27
+error[E0538]: malformed `deprecated` attribute input
+  --> $DIR/deprecation-sanity.rs:30:1
    |
 LL | #[deprecated(since = "a", since = "b", note = "c")]
-   |                           ^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^^^^^^^^^^^
+   |                           |
+   |                           found `since` used as a key more than once
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL - #[deprecated(since = "a", since = "b", note = "c")]
+LL + #[deprecated = "reason"]
+   |
+LL - #[deprecated(since = "a", since = "b", note = "c")]
+LL + #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL - #[deprecated(since = "a", since = "b", note = "c")]
+LL + #[deprecated]
+   |
 
 error: this `#[deprecated]` annotation has no effect
   --> $DIR/deprecation-sanity.rs:35:1
diff --git a/tests/ui/deprecation/invalid-literal.stderr b/tests/ui/deprecation/invalid-literal.stderr
index cbe1fcca023..6f25aebc315 100644
--- a/tests/ui/deprecation/invalid-literal.stderr
+++ b/tests/ui/deprecation/invalid-literal.stderr
@@ -1,20 +1,13 @@
-error: malformed `deprecated` attribute input
+error[E0539]: malformed `deprecated` attribute input
   --> $DIR/invalid-literal.rs:1:1
    |
 LL | #[deprecated = b"test"]
-   | ^^^^^^^^^^^^^^^^^^^^^^^
-   |
-help: the following are the possible correct uses
-   |
-LL - #[deprecated = b"test"]
-LL + #[deprecated = "reason"]
-   |
-LL - #[deprecated = b"test"]
-LL + #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
-   |
-LL - #[deprecated = b"test"]
-LL + #[deprecated]
+   | ^^^^^^^^^^^^^^^-^^^^^^^
+   |                |
+   |                help: consider removing the prefix
    |
+   = note: expected a normal string literal, not a byte string literal
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0539`.
diff --git a/tests/ui/error-codes/E0534.rs b/tests/ui/error-codes/E0534.rs
deleted file mode 100644
index a4242499419..00000000000
--- a/tests/ui/error-codes/E0534.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-#[inline()] //~ ERROR E0534
-pub fn something() {}
-
-fn main() {
-    something();
-}
diff --git a/tests/ui/error-codes/E0534.stderr b/tests/ui/error-codes/E0534.stderr
deleted file mode 100644
index 6983de7ab69..00000000000
--- a/tests/ui/error-codes/E0534.stderr
+++ /dev/null
@@ -1,9 +0,0 @@
-error[E0534]: expected one argument
-  --> $DIR/E0534.rs:1:1
-   |
-LL | #[inline()]
-   | ^^^^^^^^^^^
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0534`.
diff --git a/tests/ui/error-codes/E0565-2.rs b/tests/ui/error-codes/E0539.rs
index 0319ecb11f9..0e2c537f473 100644
--- a/tests/ui/error-codes/E0565-2.rs
+++ b/tests/ui/error-codes/E0539.rs
@@ -1,5 +1,5 @@
 // repr currently doesn't support literals
-#[deprecated(since = b"1.29", note = "hi")] //~ ERROR E0565
+#[deprecated(since = b"1.29", note = "hi")] //~ ERROR E0539
 struct A {  }
 
 fn main() {  }
diff --git a/tests/ui/error-codes/E0539.stderr b/tests/ui/error-codes/E0539.stderr
new file mode 100644
index 00000000000..18ed1c23b40
--- /dev/null
+++ b/tests/ui/error-codes/E0539.stderr
@@ -0,0 +1,13 @@
+error[E0539]: malformed `deprecated` attribute input
+  --> $DIR/E0539.rs:2:1
+   |
+LL | #[deprecated(since = b"1.29", note = "hi")]
+   | ^^^^^^^^^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^^^^^^
+   |                      |
+   |                      help: consider removing the prefix
+   |
+   = note: expected a normal string literal, not a byte string literal
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0539`.
diff --git a/tests/ui/error-codes/E0540.rs b/tests/ui/error-codes/E0540.rs
new file mode 100644
index 00000000000..db29e6801f5
--- /dev/null
+++ b/tests/ui/error-codes/E0540.rs
@@ -0,0 +1,6 @@
+#[inline()] //~ ERROR malformed `inline` attribute input
+pub fn something() {}
+
+fn main() {
+    something();
+}
diff --git a/tests/ui/error-codes/E0540.stderr b/tests/ui/error-codes/E0540.stderr
new file mode 100644
index 00000000000..3e5f408feb5
--- /dev/null
+++ b/tests/ui/error-codes/E0540.stderr
@@ -0,0 +1,19 @@
+error[E0805]: malformed `inline` attribute input
+  --> $DIR/E0540.rs:1:1
+   |
+LL | #[inline()]
+   | ^^^^^^^^--^
+   |         |
+   |         expected a single argument here
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL | #[inline(always|never)]
+   |          ++++++++++++
+LL - #[inline()]
+LL + #[inline]
+   |
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0805`.
diff --git a/tests/ui/error-codes/E0565-1.stderr b/tests/ui/error-codes/E0565-1.stderr
index 806eed2a632..6277e6400d7 100644
--- a/tests/ui/error-codes/E0565-1.stderr
+++ b/tests/ui/error-codes/E0565-1.stderr
@@ -1,8 +1,22 @@
-error[E0565]: item in `deprecated` must be a key/value pair
-  --> $DIR/E0565-1.rs:2:14
+error[E0565]: malformed `deprecated` attribute input
+  --> $DIR/E0565-1.rs:2:1
    |
 LL | #[deprecated("since")]
-   |              ^^^^^^^
+   | ^^^^^^^^^^^^^-------^^
+   |              |
+   |              didn't expect a literal here
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL - #[deprecated("since")]
+LL + #[deprecated = "reason"]
+   |
+LL - #[deprecated("since")]
+LL + #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
+   |
+LL - #[deprecated("since")]
+LL + #[deprecated]
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/error-codes/E0565-2.stderr b/tests/ui/error-codes/E0565-2.stderr
deleted file mode 100644
index 42199351c3d..00000000000
--- a/tests/ui/error-codes/E0565-2.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0565]: literal in `deprecated` value must be a string
-  --> $DIR/E0565-2.rs:2:22
-   |
-LL | #[deprecated(since = b"1.29", note = "hi")]
-   |                      -^^^^^^
-   |                      |
-   |                      help: consider removing the prefix
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0565`.
diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr
index bdca6163473..1620bf72922 100644
--- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr
+++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs-error.stderr
@@ -8,16 +8,6 @@ LL | #![rustc_main]
    = note: the `#[rustc_main]` attribute is an internal implementation detail that will never be stable
    = note: the `#[rustc_main]` attribute is used internally to specify test entry point function
 
-error: valid forms for the attribute are `#[inline]` and `#[inline(always|never)]`
-  --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:46:5
-   |
-LL |     #[inline = "2100"] fn f() { }
-   |     ^^^^^^^^^^^^^^^^^^
-   |
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-   = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
-   = note: `#[deny(ill_formed_attribute_input)]` on by default
-
 error[E0518]: attribute should be applied to function or closure
   --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:32:1
    |
@@ -314,6 +304,16 @@ error[E0517]: attribute should be applied to a struct, enum, or union
 LL |     #[repr(Rust)] impl S { }
    |            ^^^^   ---------- not a struct, enum, or union
 
+error: valid forms for the attribute are `#[inline(always|never)]` and `#[inline]`
+  --> $DIR/issue-43106-gating-of-builtin-attrs-error.rs:46:5
+   |
+LL |     #[inline = "2100"] fn f() { }
+   |     ^^^^^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
+   = note: `#[deny(ill_formed_attribute_input)]` on by default
+
 error: aborting due to 38 previous errors
 
 Some errors have detailed explanations: E0517, E0518, E0658.
diff --git a/tests/ui/force-inlining/invalid.rs b/tests/ui/force-inlining/invalid.rs
index 7574078b245..e9f5712413e 100644
--- a/tests/ui/force-inlining/invalid.rs
+++ b/tests/ui/force-inlining/invalid.rs
@@ -9,7 +9,6 @@
 // Test that invalid force inlining attributes error as expected.
 
 #[rustc_force_inline("foo")]
-//~^ ERROR malformed `rustc_force_inline` attribute input
 pub fn forced1() {
 }
 
diff --git a/tests/ui/force-inlining/invalid.stderr b/tests/ui/force-inlining/invalid.stderr
index 92b3c314bad..3b3da00ae88 100644
--- a/tests/ui/force-inlining/invalid.stderr
+++ b/tests/ui/force-inlining/invalid.stderr
@@ -1,71 +1,71 @@
-error: malformed `rustc_force_inline` attribute input
-  --> $DIR/invalid.rs:11:1
-   |
-LL | #[rustc_force_inline("foo")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-help: the following are the possible correct uses
-   |
-LL - #[rustc_force_inline("foo")]
-LL + #[rustc_force_inline = "reason"]
-   |
-LL - #[rustc_force_inline("foo")]
-LL + #[rustc_force_inline]
+error: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters
+  --> $DIR/invalid.rs:132:11
    |
+LL | fn barqux(#[rustc_force_inline] _x: u32) {}
+   |           ^^^^^^^^^^^^^^^^^^^^^
 
-error: malformed `rustc_force_inline` attribute input
-  --> $DIR/invalid.rs:16:1
+error[E0805]: malformed `rustc_force_inline` attribute input
+  --> $DIR/invalid.rs:15:1
    |
 LL | #[rustc_force_inline(bar, baz)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^----------^
+   |                     |
+   |                     expected a single argument here
    |
-help: the following are the possible correct uses
+help: try changing it to one of the following valid forms of the attribute
    |
 LL - #[rustc_force_inline(bar, baz)]
 LL + #[rustc_force_inline = "reason"]
    |
 LL - #[rustc_force_inline(bar, baz)]
+LL + #[rustc_force_inline(reason)]
+   |
+LL - #[rustc_force_inline(bar, baz)]
 LL + #[rustc_force_inline]
    |
 
-error: malformed `rustc_force_inline` attribute input
-  --> $DIR/invalid.rs:21:1
+error[E0539]: malformed `rustc_force_inline` attribute input
+  --> $DIR/invalid.rs:20:1
    |
 LL | #[rustc_force_inline(2)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^^-^^
+   |                      |
+   |                      expected a string literal here
    |
-help: the following are the possible correct uses
+help: try changing it to one of the following valid forms of the attribute
    |
 LL - #[rustc_force_inline(2)]
 LL + #[rustc_force_inline = "reason"]
    |
 LL - #[rustc_force_inline(2)]
+LL + #[rustc_force_inline(reason)]
+   |
+LL - #[rustc_force_inline(2)]
 LL + #[rustc_force_inline]
    |
 
-error: malformed `rustc_force_inline` attribute input
-  --> $DIR/invalid.rs:26:1
+error[E0539]: malformed `rustc_force_inline` attribute input
+  --> $DIR/invalid.rs:25:1
    |
 LL | #[rustc_force_inline = 2]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^^^^-^
+   |                        |
+   |                        expected a string literal here
    |
-help: the following are the possible correct uses
+help: try changing it to one of the following valid forms of the attribute
    |
 LL - #[rustc_force_inline = 2]
 LL + #[rustc_force_inline = "reason"]
    |
 LL - #[rustc_force_inline = 2]
-LL + #[rustc_force_inline]
+LL + #[rustc_force_inline(reason)]
    |
-
-error: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters
-  --> $DIR/invalid.rs:133:11
+LL - #[rustc_force_inline = 2]
+LL + #[rustc_force_inline]
    |
-LL | fn barqux(#[rustc_force_inline] _x: u32) {}
-   |           ^^^^^^^^^^^^^^^^^^^^^
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:31:1
+  --> $DIR/invalid.rs:30:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -74,7 +74,7 @@ LL | extern crate std as other_std;
    | ------------------------------ not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:35:1
+  --> $DIR/invalid.rs:34:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -83,7 +83,7 @@ LL | use std::collections::HashMap;
    | ------------------------------ not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:39:1
+  --> $DIR/invalid.rs:38:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -92,7 +92,7 @@ LL | static _FOO: &'static str = "FOO";
    | ---------------------------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:43:1
+  --> $DIR/invalid.rs:42:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -101,7 +101,7 @@ LL | const _BAR: u32 = 3;
    | -------------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:47:1
+  --> $DIR/invalid.rs:46:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -110,7 +110,7 @@ LL | mod foo { }
    | ----------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:51:1
+  --> $DIR/invalid.rs:50:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -125,7 +125,7 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:67:1
+  --> $DIR/invalid.rs:66:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -134,7 +134,7 @@ LL | type Foo = u32;
    | --------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:71:1
+  --> $DIR/invalid.rs:70:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -147,13 +147,13 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:73:10
+  --> $DIR/invalid.rs:72:10
    |
 LL | enum Bar<#[rustc_force_inline] T> {
    |          ^^^^^^^^^^^^^^^^^^^^^ - not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:75:5
+  --> $DIR/invalid.rs:74:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -162,7 +162,7 @@ LL |     Baz(std::marker::PhantomData<T>),
    |     -------------------------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:80:1
+  --> $DIR/invalid.rs:79:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -175,7 +175,7 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:83:5
+  --> $DIR/invalid.rs:82:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -184,7 +184,7 @@ LL |     field: u32,
    |     ---------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:88:1
+  --> $DIR/invalid.rs:87:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -196,7 +196,7 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:95:1
+  --> $DIR/invalid.rs:94:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -211,7 +211,7 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:110:1
+  --> $DIR/invalid.rs:109:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -220,7 +220,7 @@ LL | trait FooQux = FooBaz;
    | ---------------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:114:1
+  --> $DIR/invalid.rs:113:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -233,7 +233,7 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:122:1
+  --> $DIR/invalid.rs:121:1
    |
 LL |   #[rustc_force_inline]
    |   ^^^^^^^^^^^^^^^^^^^^^
@@ -245,7 +245,7 @@ LL | | }
    | |_- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:129:1
+  --> $DIR/invalid.rs:128:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -254,7 +254,7 @@ LL | macro_rules! barqux { ($foo:tt) => { $foo }; }
    | ---------------------------------------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:133:11
+  --> $DIR/invalid.rs:132:11
    |
 LL | fn barqux(#[rustc_force_inline] _x: u32) {}
    |           ^^^^^^^^^^^^^^^^^^^^^--------
@@ -262,7 +262,7 @@ LL | fn barqux(#[rustc_force_inline] _x: u32) {}
    |           not a function definition
 
 error: attribute cannot be applied to a `async`, `gen` or `async gen` function
-  --> $DIR/invalid.rs:137:1
+  --> $DIR/invalid.rs:136:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -271,7 +271,7 @@ LL | async fn async_foo() {}
    | -------------------- `async`, `gen` or `async gen` function
 
 error: attribute cannot be applied to a `async`, `gen` or `async gen` function
-  --> $DIR/invalid.rs:141:1
+  --> $DIR/invalid.rs:140:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -280,7 +280,7 @@ LL | gen fn gen_foo() {}
    | ---------------- `async`, `gen` or `async gen` function
 
 error: attribute cannot be applied to a `async`, `gen` or `async gen` function
-  --> $DIR/invalid.rs:145:1
+  --> $DIR/invalid.rs:144:1
    |
 LL | #[rustc_force_inline]
    | ^^^^^^^^^^^^^^^^^^^^^
@@ -289,19 +289,19 @@ LL | async gen fn async_gen_foo() {}
    | ---------------------------- `async`, `gen` or `async gen` function
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:150:14
+  --> $DIR/invalid.rs:149:14
    |
 LL |     let _x = #[rustc_force_inline] || { };
    |              ^^^^^^^^^^^^^^^^^^^^^ ------ not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:152:14
+  --> $DIR/invalid.rs:151:14
    |
 LL |     let _y = #[rustc_force_inline] 3 + 4;
    |              ^^^^^^^^^^^^^^^^^^^^^ - not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:154:5
+  --> $DIR/invalid.rs:153:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -310,7 +310,7 @@ LL |     let _z = 3;
    |     ----------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:159:9
+  --> $DIR/invalid.rs:158:9
    |
 LL |         #[rustc_force_inline]
    |         ^^^^^^^^^^^^^^^^^^^^^
@@ -319,7 +319,7 @@ LL |         1 => (),
    |         ------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:98:5
+  --> $DIR/invalid.rs:97:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -328,7 +328,7 @@ LL |     type Foo;
    |     --------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:101:5
+  --> $DIR/invalid.rs:100:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -337,7 +337,7 @@ LL |     const Bar: i32;
    |     --------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:105:5
+  --> $DIR/invalid.rs:104:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -346,7 +346,7 @@ LL |     fn foo() {}
    |     ----------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:117:5
+  --> $DIR/invalid.rs:116:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -355,7 +355,7 @@ LL |     fn foo() {}
    |     ----------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:54:5
+  --> $DIR/invalid.rs:53:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -364,7 +364,7 @@ LL |     static X: &'static u32;
    |     ----------------------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:58:5
+  --> $DIR/invalid.rs:57:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -373,7 +373,7 @@ LL |     type Y;
    |     ------- not a function definition
 
 error: attribute should be applied to a function
-  --> $DIR/invalid.rs:62:5
+  --> $DIR/invalid.rs:61:5
    |
 LL |     #[rustc_force_inline]
    |     ^^^^^^^^^^^^^^^^^^^^^
@@ -381,5 +381,7 @@ LL |
 LL |     fn foo();
    |     --------- not a function definition
 
-error: aborting due to 38 previous errors
+error: aborting due to 37 previous errors
 
+Some errors have detailed explanations: E0539, E0805.
+For more information about an error, try `rustc --explain E0539`.
diff --git a/tests/ui/invalid/invalid-inline.rs b/tests/ui/invalid/invalid-inline.rs
index 2501b1e23f2..6735e1d814d 100644
--- a/tests/ui/invalid/invalid-inline.rs
+++ b/tests/ui/invalid/invalid-inline.rs
@@ -1,10 +1,10 @@
 #![allow(dead_code)]
 
-#[inline(please,no)] //~ ERROR expected one argument
+#[inline(please,no)] //~ ERROR malformed `inline` attribute
 fn a() {
 }
 
-#[inline()] //~ ERROR expected one argument
+#[inline()] //~ ERROR malformed `inline` attribute
 fn b() {
 }
 
diff --git a/tests/ui/invalid/invalid-inline.stderr b/tests/ui/invalid/invalid-inline.stderr
index 7edbf936b1b..54e6b2b5408 100644
--- a/tests/ui/invalid/invalid-inline.stderr
+++ b/tests/ui/invalid/invalid-inline.stderr
@@ -1,15 +1,36 @@
-error[E0534]: expected one argument
+error[E0805]: malformed `inline` attribute input
   --> $DIR/invalid-inline.rs:3:1
    |
 LL | #[inline(please,no)]
-   | ^^^^^^^^^^^^^^^^^^^^
+   | ^^^^^^^^-----------^
+   |         |
+   |         expected a single argument here
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL - #[inline(please,no)]
+LL + #[inline(always|never)]
+   |
+LL - #[inline(please,no)]
+LL + #[inline]
+   |
 
-error[E0534]: expected one argument
+error[E0805]: malformed `inline` attribute input
   --> $DIR/invalid-inline.rs:7:1
    |
 LL | #[inline()]
-   | ^^^^^^^^^^^
+   | ^^^^^^^^--^
+   |         |
+   |         expected a single argument here
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL | #[inline(always|never)]
+   |          ++++++++++++
+LL - #[inline()]
+LL + #[inline]
+   |
 
 error: aborting due to 2 previous errors
 
-For more information about this error, try `rustc --explain E0534`.
+For more information about this error, try `rustc --explain E0805`.
diff --git a/tests/ui/issues/issue-43988.rs b/tests/ui/issues/issue-43988.rs
index b114e8e0333..5fea5576b7f 100644
--- a/tests/ui/issues/issue-43988.rs
+++ b/tests/ui/issues/issue-43988.rs
@@ -9,7 +9,7 @@ fn main() {
 
     #[inline(XYZ)]
     let _b = 4;
-    //~^^ ERROR attribute should be applied to function or closure
+    //~^^ ERROR malformed `inline` attribute
 
     #[repr(nothing)]
     let _x = 0;
@@ -29,7 +29,7 @@ fn main() {
 
     #[inline(ABC)]
     foo();
-    //~^^ ERROR attribute should be applied to function or closure
+    //~^^ ERROR malformed `inline` attribute
 
     let _z = #[repr] 1;
     //~^ ERROR malformed `repr` attribute
diff --git a/tests/ui/issues/issue-43988.stderr b/tests/ui/issues/issue-43988.stderr
index d629f199b22..bd4eb8bbed3 100644
--- a/tests/ui/issues/issue-43988.stderr
+++ b/tests/ui/issues/issue-43988.stderr
@@ -1,14 +1,19 @@
-error: malformed `repr` attribute input
-  --> $DIR/issue-43988.rs:24:5
+error[E0539]: malformed `inline` attribute input
+  --> $DIR/issue-43988.rs:10:5
    |
-LL |     #[repr]
-   |     ^^^^^^^ help: must be of the form: `#[repr(C)]`
-
-error: malformed `repr` attribute input
-  --> $DIR/issue-43988.rs:34:14
+LL |     #[inline(XYZ)]
+   |     ^^^^^^^^^---^^
+   |              |
+   |              valid arguments are `always` or `never`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[inline(XYZ)]
+LL +     #[inline(always|never)]
+   |
+LL -     #[inline(XYZ)]
+LL +     #[inline]
    |
-LL |     let _z = #[repr] 1;
-   |              ^^^^^^^ help: must be of the form: `#[repr(C)]`
 
 error[E0552]: unrecognized representation hint
   --> $DIR/issue-43988.rs:14:12
@@ -26,6 +31,41 @@ LL |     #[repr(something_not_real)]
    |
    = help: valid reprs are `Rust` (default), `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize`
 
+error[E0539]: malformed `repr` attribute input
+  --> $DIR/issue-43988.rs:24:5
+   |
+LL |     #[repr]
+   |     ^^^^^^^
+   |     |
+   |     expected this to be a list
+   |     help: must be of the form: `#[repr(C)]`
+
+error[E0539]: malformed `inline` attribute input
+  --> $DIR/issue-43988.rs:30:5
+   |
+LL |     #[inline(ABC)]
+   |     ^^^^^^^^^---^^
+   |              |
+   |              valid arguments are `always` or `never`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL -     #[inline(ABC)]
+LL +     #[inline(always|never)]
+   |
+LL -     #[inline(ABC)]
+LL +     #[inline]
+   |
+
+error[E0539]: malformed `repr` attribute input
+  --> $DIR/issue-43988.rs:34:14
+   |
+LL |     let _z = #[repr] 1;
+   |              ^^^^^^^
+   |              |
+   |              expected this to be a list
+   |              help: must be of the form: `#[repr(C)]`
+
 error[E0518]: attribute should be applied to function or closure
   --> $DIR/issue-43988.rs:5:5
    |
@@ -34,23 +74,7 @@ LL |     #[inline]
 LL |     let _a = 4;
    |     ----------- not a function or closure
 
-error[E0518]: attribute should be applied to function or closure
-  --> $DIR/issue-43988.rs:10:5
-   |
-LL |     #[inline(XYZ)]
-   |     ^^^^^^^^^^^^^^
-LL |     let _b = 4;
-   |     ----------- not a function or closure
-
-error[E0518]: attribute should be applied to function or closure
-  --> $DIR/issue-43988.rs:30:5
-   |
-LL |     #[inline(ABC)]
-   |     ^^^^^^^^^^^^^^
-LL |     foo();
-   |     ----- not a function or closure
-
 error: aborting due to 7 previous errors
 
-Some errors have detailed explanations: E0518, E0552.
+Some errors have detailed explanations: E0518, E0539, E0552.
 For more information about an error, try `rustc --explain E0518`.
diff --git a/tests/ui/lint/unused/unused-attr-duplicate.stderr b/tests/ui/lint/unused/unused-attr-duplicate.stderr
index 769b174874b..e1c45e832af 100644
--- a/tests/ui/lint/unused/unused-attr-duplicate.stderr
+++ b/tests/ui/lint/unused/unused-attr-duplicate.stderr
@@ -103,19 +103,6 @@ LL | #[automatically_derived]
    | ^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: unused attribute
-  --> $DIR/unused-attr-duplicate.rs:74:1
-   |
-LL | #[inline(never)]
-   | ^^^^^^^^^^^^^^^^ help: remove this attribute
-   |
-note: attribute also specified here
-  --> $DIR/unused-attr-duplicate.rs:73:1
-   |
-LL | #[inline(always)]
-   | ^^^^^^^^^^^^^^^^^
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-
-error: unused attribute
   --> $DIR/unused-attr-duplicate.rs:77:1
    |
 LL | #[cold]
@@ -289,5 +276,18 @@ note: attribute also specified here
 LL |     #[macro_export]
    |     ^^^^^^^^^^^^^^^
 
+error: unused attribute
+  --> $DIR/unused-attr-duplicate.rs:74:1
+   |
+LL | #[inline(never)]
+   | ^^^^^^^^^^^^^^^^ help: remove this attribute
+   |
+note: attribute also specified here
+  --> $DIR/unused-attr-duplicate.rs:73:1
+   |
+LL | #[inline(always)]
+   | ^^^^^^^^^^^^^^^^^
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+
 error: aborting due to 23 previous errors
 
diff --git a/tests/ui/macros/genercs-in-path-with-prettry-hir.stdout b/tests/ui/macros/genercs-in-path-with-prettry-hir.stdout
index 6b41eb530db..7c41225f95e 100644
--- a/tests/ui/macros/genercs-in-path-with-prettry-hir.stdout
+++ b/tests/ui/macros/genercs-in-path-with-prettry-hir.stdout
@@ -8,7 +8,7 @@ extern crate std;
 // issue#97006
 
 macro_rules! m { ($attr_path: path) => { #[$attr_path] fn f() {} } }
-#[inline]
+#[attr = Inline(Hint)]
 fn f() { }
 
 fn main() { }
diff --git a/tests/ui/malformed/malformed-regressions.stderr b/tests/ui/malformed/malformed-regressions.stderr
index e1dbdb9ab3c..535db55a13d 100644
--- a/tests/ui/malformed/malformed-regressions.stderr
+++ b/tests/ui/malformed/malformed-regressions.stderr
@@ -17,15 +17,6 @@ LL | #[ignore()]
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
 
-error: valid forms for the attribute are `#[inline]` and `#[inline(always|never)]`
-  --> $DIR/malformed-regressions.rs:5:1
-   |
-LL | #[inline = ""]
-   | ^^^^^^^^^^^^^^
-   |
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-   = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
-
 error: attribute must be of the form `#[link(name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...", /*opt*/ import_name_type = "decorated|noprefix|undecorated")]`
   --> $DIR/malformed-regressions.rs:7:1
    |
@@ -44,5 +35,14 @@ LL | #[link = ""]
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
 
+error: valid forms for the attribute are `#[inline(always|never)]` and `#[inline]`
+  --> $DIR/malformed-regressions.rs:5:1
+   |
+LL | #[inline = ""]
+   | ^^^^^^^^^^^^^^
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #57571 <https://github.com/rust-lang/rust/issues/57571>
+
 error: aborting due to 5 previous errors
 
diff --git a/tests/ui/repr/repr.stderr b/tests/ui/repr/repr.stderr
index e0bec666381..f3b11398eaa 100644
--- a/tests/ui/repr/repr.stderr
+++ b/tests/ui/repr/repr.stderr
@@ -1,20 +1,30 @@
-error: malformed `repr` attribute input
+error[E0539]: malformed `repr` attribute input
   --> $DIR/repr.rs:1:1
    |
 LL | #[repr]
-   | ^^^^^^^ help: must be of the form: `#[repr(C)]`
+   | ^^^^^^^
+   | |
+   | expected this to be a list
+   | help: must be of the form: `#[repr(C)]`
 
-error: malformed `repr` attribute input
+error[E0539]: malformed `repr` attribute input
   --> $DIR/repr.rs:4:1
    |
 LL | #[repr = "B"]
-   | ^^^^^^^^^^^^^ help: must be of the form: `#[repr(C)]`
+   | ^^^^^^^^^^^^^
+   | |
+   | expected this to be a list
+   | help: must be of the form: `#[repr(C)]`
 
-error: malformed `repr` attribute input
+error[E0539]: malformed `repr` attribute input
   --> $DIR/repr.rs:7:1
    |
 LL | #[repr = "C"]
-   | ^^^^^^^^^^^^^ help: must be of the form: `#[repr(C)]`
+   | ^^^^^^^^^^^^^
+   | |
+   | expected this to be a list
+   | help: must be of the form: `#[repr(C)]`
 
 error: aborting due to 3 previous errors
 
+For more information about this error, try `rustc --explain E0539`.
diff --git a/tests/ui/span/E0535.rs b/tests/ui/span/E0535.rs
deleted file mode 100644
index e26334e9bbd..00000000000
--- a/tests/ui/span/E0535.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-#[inline(unknown)] //~ ERROR E0535
-pub fn something() {}
-
-fn main() {
-    something();
-}
diff --git a/tests/ui/span/E0535.stderr b/tests/ui/span/E0535.stderr
deleted file mode 100644
index 9060b687f50..00000000000
--- a/tests/ui/span/E0535.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0535]: invalid argument
-  --> $DIR/E0535.rs:1:10
-   |
-LL | #[inline(unknown)]
-   |          ^^^^^^^
-   |
-   = help: valid inline arguments are `always` and `never`
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0535`.
diff --git a/tests/ui/span/E0539.rs b/tests/ui/span/E0539.rs
new file mode 100644
index 00000000000..e0c6dbfc591
--- /dev/null
+++ b/tests/ui/span/E0539.rs
@@ -0,0 +1,6 @@
+#[inline(unknown)] //~ ERROR malformed `inline` attribute
+pub fn something() {}
+
+fn main() {
+    something();
+}
diff --git a/tests/ui/span/E0539.stderr b/tests/ui/span/E0539.stderr
new file mode 100644
index 00000000000..01f091a2676
--- /dev/null
+++ b/tests/ui/span/E0539.stderr
@@ -0,0 +1,20 @@
+error[E0539]: malformed `inline` attribute input
+  --> $DIR/E0539.rs:1:1
+   |
+LL | #[inline(unknown)]
+   | ^^^^^^^^^-------^^
+   |          |
+   |          valid arguments are `always` or `never`
+   |
+help: try changing it to one of the following valid forms of the attribute
+   |
+LL - #[inline(unknown)]
+LL + #[inline(always|never)]
+   |
+LL - #[inline(unknown)]
+LL + #[inline]
+   |
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0539`.
diff --git a/tests/ui/stability-attribute/stability-attribute-sanity-2.rs b/tests/ui/stability-attribute/stability-attribute-sanity-2.rs
index de3ea4eaca9..92e300d33d6 100644
--- a/tests/ui/stability-attribute/stability-attribute-sanity-2.rs
+++ b/tests/ui/stability-attribute/stability-attribute-sanity-2.rs
@@ -4,7 +4,7 @@
 
 #![stable(feature = "stable_test_feature", since = "1.0.0")]
 
-#[stable(feature = "a", feature = "b", since = "1.0.0")] //~ ERROR multiple 'feature' items
+#[stable(feature = "a", feature = "b", since = "1.0.0")] //~ ERROR malformed `stable` attribute input [E0538]
 fn f1() { }
 
 #[stable(feature = "a", sinse = "1.0.0")] //~ ERROR unknown meta item 'sinse'
diff --git a/tests/ui/stability-attribute/stability-attribute-sanity-2.stderr b/tests/ui/stability-attribute/stability-attribute-sanity-2.stderr
index 8dbcc6c97ef..5b35a51cad7 100644
--- a/tests/ui/stability-attribute/stability-attribute-sanity-2.stderr
+++ b/tests/ui/stability-attribute/stability-attribute-sanity-2.stderr
@@ -1,8 +1,11 @@
-error[E0538]: multiple 'feature' items
-  --> $DIR/stability-attribute-sanity-2.rs:7:25
+error[E0538]: malformed `stable` attribute input
+  --> $DIR/stability-attribute-sanity-2.rs:7:1
    |
 LL | #[stable(feature = "a", feature = "b", since = "1.0.0")]
-   |                         ^^^^^^^^^^^^^
+   | ^^^^^^^^^^^^^^^^^^^^^^^^-------^^^^^^^^^^^^^^^^^^^^^^^^^
+   | |                       |
+   | |                       found `feature` used as a key more than once
+   | help: must be of the form: `#[stable(feature = "name", since = "version")]`
 
 error[E0541]: unknown meta item 'sinse'
   --> $DIR/stability-attribute-sanity-2.rs:10:25
diff --git a/tests/ui/stability-attribute/stability-attribute-sanity.rs b/tests/ui/stability-attribute/stability-attribute-sanity.rs
index f46e35e1a72..c4c86e12d26 100644
--- a/tests/ui/stability-attribute/stability-attribute-sanity.rs
+++ b/tests/ui/stability-attribute/stability-attribute-sanity.rs
@@ -8,16 +8,16 @@ mod bogus_attribute_types_1 {
     #[stable(feature = "a", since = "4.4.4", reason)] //~ ERROR unknown meta item 'reason' [E0541]
     fn f1() { }
 
-    #[stable(feature = "a", since)] //~ ERROR expected a quoted string literal [E0539]
+    #[stable(feature = "a", since)] //~ ERROR malformed `stable` attribute input [E0539]
     fn f2() { }
 
-    #[stable(feature, since = "3.3.3")] //~ ERROR expected a quoted string literal [E0539]
+    #[stable(feature, since = "3.3.3")] //~ ERROR malformed `stable` attribute input [E0539]
     fn f3() { }
 
-    #[stable(feature = "a", since(b))] //~ ERROR expected a quoted string literal [E0539]
+    #[stable(feature = "a", since(b))] //~ ERROR malformed `stable` attribute input [E0539]
     fn f5() { }
 
-    #[stable(feature(b), since = "3.3.3")] //~ ERROR expected a quoted string literal [E0539]
+    #[stable(feature(b), since = "3.3.3")] //~ ERROR malformed `stable` attribute input [E0539]
     fn f6() { }
 }
 
diff --git a/tests/ui/stability-attribute/stability-attribute-sanity.stderr b/tests/ui/stability-attribute/stability-attribute-sanity.stderr
index 2e2b5b509c8..ae948237d7e 100644
--- a/tests/ui/stability-attribute/stability-attribute-sanity.stderr
+++ b/tests/ui/stability-attribute/stability-attribute-sanity.stderr
@@ -4,29 +4,41 @@ error[E0541]: unknown meta item 'reason'
 LL |     #[stable(feature = "a", since = "4.4.4", reason)]
    |                                              ^^^^^^ expected one of `feature`, `since`
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/stability-attribute-sanity.rs:11:29
+error[E0539]: malformed `stable` attribute input
+  --> $DIR/stability-attribute-sanity.rs:11:5
    |
 LL |     #[stable(feature = "a", since)]
-   |                             ^^^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^-----^^
+   |     |                       |
+   |     |                       expected this to be of the form `since = "..."`
+   |     help: must be of the form: `#[stable(feature = "name", since = "version")]`
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/stability-attribute-sanity.rs:14:14
+error[E0539]: malformed `stable` attribute input
+  --> $DIR/stability-attribute-sanity.rs:14:5
    |
 LL |     #[stable(feature, since = "3.3.3")]
-   |              ^^^^^^^
+   |     ^^^^^^^^^-------^^^^^^^^^^^^^^^^^^^
+   |     |        |
+   |     |        expected this to be of the form `feature = "..."`
+   |     help: must be of the form: `#[stable(feature = "name", since = "version")]`
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/stability-attribute-sanity.rs:17:29
+error[E0539]: malformed `stable` attribute input
+  --> $DIR/stability-attribute-sanity.rs:17:5
    |
 LL |     #[stable(feature = "a", since(b))]
-   |                             ^^^^^^^^
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^--------^^
+   |     |                       |
+   |     |                       expected this to be of the form `since = "..."`
+   |     help: must be of the form: `#[stable(feature = "name", since = "version")]`
 
-error[E0539]: expected a quoted string literal
-  --> $DIR/stability-attribute-sanity.rs:20:14
+error[E0539]: malformed `stable` attribute input
+  --> $DIR/stability-attribute-sanity.rs:20:5
    |
 LL |     #[stable(feature(b), since = "3.3.3")]
-   |              ^^^^^^^^^^
+   |     ^^^^^^^^^----------^^^^^^^^^^^^^^^^^^^
+   |     |        |
+   |     |        expected this to be of the form `feature = "..."`
+   |     help: must be of the form: `#[stable(feature = "name", since = "version")]`
 
 error[E0546]: missing 'feature'
   --> $DIR/stability-attribute-sanity.rs:25:5