diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index 4215d5c55a0..4996c2195ef 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -712,10 +712,6 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) {
     gate_all!(const_trait_impl, "const trait impls are experimental");
     gate_all!(half_open_range_patterns, "half-open range patterns are unstable");
     gate_all!(inline_const, "inline-const is experimental");
-    gate_all!(
-        extended_key_value_attributes,
-        "arbitrary expressions in key-value attributes are unstable"
-    );
     gate_all!(
         const_generics_defaults,
         "default values for const generic parameters are experimental"
diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs
index 329458773ff..8eef06f018f 100644
--- a/compiler/rustc_codegen_llvm/src/lib.rs
+++ b/compiler/rustc_codegen_llvm/src/lib.rs
@@ -8,7 +8,7 @@
 #![feature(bool_to_option)]
 #![feature(const_cstr_unchecked)]
 #![feature(crate_visibility_modifier)]
-#![feature(extended_key_value_attributes)]
+#![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
 #![feature(extern_types)]
 #![feature(in_band_lifetimes)]
 #![feature(iter_zip)]
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index f53ce7ceace..65352f0bc6e 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -5,7 +5,7 @@
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![feature(crate_visibility_modifier)]
 #![feature(backtrace)]
-#![feature(extended_key_value_attributes)]
+#![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
 #![feature(format_args_capture)]
 #![feature(iter_zip)]
 #![feature(nll)]
diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs
index e8642a52749..eef71e096a5 100644
--- a/compiler/rustc_feature/src/accepted.rs
+++ b/compiler/rustc_feature/src/accepted.rs
@@ -281,6 +281,8 @@ declare_features! (
     (accepted, or_patterns, "1.53.0", Some(54883), None),
     /// Allows defining identifiers beyond ASCII.
     (accepted, non_ascii_idents, "1.53.0", Some(55467), None),
+    /// Allows arbitrary expressions in key-value attributes at parse time.
+    (accepted, extended_key_value_attributes, "1.54.0", Some(78835), None),
 
     // -------------------------------------------------------------------------
     // feature-group-end: accepted features
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index b3a6fb3c808..2cef46a844a 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -601,9 +601,6 @@ declare_features! (
     /// Allows capturing disjoint fields in a closure/generator (RFC 2229).
     (active, capture_disjoint_fields, "1.49.0", Some(53488), None),
 
-    /// Allows arbitrary expressions in key-value attributes at parse time.
-    (active, extended_key_value_attributes, "1.50.0", Some(78835), None),
-
     /// Allows const generics to have default values (e.g. `struct Foo<const N: usize = 3>(...);`).
     (active, const_generics_defaults, "1.51.0", Some(44580), None),
 
diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs
index 65c99535c4e..71e997994de 100644
--- a/compiler/rustc_hir/src/lib.rs
+++ b/compiler/rustc_hir/src/lib.rs
@@ -4,7 +4,7 @@
 
 #![feature(crate_visibility_modifier)]
 #![feature(const_panic)]
-#![feature(extended_key_value_attributes)]
+#![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
 #![feature(in_band_lifetimes)]
 #![feature(once_cell)]
 #![cfg_attr(bootstrap, feature(or_patterns))]
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 35cfaae13a4..4c2bc6ebf31 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -1065,24 +1065,11 @@ impl<'a> Parser<'a> {
             } else if !delimited_only {
                 if self.eat(&token::Eq) {
                     let eq_span = self.prev_token.span;
-                    let mut is_interpolated_expr = false;
-                    if let token::Interpolated(nt) = &self.token.kind {
-                        if let token::NtExpr(..) = **nt {
-                            is_interpolated_expr = true;
-                        }
-                    }
 
                     // Collect tokens because they are used during lowering to HIR.
                     let expr = self.parse_expr_force_collect()?;
                     let span = expr.span;
 
-                    match &expr.kind {
-                        // Not gated to support things like `doc = $expr` that work on stable.
-                        _ if is_interpolated_expr => {}
-                        ExprKind::Lit(lit) if lit.kind.is_unsuffixed() => {}
-                        _ => self.sess.gated_spans.gate(sym::extended_key_value_attributes, span),
-                    }
-
                     let token_kind = token::Interpolated(Lrc::new(token::NtExpr(expr)));
                     MacArgs::Eq(eq_span, Token::new(token_kind, span))
                 } else {
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 6a4f2d5a544..f652c52db53 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -113,7 +113,7 @@
 #![cfg_attr(bootstrap, feature(doc_spotlight))]
 #![cfg_attr(not(bootstrap), feature(doc_notable_trait))]
 #![feature(duration_consts_2)]
-#![feature(extended_key_value_attributes)]
+#![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
 #![feature(extern_types)]
 #![feature(fundamental)]
 #![feature(intra_doc_pointers)]
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 5f89ac059fd..6d11698d268 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -268,7 +268,7 @@
 #![feature(exact_size_is_empty)]
 #![feature(exhaustive_patterns)]
 #![feature(extend_one)]
-#![feature(extended_key_value_attributes)]
+#![cfg_attr(bootstrap, feature(extended_key_value_attributes))]
 #![feature(fn_traits)]
 #![feature(format_args_nl)]
 #![feature(gen_future)]
diff --git a/src/doc/rustdoc/README.md b/src/doc/rustdoc/README.md
new file mode 100644
index 00000000000..7d97d5e4ab5
--- /dev/null
+++ b/src/doc/rustdoc/README.md
@@ -0,0 +1,5 @@
+# Rustdoc
+
+This is documentation for rustdoc itself, written in mdbook format.
+To build the book, use `x.py doc src/doc/rustdoc`.
+To run doctests, use `x.py test src/doc/rustdoc`.
diff --git a/src/doc/rustdoc/src/the-doc-attribute.md b/src/doc/rustdoc/src/the-doc-attribute.md
index 52f2a3728fa..d192f7d5ce9 100644
--- a/src/doc/rustdoc/src/the-doc-attribute.md
+++ b/src/doc/rustdoc/src/the-doc-attribute.md
@@ -35,6 +35,13 @@ Which can feel more flexible. Note that this would generate this:
 
 but given that docs are rendered via Markdown, it will remove these newlines.
 
+Another use case is for including external files as documentation:
+
+```rust,no_run
+#[doc = include_str!("../README.md")]
+# fn f() {}
+```
+
 The `doc` attribute has more options though! These don't involve the text of
 the output, but instead, various aspects of the presentation of the output.
 We've split them into two kinds below: attributes that are useful at the
diff --git a/src/test/rustdoc/external-doc.rs b/src/test/rustdoc/external-doc.rs
index befd31a5492..0dadca551a9 100644
--- a/src/test/rustdoc/external-doc.rs
+++ b/src/test/rustdoc/external-doc.rs
@@ -1,5 +1,4 @@
 #![feature(external_doc)]
-#![feature(extended_key_value_attributes)]
 
 // @has external_doc/struct.CanHasDocs.html
 // @has - '//h1' 'External Docs'
diff --git a/src/test/ui/attributes/key-value-expansion-on-mac.rs b/src/test/ui/attributes/key-value-expansion-on-mac.rs
index 1247ff2b230..95bc1c04961 100644
--- a/src/test/ui/attributes/key-value-expansion-on-mac.rs
+++ b/src/test/ui/attributes/key-value-expansion-on-mac.rs
@@ -1,4 +1,3 @@
-#![feature(extended_key_value_attributes)]
 #![feature(rustc_attrs)]
 
 #[rustc_dummy = stringify!(a)] // OK
diff --git a/src/test/ui/attributes/key-value-expansion-on-mac.stderr b/src/test/ui/attributes/key-value-expansion-on-mac.stderr
index b74f3518a7e..fa9ea543765 100644
--- a/src/test/ui/attributes/key-value-expansion-on-mac.stderr
+++ b/src/test/ui/attributes/key-value-expansion-on-mac.stderr
@@ -1,5 +1,5 @@
 error: unexpected token: `stringify!(b)`
-  --> $DIR/key-value-expansion-on-mac.rs:12:17
+  --> $DIR/key-value-expansion-on-mac.rs:11:17
    |
 LL | #[rustc_dummy = stringify!(b)]
    |                 ^^^^^^^^^^^^^
diff --git a/src/test/ui/feature-gates/feature-gate-extended_key_value_attributes.rs b/src/test/ui/feature-gates/feature-gate-extended_key_value_attributes.rs
deleted file mode 100644
index f19fdb45f1f..00000000000
--- a/src/test/ui/feature-gates/feature-gate-extended_key_value_attributes.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-#[cfg(FALSE)]
-#[attr = multi::segment::path] //~ ERROR arbitrary expressions in key-value attributes are unstable
-#[attr = macro_call!()] //~ ERROR arbitrary expressions in key-value attributes are unstable
-#[attr = 1 + 2] //~ ERROR arbitrary expressions in key-value attributes are unstable
-#[attr = what?] //~ ERROR arbitrary expressions in key-value attributes are unstable
-struct S;
-
-fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-extended_key_value_attributes.stderr b/src/test/ui/feature-gates/feature-gate-extended_key_value_attributes.stderr
deleted file mode 100644
index 9887814b907..00000000000
--- a/src/test/ui/feature-gates/feature-gate-extended_key_value_attributes.stderr
+++ /dev/null
@@ -1,39 +0,0 @@
-error[E0658]: arbitrary expressions in key-value attributes are unstable
-  --> $DIR/feature-gate-extended_key_value_attributes.rs:2:10
-   |
-LL | #[attr = multi::segment::path]
-   |          ^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
-   = help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
-
-error[E0658]: arbitrary expressions in key-value attributes are unstable
-  --> $DIR/feature-gate-extended_key_value_attributes.rs:3:10
-   |
-LL | #[attr = macro_call!()]
-   |          ^^^^^^^^^^^^^
-   |
-   = note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
-   = help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
-
-error[E0658]: arbitrary expressions in key-value attributes are unstable
-  --> $DIR/feature-gate-extended_key_value_attributes.rs:4:10
-   |
-LL | #[attr = 1 + 2]
-   |          ^^^^^
-   |
-   = note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
-   = help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
-
-error[E0658]: arbitrary expressions in key-value attributes are unstable
-  --> $DIR/feature-gate-extended_key_value_attributes.rs:5:10
-   |
-LL | #[attr = what?]
-   |          ^^^^^
-   |
-   = note: see issue #78835 <https://github.com/rust-lang/rust/issues/78835> for more information
-   = help: add `#![feature(extended_key_value_attributes)]` to the crate attributes to enable
-
-error: aborting due to 4 previous errors
-
-For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/suffixed-literal-meta.rs b/src/test/ui/suffixed-literal-meta.rs
index 319264aec9c..a6531490c01 100644
--- a/src/test/ui/suffixed-literal-meta.rs
+++ b/src/test/ui/suffixed-literal-meta.rs
@@ -1,4 +1,4 @@
-#![feature(rustc_attrs, extended_key_value_attributes)]
+#![feature(rustc_attrs)]
 
 #[rustc_dummy = 1usize] //~ ERROR: suffixed literals are not allowed in attributes
 #[rustc_dummy = 1u8] //~ ERROR: suffixed literals are not allowed in attributes