about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_expand/src/base.rs2
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs40
-rw-r--r--compiler/rustc_resolve/Cargo.toml1
-rw-r--r--compiler/rustc_resolve/src/build_reduced_graph.rs26
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs14
-rw-r--r--compiler/rustc_resolve/src/diagnostics/tests.rs40
-rw-r--r--compiler/rustc_resolve/src/lib.rs2
-rw-r--r--compiler/rustc_resolve/src/macros.rs41
8 files changed, 139 insertions, 27 deletions
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index ae1b50a4176..3d28da684d1 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -887,6 +887,8 @@ pub trait ResolverExpand {
         force: bool,
     ) -> Result<Lrc<SyntaxExtension>, Indeterminate>;
 
+    fn record_macro_rule_usage(&mut self, mac_id: NodeId, rule_index: usize);
+
     fn check_unused_macros(&mut self);
 
     // Resolver interfaces for specific built-in macros.
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 050710097c3..ba0b35470b6 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -156,13 +156,13 @@ impl<'a> ParserAnyMacro<'a> {
 }
 
 struct MacroRulesMacroExpander {
+    node_id: NodeId,
     name: Ident,
     span: Span,
     transparency: Transparency,
     lhses: Vec<Vec<MatcherLoc>>,
     rhses: Vec<mbe::TokenTree>,
     valid: bool,
-    is_local: bool,
 }
 
 impl TTMacroExpander for MacroRulesMacroExpander {
@@ -179,12 +179,12 @@ impl TTMacroExpander for MacroRulesMacroExpander {
             cx,
             sp,
             self.span,
+            self.node_id,
             self.name,
             self.transparency,
             input,
             &self.lhses,
             &self.rhses,
-            self.is_local,
         )
     }
 }
@@ -207,14 +207,17 @@ fn generic_extension<'cx, 'tt>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     def_span: Span,
+    node_id: NodeId,
     name: Ident,
     transparency: Transparency,
     arg: TokenStream,
     lhses: &'tt [Vec<MatcherLoc>],
     rhses: &'tt [mbe::TokenTree],
-    is_local: bool,
 ) -> Box<dyn MacResult + 'cx> {
     let sess = &cx.sess.parse_sess;
