From c560da8d5eb0019255d439c5c55bd318385f6caa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Tue, 3 Dec 2024 21:18:24 +0000
Subject: [PATCH 1/4] Replace `cfg_attr` in AST with `rustc-cfg-placeholder`
 for accurate span tracking

Previously, when evaluating a `#[cfg_attr(..)]` to false, the entire attribute was removed from the AST. Afterwards, we insert in its place a `#[rustc-cfg-placeholder]` attribute so that checks for attributes can still know about their placement. This is particularly relevant when we suggest removing items with `cfg_attr`s (fix #56328). We use `rustc-cfg-placeholder` as it is an ident that can't be written by the end user to begin with. We tweak the wording of the existing "unused `extern crate`" lint.

```
warning: unused `extern crate`
  --> $DIR/removing-extern-crate.rs:9:1
   |
LL | extern crate removing_extern_crate as foo;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
   |
note: the lint level is defined here
  --> $DIR/removing-extern-crate.rs:6:9
   |
LL | #![warn(rust_2018_idioms)]
   |         ^^^^^^^^^^^^^^^^
   = note: `#[warn(unused_extern_crates)]` implied by `#[warn(rust_2018_idioms)]`
help: remove the unused `extern crate`
   |
LL - #[cfg_attr(test, macro_use)]
LL - extern crate removing_extern_crate as foo;
LL +
   |
```
---
 compiler/rustc_ast/src/ast.rs                 |  4 ++
 compiler/rustc_ast/src/attr/mod.rs            |  1 +
 .../rustc_ast_passes/src/ast_validation.rs    |  4 ++
 compiler/rustc_expand/src/config.rs           | 20 ++++++-
 compiler/rustc_feature/src/builtin_attrs.rs   |  6 +++
 compiler/rustc_lint/messages.ftl              |  5 +-
 compiler/rustc_lint/src/early/diagnostics.rs  |  4 +-
 compiler/rustc_lint/src/lints.rs              |  4 +-
 compiler/rustc_lint_defs/src/lib.rs           |  1 +
 compiler/rustc_passes/src/check_attr.rs       |  4 +-
 compiler/rustc_resolve/src/check_unused.rs    |  1 +
 compiler/rustc_span/src/symbol.rs             |  2 +
 .../editions/edition-extern-crate-allowed.rs  |  2 +-
 .../edition-extern-crate-allowed.stderr       |  8 ++-
 tests/ui/imports/extern-crate-used.rs         |  2 +-
 tests/ui/imports/extern-crate-used.stderr     |  9 +++-
 tests/ui/lint/unnecessary-extern-crate.rs     | 12 ++---
 tests/ui/lint/unnecessary-extern-crate.stderr | 53 ++++++++++++++-----
 .../lint/unused/lint-unused-extern-crate.rs   |  4 +-
 .../unused/lint-unused-extern-crate.stderr    | 19 +++++--
 tests/ui/proc-macro/no-macro-use-attr.rs      |  2 +-
 tests/ui/proc-macro/no-macro-use-attr.stderr  |  8 ++-
 tests/ui/removing-extern-crate.fixed          |  8 +--
 tests/ui/removing-extern-crate.rs             | 10 ++--
 tests/ui/removing-extern-crate.stderr         | 49 ++++++++++++-----
 .../extern-crate-idiomatic-in-2018.fixed      |  2 +-
 .../extern-crate-idiomatic-in-2018.rs         |  2 +-
 .../extern-crate-idiomatic-in-2018.stderr     |  8 ++-
 ...-54400-unused-extern-crate-attr-span.fixed |  2 +-
 ...sue-54400-unused-extern-crate-attr-span.rs |  4 +-
 ...54400-unused-extern-crate-attr-span.stderr | 14 ++---
 tests/ui/rust-2018/remove-extern-crate.fixed  |  2 +-
 tests/ui/rust-2018/remove-extern-crate.rs     |  2 +-
 tests/ui/rust-2018/remove-extern-crate.stderr |  9 +++-
 34 files changed, 210 insertions(+), 77 deletions(-)

diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 1b831c454e6d..738ff4277108 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -3143,6 +3143,10 @@ impl AttrItem {
             || self.path == sym::allow
             || self.path == sym::deny
     }
+
+    pub fn is_cfg_placeholder(&self) -> bool {
+        self.path == sym::rustc_cfg_placeholder
+    }
 }
 
 /// `TraitRef`s appear in impls.
diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs
index 4d613085d793..6e41958d420c 100644
--- a/compiler/rustc_ast/src/attr/mod.rs
+++ b/compiler/rustc_ast/src/attr/mod.rs
@@ -233,6 +233,7 @@ impl Attribute {
 
     pub fn token_trees(&self) -> Vec<TokenTree> {
         match self.kind {
+            AttrKind::Normal(ref normal) if normal.item.is_cfg_placeholder() => vec![],
             AttrKind::Normal(ref normal) => normal
                 .tokens
                 .as_ref()
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 232d60be4eb2..a4c435c09c3c 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -339,6 +339,10 @@ impl<'a> AstValidator<'a> {
                     sym::deny,
                     sym::expect,
                     sym::forbid,
+                    // `cfg` and `cfg_attr` get replaced with an inert `rustc_cfg_placeholder` to
+                    // keep the attribute "spot" in the AST. This allows suggestions to remove an
+                    // item to provide a correct suggestion when `#[cfg_attr]`s are present.
+                    sym::rustc_cfg_placeholder,
                     sym::warn,
                 ];
                 !arr.contains(&attr.name_or_empty()) && rustc_attr_parsing::is_builtin_attr(*attr)
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index 5570c0c38e83..fae60613d0f8 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -292,7 +292,25 @@ impl<'a> StripUnconfigured<'a> {
         }
 
         if !attr::cfg_matches(&cfg_predicate, &self.sess, self.lint_node_id, self.features) {
-            return vec![];
+            // `cfg` and `cfg_attr` gets replaced with an inert `rustc_cfg_placeholder` to keep the
+            // attribute "spot" in the AST. This allows suggestions to remove an item to provide a
+            // correct suggestion when `#[cfg_attr]`s are present.
+            let item = ast::AttrItem {
+                unsafety: ast::Safety::Default,
+                path: ast::Path::from_ident(rustc_span::symbol::Ident::with_dummy_span(
+                    sym::rustc_cfg_placeholder,
+                )),
+                args: ast::AttrArgs::Empty,
+                tokens: None,
+            };
+            let kind = ast::AttrKind::Normal(P(ast::NormalAttr { item, tokens: None }));
+            let attr: ast::Attribute = ast::Attribute {
+                kind,
+                id: self.sess.psess.attr_id_generator.mk_attr_id(),
+                style: ast::AttrStyle::Outer,
+                span: cfg_attr.span,
+            };
+            return vec![attr];
         }
 
         if recursive {
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index 40857e0066ee..db7d9a3f8243 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -752,6 +752,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         template!(Word, List: r#""...""#), DuplicatesOk,
         EncodeCrossCrate::Yes, INTERNAL_UNSTABLE
     ),
+    // placeholder that replaces `cfg_attr` in the item's attribute list
+    ungated!(
+        rustc_cfg_placeholder, Normal, template!(Word /* irrelevant */), DuplicatesOk,
+        EncodeCrossCrate::No
+    ),
+
 
     // ==========================================================================
     // Internal attributes, Diagnostics related:
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index d51865810b9a..666999f81d6b 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -945,8 +945,9 @@ lint_unused_doc_comment = unused doc comment
     .label = rustdoc does not generate documentation for macro invocations
     .help = to document an item produced by a macro, the macro must produce the documentation as part of its expansion
 
-lint_unused_extern_crate = unused extern crate
-    .suggestion = remove it
+lint_unused_extern_crate = unused `extern crate`
+    .label = unused
+    .suggestion = remove the unused `extern crate`
 
 lint_unused_import_braces = braces around {$node} is unnecessary
 
diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs
index 40ca9e05d95d..279f452a305e 100644
--- a/compiler/rustc_lint/src/early/diagnostics.rs
+++ b/compiler/rustc_lint/src/early/diagnostics.rs
@@ -292,8 +292,8 @@ pub(super) fn decorate_lint(
         BuiltinLintDiag::ByteSliceInPackedStructWithDerive { ty } => {
             lints::ByteSliceInPackedStructWithDerive { ty }.decorate_lint(diag);
         }
-        BuiltinLintDiag::UnusedExternCrate { removal_span } => {
-            lints::UnusedExternCrate { removal_span }.decorate_lint(diag);
+        BuiltinLintDiag::UnusedExternCrate { span, removal_span } => {
+            lints::UnusedExternCrate { span, removal_span }.decorate_lint(diag);
         }
         BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => {
             let suggestion_span = vis_span.between(ident_span);
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 005863095729..ff64dc5dd35b 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -3001,7 +3001,9 @@ pub(crate) struct ByteSliceInPackedStructWithDerive {
 #[derive(LintDiagnostic)]
 #[diag(lint_unused_extern_crate)]
 pub(crate) struct UnusedExternCrate {
-    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[label]
+    pub span: Span,
+    #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")]
     pub removal_span: Span,
 }
 
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 46b4b1d43838..01b0b961974a 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -735,6 +735,7 @@ pub enum BuiltinLintDiag {
         ty: String,
     },
     UnusedExternCrate {
+        span: Span,
         removal_span: Span,
     },
     ExternCrateNotIdiomatic {
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index ece5a53aaa9c..a43392083783 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -286,6 +286,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                             | sym::prelude_import
                             | sym::panic_handler
                             | sym::allow_internal_unsafe
+                            | sym::rustc_cfg_placeholder // Inert, it's a placeholder in the AST.
                             | sym::fundamental
                             | sym::lang
                             | sym::needs_allocator
@@ -575,6 +576,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // conditional compilation
             sym::cfg,
             sym::cfg_attr,
+            sym::rustc_cfg_placeholder,
             // testing (allowed here so better errors can be generated in `rustc_builtin_macros::test`)
             sym::test,
             sym::ignore,
@@ -2641,7 +2643,7 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
         // only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed
         // if we allow more attributes (e.g., tool attributes and `allow/deny/warn`)
         // in where clauses. After that, only `self.check_attributes` should be enough.
-        const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg, sym::cfg_attr];
+        const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg, sym::cfg_attr, sym::rustc_cfg_placeholder];
         let spans = self
             .tcx
             .hir_attrs(where_predicate.hir_id)
diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs
index 1c1e8494ffc7..31090d40679c 100644
--- a/compiler/rustc_resolve/src/check_unused.rs
+++ b/compiler/rustc_resolve/src/check_unused.rs
@@ -154,6 +154,7 @@ impl<'a, 'ra, 'tcx> UnusedImportCheckVisitor<'a, 'ra, 'tcx> {
                         extern_crate.id,
                         span,
                         BuiltinLintDiag::UnusedExternCrate {
+                            span: extern_crate.span,
                             removal_span: extern_crate.span_with_attributes,
                         },
                     );
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 8a8bec35d819..c1a54c2d4092 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1745,6 +1745,8 @@ symbols! {
         rustc_autodiff,
         rustc_builtin_macro,
         rustc_capture_analysis,
+        // We ensure that the attribute can't be written by end users by adding `-` to the name.
+        rustc_cfg_placeholder: "rustc-cfg-placeholder",
         rustc_clean,
         rustc_coherence_is_core,
         rustc_coinductive,
diff --git a/tests/ui/editions/edition-extern-crate-allowed.rs b/tests/ui/editions/edition-extern-crate-allowed.rs
index 5e07417e5aa1..7798928a7c42 100644
--- a/tests/ui/editions/edition-extern-crate-allowed.rs
+++ b/tests/ui/editions/edition-extern-crate-allowed.rs
@@ -5,6 +5,6 @@
 #![warn(rust_2018_idioms)]
 
 extern crate edition_extern_crate_allowed;
-//~^ WARNING unused extern crate
+//~^ WARNING unused `extern crate`
 
 fn main() {}
diff --git a/tests/ui/editions/edition-extern-crate-allowed.stderr b/tests/ui/editions/edition-extern-crate-allowed.stderr
index dde774c520d7..f816f172311c 100644
--- a/tests/ui/editions/edition-extern-crate-allowed.stderr
+++ b/tests/ui/editions/edition-extern-crate-allowed.stderr
@@ -1,8 +1,8 @@
-warning: unused extern crate
+warning: unused `extern crate`
   --> $DIR/edition-extern-crate-allowed.rs:7:1
    |
 LL | extern crate edition_extern_crate_allowed;
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/edition-extern-crate-allowed.rs:5:9
@@ -10,6 +10,10 @@ note: the lint level is defined here
 LL | #![warn(rust_2018_idioms)]
    |         ^^^^^^^^^^^^^^^^
    = note: `#[warn(unused_extern_crates)]` implied by `#[warn(rust_2018_idioms)]`
+help: remove the unused `extern crate`
+   |
+LL - extern crate edition_extern_crate_allowed;
+   |
 
 warning: 1 warning emitted
 
diff --git a/tests/ui/imports/extern-crate-used.rs b/tests/ui/imports/extern-crate-used.rs
index b57dd02cd80c..9e2e1ca340f8 100644
--- a/tests/ui/imports/extern-crate-used.rs
+++ b/tests/ui/imports/extern-crate-used.rs
@@ -15,7 +15,7 @@ extern crate core as iso3;
 extern crate core as iso4;
 
 // Doesn't introduce its extern prelude entry, so it's still considered unused.
-extern crate core; //~ ERROR unused extern crate
+extern crate core; //~ ERROR unused `extern crate`
 
 mod m {
     use iso1::any as are_you_okay1;
diff --git a/tests/ui/imports/extern-crate-used.stderr b/tests/ui/imports/extern-crate-used.stderr
index 982da0c913ed..fcca4a4e1a7f 100644
--- a/tests/ui/imports/extern-crate-used.stderr
+++ b/tests/ui/imports/extern-crate-used.stderr
@@ -1,14 +1,19 @@
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/extern-crate-used.rs:18:1
    |
 LL | extern crate core;
-   | ^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/extern-crate-used.rs:6:9
    |
 LL | #![deny(unused_extern_crates)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: remove the unused `extern crate`
+   |
+LL - extern crate core;
+LL +
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/lint/unnecessary-extern-crate.rs b/tests/ui/lint/unnecessary-extern-crate.rs
index 7f97a4c469ed..fb4edee029ec 100644
--- a/tests/ui/lint/unnecessary-extern-crate.rs
+++ b/tests/ui/lint/unnecessary-extern-crate.rs
@@ -4,10 +4,10 @@
 #![feature(test)]
 
 extern crate core;
-//~^ ERROR unused extern crate
+//~^ ERROR unused `extern crate`
 //~| HELP remove
 extern crate core as x;
-//~^ ERROR unused extern crate
+//~^ ERROR unused `extern crate`
 //~| HELP remove
 
 extern crate proc_macro;
@@ -29,11 +29,11 @@ mod foo {
     pub(super) extern crate alloc as d;
 
     extern crate core;
-    //~^ ERROR unused extern crate
+    //~^ ERROR unused `extern crate`
     //~| HELP remove
 
     extern crate core as x;
-    //~^ ERROR unused extern crate
+    //~^ ERROR unused `extern crate`
     //~| HELP remove
 
     pub extern crate test;
@@ -42,11 +42,11 @@ mod foo {
 
     mod bar {
         extern crate core;
-        //~^ ERROR unused extern crate
+        //~^ ERROR unused `extern crate`
         //~| HELP remove
 
         extern crate core as x;
-        //~^ ERROR unused extern crate
+        //~^ ERROR unused `extern crate`
         //~| HELP remove
 
         pub(in crate::foo::bar) extern crate alloc as e;
diff --git a/tests/ui/lint/unnecessary-extern-crate.stderr b/tests/ui/lint/unnecessary-extern-crate.stderr
index 1fa4aa9c9a9c..1049476fa83b 100644
--- a/tests/ui/lint/unnecessary-extern-crate.stderr
+++ b/tests/ui/lint/unnecessary-extern-crate.stderr
@@ -1,44 +1,73 @@
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/unnecessary-extern-crate.rs:6:1
    |
 LL | extern crate core;
-   | ^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/unnecessary-extern-crate.rs:3:9
    |
 LL | #![deny(unused_extern_crates)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: remove the unused `extern crate`
+   |
+LL - extern crate core;
+   |
 
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/unnecessary-extern-crate.rs:9:1
    |
 LL | extern crate core as x;
-   | ^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL - extern crate core as x;
+   |
 
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/unnecessary-extern-crate.rs:31:5
    |
 LL |     extern crate core;
-   |     ^^^^^^^^^^^^^^^^^^ help: remove it
+   |     ^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     extern crate core;
+   |
 
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/unnecessary-extern-crate.rs:35:5
    |
 LL |     extern crate core as x;
-   |     ^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   |     ^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     extern crate core as x;
+   |
 
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/unnecessary-extern-crate.rs:44:9
    |
 LL |         extern crate core;
-   |         ^^^^^^^^^^^^^^^^^^ help: remove it
+   |         ^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -         extern crate core;
+   |
 
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/unnecessary-extern-crate.rs:48:9
    |
 LL |         extern crate core as x;
-   |         ^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   |         ^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -         extern crate core as x;
+   |
 
 error: aborting due to 6 previous errors
 
diff --git a/tests/ui/lint/unused/lint-unused-extern-crate.rs b/tests/ui/lint/unused/lint-unused-extern-crate.rs
index 58ce3a4f55c7..be506d85caf6 100644
--- a/tests/ui/lint/unused/lint-unused-extern-crate.rs
+++ b/tests/ui/lint/unused/lint-unused-extern-crate.rs
@@ -8,7 +8,7 @@
 #![allow(unused_variables)]
 #![allow(deprecated)]
 
-extern crate lint_unused_extern_crate5; //~ ERROR: unused extern crate
+extern crate lint_unused_extern_crate5; //~ ERROR: unused `extern crate`
 
 pub extern crate lint_unused_extern_crate4; // no error, it is re-exported
 
@@ -26,7 +26,7 @@ use other::*;
 
 mod foo {
     // Test that this is unused even though an earlier `extern crate` is used.
-    extern crate lint_unused_extern_crate2; //~ ERROR unused extern crate
+    extern crate lint_unused_extern_crate2; //~ ERROR unused `extern crate`
 }
 
 fn main() {
diff --git a/tests/ui/lint/unused/lint-unused-extern-crate.stderr b/tests/ui/lint/unused/lint-unused-extern-crate.stderr
index 46d8f3beeab4..c4ee8ed1d944 100644
--- a/tests/ui/lint/unused/lint-unused-extern-crate.stderr
+++ b/tests/ui/lint/unused/lint-unused-extern-crate.stderr
@@ -1,20 +1,31 @@
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/lint-unused-extern-crate.rs:11:1
    |
 LL | extern crate lint_unused_extern_crate5;
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/lint-unused-extern-crate.rs:7:9
    |
 LL | #![deny(unused_extern_crates)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: remove the unused `extern crate`
+   |
+LL - extern crate lint_unused_extern_crate5;
+LL +
+   |
 
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/lint-unused-extern-crate.rs:29:5
    |
 LL |     extern crate lint_unused_extern_crate2;
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     extern crate lint_unused_extern_crate2;
+LL +
+   |
 
 error: aborting due to 2 previous errors
 
diff --git a/tests/ui/proc-macro/no-macro-use-attr.rs b/tests/ui/proc-macro/no-macro-use-attr.rs
index d44f51bfd8d4..03e01ee33d60 100644
--- a/tests/ui/proc-macro/no-macro-use-attr.rs
+++ b/tests/ui/proc-macro/no-macro-use-attr.rs
@@ -4,7 +4,7 @@
 #![warn(unused_extern_crates)]
 
 extern crate test_macros;
-//~^ WARN unused extern crate
+//~^ WARN unused `extern crate`
 
 #[rustc_error]
 fn main() {} //~ ERROR fatal error triggered by #[rustc_error]
diff --git a/tests/ui/proc-macro/no-macro-use-attr.stderr b/tests/ui/proc-macro/no-macro-use-attr.stderr
index 3dda3cc7d5a5..dcc8c5333412 100644
--- a/tests/ui/proc-macro/no-macro-use-attr.stderr
+++ b/tests/ui/proc-macro/no-macro-use-attr.stderr
@@ -1,14 +1,18 @@
-warning: unused extern crate
+warning: unused `extern crate`
   --> $DIR/no-macro-use-attr.rs:6:1
    |
 LL | extern crate test_macros;
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/no-macro-use-attr.rs:4:9
    |
 LL | #![warn(unused_extern_crates)]
    |         ^^^^^^^^^^^^^^^^^^^^
+help: remove the unused `extern crate`
+   |
+LL - extern crate test_macros;
+   |
 
 error: fatal error triggered by #[rustc_error]
   --> $DIR/no-macro-use-attr.rs:10:1
diff --git a/tests/ui/removing-extern-crate.fixed b/tests/ui/removing-extern-crate.fixed
index 477161fba804..9941337f72be 100644
--- a/tests/ui/removing-extern-crate.fixed
+++ b/tests/ui/removing-extern-crate.fixed
@@ -5,12 +5,12 @@
 
 #![warn(rust_2018_idioms)]
 
- //~ WARNING unused extern crate
- //~ WARNING unused extern crate
+ //~ WARNING unused `extern crate`
+ //~ WARNING unused `extern crate`
 
 mod another {
-     //~ WARNING unused extern crate
-     //~ WARNING unused extern crate
+     //~ WARNING unused `extern crate`
+     //~ WARNING unused `extern crate`
 }
 
 fn main() {}
diff --git a/tests/ui/removing-extern-crate.rs b/tests/ui/removing-extern-crate.rs
index 0b819482c710..2e6bf7a9ada6 100644
--- a/tests/ui/removing-extern-crate.rs
+++ b/tests/ui/removing-extern-crate.rs
@@ -5,12 +5,14 @@
 
 #![warn(rust_2018_idioms)]
 
-extern crate removing_extern_crate as foo; //~ WARNING unused extern crate
-extern crate core; //~ WARNING unused extern crate
+#[cfg_attr(test, macro_use)]
+extern crate removing_extern_crate as foo; //~ WARNING unused `extern crate`
+extern crate core; //~ WARNING unused `extern crate`
 
 mod another {
-    extern crate removing_extern_crate as foo; //~ WARNING unused extern crate
-    extern crate core; //~ WARNING unused extern crate
+    #[cfg_attr(test, macro_use)]
+    extern crate removing_extern_crate as foo; //~ WARNING unused `extern crate`
+    extern crate core; //~ WARNING unused `extern crate`
 }
 
 fn main() {}
diff --git a/tests/ui/removing-extern-crate.stderr b/tests/ui/removing-extern-crate.stderr
index 4dddf160ce27..371eca06685c 100644
--- a/tests/ui/removing-extern-crate.stderr
+++ b/tests/ui/removing-extern-crate.stderr
@@ -1,8 +1,8 @@
-warning: unused extern crate
-  --> $DIR/removing-extern-crate.rs:8:1
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate.rs:9:1
    |
 LL | extern crate removing_extern_crate as foo;
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/removing-extern-crate.rs:6:9
@@ -10,24 +10,49 @@ note: the lint level is defined here
 LL | #![warn(rust_2018_idioms)]
    |         ^^^^^^^^^^^^^^^^
    = note: `#[warn(unused_extern_crates)]` implied by `#[warn(rust_2018_idioms)]`
+help: remove the unused `extern crate`
+   |
+LL - #[cfg_attr(test, macro_use)]
+LL - extern crate removing_extern_crate as foo;
+LL +
+   |
 
-warning: unused extern crate
-  --> $DIR/removing-extern-crate.rs:9:1
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate.rs:10:1
    |
 LL | extern crate core;
-   | ^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL - extern crate core;
+LL +
+   |
 
-warning: unused extern crate
-  --> $DIR/removing-extern-crate.rs:12:5
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate.rs:14:5
    |
 LL |     extern crate removing_extern_crate as foo;
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     #[cfg_attr(test, macro_use)]
+LL -     extern crate removing_extern_crate as foo;
+LL +
+   |
 
-warning: unused extern crate
-  --> $DIR/removing-extern-crate.rs:13:5
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate.rs:15:5
    |
 LL |     extern crate core;
-   |     ^^^^^^^^^^^^^^^^^^ help: remove it
+   |     ^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     extern crate core;
+LL +
+   |
 
 warning: 4 warnings emitted
 
diff --git a/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.fixed b/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.fixed
index ca8422c03a3a..3eeacbb2c058 100644
--- a/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.fixed
+++ b/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.fixed
@@ -9,7 +9,7 @@
 #![deny(rust_2018_idioms)]
 #![allow(dead_code)]
 
-//~^ ERROR unused extern crate
+//~^ ERROR unused `extern crate`
 
 // Shouldn't suggest changing to `use`, as `bar`
 // would no longer be added to the prelude which could cause
diff --git a/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.rs b/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.rs
index 717e1a039825..b962bc8fdefe 100644
--- a/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.rs
+++ b/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.rs
@@ -10,7 +10,7 @@
 #![allow(dead_code)]
 
 extern crate edition_lint_paths;
-//~^ ERROR unused extern crate
+//~^ ERROR unused `extern crate`
 
 // Shouldn't suggest changing to `use`, as `bar`
 // would no longer be added to the prelude which could cause
diff --git a/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.stderr b/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.stderr
index a68d99c14cea..6561b49e6019 100644
--- a/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.stderr
+++ b/tests/ui/rust-2018/extern-crate-idiomatic-in-2018.stderr
@@ -1,8 +1,8 @@
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/extern-crate-idiomatic-in-2018.rs:12:1
    |
 LL | extern crate edition_lint_paths;
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/extern-crate-idiomatic-in-2018.rs:9:9
@@ -10,6 +10,10 @@ note: the lint level is defined here
 LL | #![deny(rust_2018_idioms)]
    |         ^^^^^^^^^^^^^^^^
    = note: `#[deny(unused_extern_crates)]` implied by `#[deny(rust_2018_idioms)]`
+help: remove the unused `extern crate`
+   |
+LL - extern crate edition_lint_paths;
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed b/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed
index 878d1dc72ccb..e82422be5ccc 100644
--- a/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed
+++ b/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.fixed
@@ -8,6 +8,6 @@
 
 // The suggestion span should include the attribute.
 
-//~^ ERROR unused extern crate
+//~^ ERROR unused `extern crate`
 
 fn main() {}
diff --git a/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs b/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs
index 573942bd0955..57a05631cc8f 100644
--- a/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs
+++ b/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.rs
@@ -8,8 +8,8 @@
 
 // The suggestion span should include the attribute.
 
-#[cfg(not(FALSE))] //~ HELP remove it
+#[cfg(not(FALSE))] //~ HELP remove
 extern crate edition_lint_paths;
-//~^ ERROR unused extern crate
+//~^ ERROR unused `extern crate`
 
 fn main() {}
diff --git a/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr b/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr
index 038a9dd967b7..817ed89bc89b 100644
--- a/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr
+++ b/tests/ui/rust-2018/issue-54400-unused-extern-crate-attr-span.stderr
@@ -1,11 +1,8 @@
-error: unused extern crate
+error: unused `extern crate`
   --> $DIR/issue-54400-unused-extern-crate-attr-span.rs:12:1
    |
-LL | / #[cfg(not(FALSE))]
-LL | | extern crate edition_lint_paths;
-   | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
-   | |________________________________|
-   |                                  help: remove it
+LL | extern crate edition_lint_paths;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/issue-54400-unused-extern-crate-attr-span.rs:6:9
@@ -13,6 +10,11 @@ note: the lint level is defined here
 LL | #![deny(rust_2018_idioms)]
    |         ^^^^^^^^^^^^^^^^
    = note: `#[deny(unused_extern_crates)]` implied by `#[deny(rust_2018_idioms)]`
+help: remove the unused `extern crate`
+   |
+LL - #[cfg(not(FALSE))]
+LL - extern crate edition_lint_paths;
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/rust-2018/remove-extern-crate.fixed b/tests/ui/rust-2018/remove-extern-crate.fixed
index 19b1dc6fb013..0952c3b2aa9e 100644
--- a/tests/ui/rust-2018/remove-extern-crate.fixed
+++ b/tests/ui/rust-2018/remove-extern-crate.fixed
@@ -8,7 +8,7 @@
 #![allow(dropping_copy_types)]
 #![allow(unused_imports)]
 
- //~ WARNING unused extern crate
+ //~ WARNING unused `extern crate`
 // Shouldn't suggest changing to `use`, as `another_name`
 // would no longer be added to the prelude which could cause
 // compilation errors for imports that use `another_name` in other
diff --git a/tests/ui/rust-2018/remove-extern-crate.rs b/tests/ui/rust-2018/remove-extern-crate.rs
index 88ef858da147..59a3d4343c89 100644
--- a/tests/ui/rust-2018/remove-extern-crate.rs
+++ b/tests/ui/rust-2018/remove-extern-crate.rs
@@ -8,7 +8,7 @@
 #![allow(dropping_copy_types)]
 #![allow(unused_imports)]
 
-extern crate core; //~ WARNING unused extern crate
+extern crate core; //~ WARNING unused `extern crate`
 // Shouldn't suggest changing to `use`, as `another_name`
 // would no longer be added to the prelude which could cause
 // compilation errors for imports that use `another_name` in other
diff --git a/tests/ui/rust-2018/remove-extern-crate.stderr b/tests/ui/rust-2018/remove-extern-crate.stderr
index cb090c621e9f..4c8fa017cf83 100644
--- a/tests/ui/rust-2018/remove-extern-crate.stderr
+++ b/tests/ui/rust-2018/remove-extern-crate.stderr
@@ -1,8 +1,8 @@
-warning: unused extern crate
+warning: unused `extern crate`
   --> $DIR/remove-extern-crate.rs:11:1
    |
 LL | extern crate core;
-   | ^^^^^^^^^^^^^^^^^^ help: remove it
+   | ^^^^^^^^^^^^^^^^^^ unused
    |
 note: the lint level is defined here
   --> $DIR/remove-extern-crate.rs:7:9
@@ -10,6 +10,11 @@ note: the lint level is defined here
 LL | #![warn(rust_2018_idioms)]
    |         ^^^^^^^^^^^^^^^^
    = note: `#[warn(unused_extern_crates)]` implied by `#[warn(rust_2018_idioms)]`
+help: remove the unused `extern crate`
+   |
+LL - extern crate core;
+LL +
+   |
 
 warning: `extern crate` is not idiomatic in the new edition
   --> $DIR/remove-extern-crate.rs:35:5

From 5a8cffadefb975a681a64894209cf80138b67f7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Tue, 3 Dec 2024 21:39:35 +0000
Subject: [PATCH 2/4] Use the attribute placeholder if the `cfg_attr` couldn't
 be parsed

---
 compiler/rustc_expand/src/config.rs           | 43 ++++++-----
 .../removing-extern-crate-malformed-cfg.fixed | 15 ++++
 .../ui/removing-extern-crate-malformed-cfg.rs | 17 +++++
 ...removing-extern-crate-malformed-cfg.stderr | 76 +++++++++++++++++++
 4 files changed, 131 insertions(+), 20 deletions(-)
 create mode 100644 tests/ui/removing-extern-crate-malformed-cfg.fixed
 create mode 100644 tests/ui/removing-extern-crate-malformed-cfg.rs
 create mode 100644 tests/ui/removing-extern-crate-malformed-cfg.stderr

diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index fae60613d0f8..7d34c45efbba 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -265,6 +265,27 @@ impl<'a> StripUnconfigured<'a> {
         }
     }
 
+    /// `cfg` and `cfg_attr` gets replaced with an inert `rustc_cfg_placeholder` to keep the
+    /// attribute "spot" in the AST. This allows suggestions to remove an item to provide a
+    /// correct suggestion when `#[cfg_attr]`s are present.
+    fn mk_placeholder(&self, cfg_attr: &ast::Attribute) -> ast::Attribute {
+        let item = ast::AttrItem {
+            unsafety: ast::Safety::Default,
+            path: ast::Path::from_ident(rustc_span::symbol::Ident::with_dummy_span(
+                sym::rustc_cfg_placeholder,
+            )),
+            args: ast::AttrArgs::Empty,
+            tokens: None,
+        };
+        let kind = ast::AttrKind::Normal(P(ast::NormalAttr { item, tokens: None }));
+        ast::Attribute {
+            kind,
+            id: self.sess.psess.attr_id_generator.mk_attr_id(),
+            style: cfg_attr.style,
+            span: cfg_attr.span,
+        }
+    }
+
     /// Parse and expand a single `cfg_attr` attribute into a list of attributes
     /// when the configuration predicate is true, or otherwise expand into an
     /// empty list of attributes.
@@ -278,7 +299,7 @@ impl<'a> StripUnconfigured<'a> {
         let Some((cfg_predicate, expanded_attrs)) =
             rustc_parse::parse_cfg_attr(cfg_attr, &self.sess.psess)
         else {
-            return vec![];
+            return vec![self.mk_placeholder(cfg_attr)];
         };
 
         // Lint on zero attributes in source.
@@ -292,25 +313,7 @@ impl<'a> StripUnconfigured<'a> {
         }
 
         if !attr::cfg_matches(&cfg_predicate, &self.sess, self.lint_node_id, self.features) {
-            // `cfg` and `cfg_attr` gets replaced with an inert `rustc_cfg_placeholder` to keep the
-            // attribute "spot" in the AST. This allows suggestions to remove an item to provide a
-            // correct suggestion when `#[cfg_attr]`s are present.
-            let item = ast::AttrItem {
-                unsafety: ast::Safety::Default,
-                path: ast::Path::from_ident(rustc_span::symbol::Ident::with_dummy_span(
-                    sym::rustc_cfg_placeholder,
-                )),
-                args: ast::AttrArgs::Empty,
-                tokens: None,
-            };
-            let kind = ast::AttrKind::Normal(P(ast::NormalAttr { item, tokens: None }));
-            let attr: ast::Attribute = ast::Attribute {
-                kind,
-                id: self.sess.psess.attr_id_generator.mk_attr_id(),
-                style: ast::AttrStyle::Outer,
-                span: cfg_attr.span,
-            };
-            return vec![attr];
+            return vec![self.mk_placeholder(cfg_attr)];
         }
 
         if recursive {
diff --git a/tests/ui/removing-extern-crate-malformed-cfg.fixed b/tests/ui/removing-extern-crate-malformed-cfg.fixed
new file mode 100644
index 000000000000..df9ca180a2dd
--- /dev/null
+++ b/tests/ui/removing-extern-crate-malformed-cfg.fixed
@@ -0,0 +1,15 @@
+//@ edition:2018
+//@ aux-build:removing-extern-crate.rs
+//@ run-rustfix
+
+#![warn(rust_2018_idioms)]
+
+ //~ WARNING unused `extern crate`
+ //~ WARNING unused `extern crate`
+
+mod another {
+     //~ WARNING unused `extern crate`
+     //~ WARNING unused `extern crate`
+}
+
+fn main() {}
diff --git a/tests/ui/removing-extern-crate-malformed-cfg.rs b/tests/ui/removing-extern-crate-malformed-cfg.rs
new file mode 100644
index 000000000000..bf362f157537
--- /dev/null
+++ b/tests/ui/removing-extern-crate-malformed-cfg.rs
@@ -0,0 +1,17 @@
+//@ edition:2018
+//@ aux-build:removing-extern-crate.rs
+//@ run-rustfix
+
+#![warn(rust_2018_idioms)]
+
+#[cfg_attr(test, "macro_use")] //~ ERROR expected
+extern crate removing_extern_crate as foo; //~ WARNING unused `extern crate`
+extern crate core; //~ WARNING unused `extern crate`
+
+mod another {
+    #[cfg_attr(test)] //~ ERROR expected
+    extern crate removing_extern_crate as foo; //~ WARNING unused `extern crate`
+    extern crate core; //~ WARNING unused `extern crate`
+}
+
+fn main() {}
diff --git a/tests/ui/removing-extern-crate-malformed-cfg.stderr b/tests/ui/removing-extern-crate-malformed-cfg.stderr
new file mode 100644
index 000000000000..4ab02ef8fc1e
--- /dev/null
+++ b/tests/ui/removing-extern-crate-malformed-cfg.stderr
@@ -0,0 +1,76 @@
+error: expected identifier, found `"macro_use"`
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:7:18
+   |
+LL | #[cfg_attr(test, "macro_use")]
+   |                  ^^^^^^^^^^^ expected identifier
+   |
+   = help: the valid syntax is `#[cfg_attr(condition, attribute, other_attribute, ...)]`
+   = note: for more information, visit <https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute>
+
+error: expected one of `(`, `,`, `::`, or `=`, found `<eof>`
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:12:16
+   |
+LL |     #[cfg_attr(test)]
+   |                ^^^^ expected one of `(`, `,`, `::`, or `=`
+   |
+   = help: the valid syntax is `#[cfg_attr(condition, attribute, other_attribute, ...)]`
+   = note: for more information, visit <https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute>
+
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:8:1
+   |
+LL | extern crate removing_extern_crate as foo;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+note: the lint level is defined here
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:5:9
+   |
+LL | #![warn(rust_2018_idioms)]
+   |         ^^^^^^^^^^^^^^^^
+   = note: `#[warn(unused_extern_crates)]` implied by `#[warn(rust_2018_idioms)]`
+help: remove the unused `extern crate`
+   |
+LL - #[cfg_attr(test, "macro_use")]
+LL - extern crate removing_extern_crate as foo;
+LL +
+   |
+
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:9:1
+   |
+LL | extern crate core;
+   | ^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL - extern crate core;
+LL +
+   |
+
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:13:5
+   |
+LL |     extern crate removing_extern_crate as foo;
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     #[cfg_attr(test)]
+LL -     extern crate removing_extern_crate as foo;
+LL +
+   |
+
+warning: unused `extern crate`
+  --> $DIR/removing-extern-crate-malformed-cfg.rs:14:5
+   |
+LL |     extern crate core;
+   |     ^^^^^^^^^^^^^^^^^^ unused
+   |
+help: remove the unused `extern crate`
+   |
+LL -     extern crate core;
+LL +
+   |
+
+error: aborting due to 2 previous errors; 4 warnings emitted
+

From 9985925088e80292ccad0311883f091387eca362 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Tue, 3 Dec 2024 23:29:09 +0000
Subject: [PATCH 3/4] Make clippy account for `rustc_cfg_placeholder`

Avoid "duplicated attribute" lints firing on the cfg placeholder.
---
 .../clippy/clippy_lints/src/attrs/duplicated_attributes.rs | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs b/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs
index 2ddbc7a6a76d..0f3888b07965 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs
@@ -36,7 +36,12 @@ fn check_duplicated_attr(
     }
     let Some(ident) = attr.ident() else { return };
     let name = ident.name;
-    if name == sym::doc || name == sym::cfg_attr || name == sym::rustc_on_unimplemented || name == sym::reason {
+    if name == sym::doc
+        || name == sym::cfg_attr
+        || name == sym::rustc_on_unimplemented
+        || name == sym::reason
+        || name == sym::rustc_cfg_placeholder
+    {
         // FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
         // conditions are the same.
         // `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.

From 59e4c2c5e069e07698217370c57af1ad00861c75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Tue, 10 Dec 2024 22:01:43 +0000
Subject: [PATCH 4/4] Add test for rustc-cfg-placeholder interaction with
 proc-macro

Ensure that the cfg-placeholder isn't visible by proc-macros.
---
 .../proc-macro/auxiliary/cfg-placeholder.rs   | 31 +++++++++++++++++++
 ...ttr-placeholder-invisible-to-proc-macro.rs | 13 ++++++++
 2 files changed, 44 insertions(+)
 create mode 100644 tests/ui/proc-macro/auxiliary/cfg-placeholder.rs
 create mode 100644 tests/ui/proc-macro/cfg-attr-placeholder-invisible-to-proc-macro.rs

diff --git a/tests/ui/proc-macro/auxiliary/cfg-placeholder.rs b/tests/ui/proc-macro/auxiliary/cfg-placeholder.rs
new file mode 100644
index 000000000000..0aaf56293a9f
--- /dev/null
+++ b/tests/ui/proc-macro/auxiliary/cfg-placeholder.rs
@@ -0,0 +1,31 @@
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+#[proc_macro_attribute]
+pub fn my_proc_macro(_: TokenStream, input: TokenStream) -> TokenStream {
+    if format!("{input:#?}").contains("my_attr1") {
+        panic!("found gated attribute my_attr1");
+    }
+    if format!("{input:#?}").contains("placeholder") {
+        panic!("found placeholder attribute");
+    }
+    if !format!("{input:#?}").contains("my_attr2") {
+        panic!("didn't if gated my_attr2");
+    }
+    input
+}
+
+#[proc_macro_attribute]
+pub fn my_attr1(_: TokenStream, input: TokenStream) -> TokenStream {
+    panic!("my_attr1 was called");
+    input
+}
+
+#[proc_macro_attribute]
+pub fn my_attr2(_: TokenStream, input: TokenStream) -> TokenStream {
+    if format!("{input:#?}").contains("my_attr1") {
+        panic!("found gated attribute my_attr1");
+    }
+    input
+}
diff --git a/tests/ui/proc-macro/cfg-attr-placeholder-invisible-to-proc-macro.rs b/tests/ui/proc-macro/cfg-attr-placeholder-invisible-to-proc-macro.rs
new file mode 100644
index 000000000000..62cac7878836
--- /dev/null
+++ b/tests/ui/proc-macro/cfg-attr-placeholder-invisible-to-proc-macro.rs
@@ -0,0 +1,13 @@
+// Ensure that `rustc-cfg-placeholder` isn't visible to proc-macros.
+//@proc-macro: cfg-placeholder.rs
+//@check-pass
+#![feature(cfg_eval)]
+#[macro_use] extern crate cfg_placeholder;
+
+#[cfg_eval]
+#[my_proc_macro]
+#[cfg_attr(FALSE, my_attr1)]
+#[cfg_attr(all(), my_attr2)]
+struct S {}
+
+fn main() {}