Implement the unused_macro_rules lint
This commit is contained in:
parent
3d43be3ad3
commit
0bd2232fe4
@ -887,6 +887,8 @@ fn resolve_macro_invocation(
|
||||
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.
|
||||
|
@ -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 @@ fn expand<'cx>(
|
||||
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 {
|
||||
|
@ -4,7 +4,6 @@ version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
|
@ -194,7 +194,7 @@ pub fn expect_module(&mut self, def_id: DefId) -> Module<'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 @@ fn proc_macro_stub(&self, item: &ast::Item) -> Option<(MacroKind, Ident, Span)>
|
||||
// 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 @@ fn define_macro(&mut self, item: &ast::Item) -> MacroRulesScopeRef<'a> {
|
||||
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 @@ fn define_macro(&mut self, item: &ast::Item) -> MacroRulesScopeRef<'a> {
|
||||
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 @@ fn define_macro(&mut self, item: &ast::Item) -> MacroRulesScopeRef<'a> {
|
||||
_ => 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);
|
||||
|
@ -35,6 +35,9 @@
|
||||
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}")
|
||||
}
|
||||
|
40
compiler/rustc_resolve/src/diagnostics/tests.rs
Normal file
40
compiler/rustc_resolve/src/diagnostics/tests.rs
Normal file
@ -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");
|
||||
}
|
@ -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 @@ pub fn new(
|
||||
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(),
|
||||
|
@ -22,7 +22,8 @@
|
||||
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 @@ fn resolve_macro_invocation(
|
||||
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 @@ fn check_unused_macros(&mut self) {
|
||||
&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 @@ fn prohibit_imported_non_macro_attrs(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 @@ fn prohibit_imported_non_macro_attrs(
|
||||
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 @@ fn prohibit_imported_non_macro_attrs(
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
(result, rule_spans)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user