+    // Macros defined in the current crate have a real node id,
+    // whereas macros from an external crate have a dummy id.
+    let is_local = node_id != DUMMY_NODE_ID;
 
     if cx.trace_macros() {
         let msg = format!("expanding `{}! {{ {} }}`", name, pprust::tts_to_string(&arg));
@@ -296,6 +299,10 @@ fn generic_extension<'cx, 'tt>(
                 let mut p = Parser::new(sess, tts, false, None);
                 p.last_type_ascription = cx.current_expansion.prior_type_ascription;
 
+                if is_local {
+                    cx.resolver.record_macro_rule_usage(node_id, i);
+                }
+
                 // Let the context choose how to interpret the result.
                 // Weird, but useful for X-macros.
                 return Box::new(ParserAnyMacro {
@@ -372,7 +379,7 @@ pub fn compile_declarative_macro(
     features: &Features,
     def: &ast::Item,
     edition: Edition,
-) -> SyntaxExtension {
+) -> (SyntaxExtension, Vec<Span>) {
     debug!("compile_declarative_macro: {:?}", def);
     let mk_syn_ext = |expander| {
         SyntaxExtension::new(
@@ -385,6 +392,7 @@ pub fn compile_declarative_macro(
             &def.attrs,
         )
     };
+    let dummy_syn_ext = || (mk_syn_ext(Box::new(macro_rules_dummy_expander)), Vec::new());
 
     let diag = &sess.parse_sess.span_diagnostic;
     let lhs_nm = Ident::new(sym::lhs, def.span);
@@ -445,17 +453,17 @@ pub fn compile_declarative_macro(
             let s = parse_failure_msg(&token);
             let sp = token.span.substitute_dummy(def.span);
             sess.parse_sess.span_diagnostic.struct_span_err(sp, &s).span_label(sp, msg).emit();
-            return mk_syn_ext(Box::new(macro_rules_dummy_expander));
+            return dummy_syn_ext();
         }
         Error(sp, msg) => {
             sess.parse_sess
                 .span_diagnostic
                 .struct_span_err(sp.substitute_dummy(def.span), &msg)
                 .emit();
-            return mk_syn_ext(Box::new(macro_rules_dummy_expander));
+            return dummy_syn_ext();
         }
         ErrorReported => {
-            return mk_syn_ext(Box::new(macro_rules_dummy_expander));
+            return dummy_syn_ext();
         }
     };
 
@@ -530,6 +538,15 @@ pub fn compile_declarative_macro(
         None => {}
     }
 
+    // Compute the spans of the macro rules
+    // We only take the span of the lhs here,
+    // so that the spans of created warnings are smaller.
+    let rule_spans = if def.id != DUMMY_NODE_ID {
+        lhses.iter().map(|lhs| lhs.span()).collect::<Vec<_>>()
+    } else {
+        Vec::new()
+    };
+
     // Convert the lhses into `MatcherLoc` form, which is better for doing the
     // actual matching. Unless the matcher is invalid.
     let lhses = if valid {
@@ -549,17 +566,16 @@ pub fn compile_declarative_macro(
         vec![]
     };
 
-    mk_syn_ext(Box::new(MacroRulesMacroExpander {
+    let expander = Box::new(MacroRulesMacroExpander {
         name: def.ident,
         span: def.span,
+        node_id: def.id,
         transparency,
         lhses,
         rhses,
         valid,
-        // Macros defined in the current crate have a real node id,
-        // whereas macros from an external crate have a dummy id.
-        is_local: def.id != DUMMY_NODE_ID,
-    }))
+    });
+    (mk_syn_ext(expander), rule_spans)
 }
 
 fn check_lhs_nt_follows(sess: &ParseSess, def: &ast::Item, lhs: &mbe::TokenTree) -> bool {
diff --git a/compiler/rustc_resolve/Cargo.toml b/compiler/rustc_resolve/Cargo.toml
index bd27c16c732..b2178ff5995 100644
--- a/compiler/rustc_resolve/Cargo.toml
+++ b/compiler/rustc_resolve/Cargo.toml
@@ -4,7 +4,6 @@ version = "0.0.0"
 edition = "2021"
 
 [lib]
-test = false
 doctest = false
 
 [dependencies]
diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs
index 783ff5a3f91..e68d6fdeea5 100644
--- a/compiler/rustc_resolve/src/build_reduced_graph.rs
+++ b/compiler/rustc_resolve/src/build_reduced_graph.rs
@@ -194,7 +194,7 @@ impl<'a> Resolver<'a> {
         }
 
         let ext = Lrc::new(match self.cstore().load_macro_untracked(def_id, &self.session) {
-            LoadedMacro::MacroDef(item, edition) => self.compile_macro(&item, edition),
+            LoadedMacro::MacroDef(item, edition) => self.compile_macro(&item, edition).0,
             LoadedMacro::ProcMacro(ext) => ext,
         });
 
@@ -1218,9 +1218,18 @@ impl<'a, 'b> BuildReducedGraphVisitor<'a, 'b> {
     // Mark the given macro as unused unless its name starts with `_`.
     // Macro uses will remove items from this set, and the remaining
     // items will be reported as `unused_macros`.
-    fn insert_unused_macro(&mut self, ident: Ident, def_id: LocalDefId, node_id: NodeId) {
+    fn insert_unused_macro(
+        &mut self,
+        ident: Ident,
+        def_id: LocalDefId,
+        node_id: NodeId,
+        rule_spans: &[Span],
+    ) {
         if !ident.as_str().starts_with('_') {
             self.r.unused_macros.insert(def_id, (node_id, ident));
+            for (rule_i, rule_span) in rule_spans.iter().enumerate() {
+                self.r.unused_macro_rules.insert((def_id, rule_i), (ident, *rule_span));
+            }
         }
     }
 
@@ -1228,15 +1237,16 @@ impl<'a, 'b> BuildReducedGraphVisitor<'a, 'b> {
         let parent_scope = self.parent_scope;
         let expansion = parent_scope.expansion;
         let def_id = self.r.local_def_id(item.id);
-        let (ext, ident, span, macro_rules) = match &item.kind {
+        let (ext, ident, span, macro_rules, rule_spans) = match &item.kind {
             ItemKind::MacroDef(def) => {
-                let ext = Lrc::new(self.r.compile_macro(item, self.r.session.edition()));
-                (ext, item.ident, item.span, def.macro_rules)
+                let (ext, rule_spans) = self.r.compile_macro(item, self.r.session.edition());
+                let ext = Lrc::new(ext);
+                (ext, item.ident, item.span, def.macro_rules, rule_spans)
             }
             ItemKind::Fn(..) => match self.proc_macro_stub(item) {
                 Some((macro_kind, ident, span)) => {
                     self.r.proc_macro_stubs.insert(def_id);
-                    (self.r.dummy_ext(macro_kind), ident, span, false)
+                    (self.r.dummy_ext(macro_kind), ident, span, false, Vec::new())
                 }
                 None => return parent_scope.macro_rules,
             },
@@ -1264,7 +1274,7 @@ impl<'a, 'b> BuildReducedGraphVisitor<'a, 'b> {
                 self.r.define(module, ident, MacroNS, (res, vis, span, expansion, IsMacroExport));
             } else {
                 self.r.check_reserved_macro_name(ident, res);
-                self.insert_unused_macro(ident, def_id, item.id);
+                self.insert_unused_macro(ident, def_id, item.id, &rule_spans);
             }
             self.r.visibilities.insert(def_id, vis);
             let scope = self.r.arenas.alloc_macro_rules_scope(MacroRulesScope::Binding(
@@ -1287,7 +1297,7 @@ impl<'a, 'b> BuildReducedGraphVisitor<'a, 'b> {
                 _ => self.resolve_visibility(&item.vis),
             };
             if vis != ty::Visibility::Public {
-                self.insert_unused_macro(ident, def_id, item.id);
+                self.insert_unused_macro(ident, def_id, item.id, &rule_spans);
             }
             self.r.define(module, ident, MacroNS, (res, vis, span, expansion));
             self.r.visibilities.insert(def_id, vis);
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index aef9fb57a6a..bffff0dfac5 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -35,6 +35,9 @@ use crate::{LexicalScopeBinding, NameBinding, NameBindingKind, PrivacyError, Vis
 use crate::{ParentScope, PathResult, ResolutionError, Resolver, Scope, ScopeSet};
 use crate::{Segment, UseError};
 
+#[cfg(test)]
+mod tests;
+
 type Res = def::Res<ast::NodeId>;
 
 /// A vector of spans and replacements, a message and applicability.
@@ -2663,3 +2666,14 @@ fn is_span_suitable_for_use_injection(s: Span) -> bool {
     // import or other generated ones
     !s.from_expansion()
 }
+
+/// Convert the given number into the corresponding ordinal
+crate fn ordinalize(v: usize) -> String {
+    let suffix = match ((11..=13).contains(&(v % 100)), v % 10) {
+        (false, 1) => "st",
+        (false, 2) => "nd",
+        (false, 3) => "rd",
+        _ => "th",
+    };
+    format!("{v}{suffix}")
+}
diff --git a/compiler/rustc_resolve/src/diagnostics/tests.rs b/compiler/rustc_resolve/src/diagnostics/tests.rs
new file mode 100644
index 00000000000..2aa6cc61e46
--- /dev/null
+++ b/compiler/rustc_resolve/src/diagnostics/tests.rs
@@ -0,0 +1,40 @@
+use super::ordinalize;
+
+#[test]
+fn test_ordinalize() {
+    assert_eq!(ordinalize(1), "1st");
+    assert_eq!(ordinalize(2), "2nd");
+    assert_eq!(ordinalize(3), "3rd");
+    assert_eq!(ordinalize(4), "4th");
+    assert_eq!(ordinalize(5), "5th");
+    // ...
+    assert_eq!(ordinalize(10), "10th");
+    assert_eq!(ordinalize(11), "11th");
+    assert_eq!(ordinalize(12), "12th");
+    assert_eq!(ordinalize(13), "13th");
+    assert_eq!(ordinalize(14), "14th");
+    // ...
+    assert_eq!(ordinalize(20), "20th");
+    assert_eq!(ordinalize(21), "21st");
+    assert_eq!(ordinalize(22), "22nd");
+    assert_eq!(ordinalize(23), "23rd");
+    assert_eq!(ordinalize(24), "24th");
+    // ...
+    assert_eq!(ordinalize(30), "30th");
+    assert_eq!(ordinalize(31), "31st");
+    assert_eq!(ordinalize(32), "32nd");
+    assert_eq!(ordinalize(33), "33rd");
+    assert_eq!(ordinalize(34), "34th");
+    // ...
+    assert_eq!(ordinalize(7010), "7010th");
+    assert_eq!(ordinalize(7011), "7011th");
+    assert_eq!(ordinalize(7012), "7012th");
+    assert_eq!(ordinalize(7013), "7013th");
+    assert_eq!(ordinalize(7014), "7014th");
+    // ...
+    assert_eq!(ordinalize(7020), "7020th");
+    assert_eq!(ordinalize(7021), "7021st");
+    assert_eq!(ordinalize(7022), "7022nd");
+    assert_eq!(ordinalize(7023), "7023rd");
+    assert_eq!(ordinalize(7024), "7024th");
+}
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index f6109b1dc1a..7aa66efc038 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -973,6 +973,7 @@ pub struct Resolver<'a> {
     local_macro_def_scopes: FxHashMap<LocalDefId, Module<'a>>,
     ast_transform_scopes: FxHashMap<LocalExpnId, Module<'a>>,
     unused_macros: FxHashMap<LocalDefId, (NodeId, Ident)>,
+    unused_macro_rules: FxHashMap<(LocalDefId, usize), (Ident, Span)>,
     proc_macro_stubs: FxHashSet<LocalDefId>,
     /// Traces collected during macro resolution and validated when it's complete.
     single_segment_macro_resolutions:
@@ -1372,6 +1373,7 @@ impl<'a> Resolver<'a> {
             potentially_unused_imports: Vec::new(),
             struct_constructors: Default::default(),
             unused_macros: Default::default(),
+            unused_macro_rules: Default::default(),
             proc_macro_stubs: Default::default(),
             single_segment_macro_resolutions: Default::default(),
             multi_segment_macro_resolutions: Default::default(),
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index 19a9c1b99fc..2337f72f1e8 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -22,7 +22,8 @@ use rustc_hir::def::{self, DefKind, NonMacroAttrKind};
 use rustc_hir::def_id::{CrateNum, LocalDefId};
 use rustc_middle::middle::stability;
 use rustc_middle::ty::RegisteredTools;
-use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNUSED_MACROS};
+use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE};
+use rustc_session::lint::builtin::{UNUSED_MACROS, UNUSED_MACRO_RULES};
 use rustc_session::lint::BuiltinLintDiagnostics;
 use rustc_session::parse::feature_err;
 use rustc_session::Session;
@@ -311,6 +312,11 @@ impl<'a> ResolverExpand for Resolver<'a> {
         Ok(ext)
     }
 
+    fn record_macro_rule_usage(&mut self, id: NodeId, rule_i: usize) {
+        let did = self.local_def_id(id);
+        self.unused_macro_rules.remove(&(did, rule_i));
+    }
+
     fn check_unused_macros(&mut self) {
         for (_, &(node_id, ident)) in self.unused_macros.iter() {
             self.lint_buffer.buffer_lint(
@@ -320,6 +326,23 @@ impl<'a> ResolverExpand for Resolver<'a> {
                 &format!("unused macro definition: `{}`", ident.as_str()),
             );
         }
+        for (&(def_id, arm_i), &(ident, rule_span)) in self.unused_macro_rules.iter() {
+            if self.unused_macros.contains_key(&def_id) {
+                // We already lint the entire macro as unused
+                continue;
+            }
+            let node_id = self.def_id_to_node_id[def_id];
+            self.lint_buffer.buffer_lint(
+                UNUSED_MACRO_RULES,
+                node_id,
+                rule_span,
+                &format!(
+                    "{} rule of macro `{}` is never used",
+                    crate::diagnostics::ordinalize(arm_i + 1),
+                    ident.as_str()
+                ),
+            );
+        }
     }
 
     fn has_derive_copy(&self, expn_id: LocalExpnId) -> bool {
@@ -830,10 +853,15 @@ impl<'a> Resolver<'a> {
         }
     }
 
-    /// Compile the macro into a `SyntaxExtension` and possibly replace
-    /// its expander to a pre-defined one for built-in macros.
-    crate fn compile_macro(&mut self, item: &ast::Item, edition: Edition) -> SyntaxExtension {
-        let mut result = compile_declarative_macro(
+    /// Compile the macro into a `SyntaxExtension` and its rule spans.
+    ///
+    /// Possibly replace its expander to a pre-defined one for built-in macros.
+    crate fn compile_macro(
+        &mut self,
+        item: &ast::Item,
+        edition: Edition,
+    ) -> (SyntaxExtension, Vec<Span>) {
+        let (mut result, mut rule_spans) = compile_declarative_macro(
             &self.session,
             self.session.features_untracked(),
             item,
@@ -849,6 +877,7 @@ impl<'a> Resolver<'a> {
                 match mem::replace(builtin_macro, BuiltinMacroState::AlreadySeen(item.span)) {
                     BuiltinMacroState::NotYetSeen(ext) => {
                         result.kind = ext;
+                        rule_spans = Vec::new();
                         if item.id != ast::DUMMY_NODE_ID {
                             self.builtin_macro_kinds
                                 .insert(self.local_def_id(item.id), result.macro_kind());
@@ -871,6 +900,6 @@ impl<'a> Resolver<'a> {
             }
         }
 
-        result
+        (result, rule_spans)
     }
 }