#![allow(rustc::potential_query_instability)]
+#![feature(array_windows)]
#![feature(associated_type_bounds)]
#![feature(associated_type_defaults)]
#![feature(if_let_guard)]
features: &Features,
def: &ast::Item,
edition: Edition,
-) -> (SyntaxExtension, Vec<Span>) {
+) -> (SyntaxExtension, Vec<(usize, Span)>) {
debug!("compile_declarative_macro: {:?}", def);
let mk_syn_ext = |expander| {
SyntaxExtension::new(
// 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.
+ // Also, we are only interested in non-foreign macros.
let rule_spans = if def.id != DUMMY_NODE_ID {
- lhses.iter().map(|lhs| lhs.span()).collect::<Vec<_>>()
+ lhses
+ .iter()
+ .zip(rhses.iter())
+ .enumerate()
+ // If the rhs contains an invocation like compile_error!,
+ // don't consider the rule for the unused rule lint.
+ .filter(|(_idx, (_lhs, rhs))| !has_compile_error_macro(rhs))
+ .map(|(idx, (lhs, _rhs))| (idx, lhs.span()))
+ .collect::<Vec<_>>()
} else {
Vec::new()
};
err == sess.span_diagnostic.err_count()
}
+fn has_compile_error_macro(rhs: &mbe::TokenTree) -> bool {
+ match rhs {
+ mbe::TokenTree::Delimited(_sp, d) => {
+ let has_compile_error = d.tts.array_windows::<3>().any(|[ident, bang, args]| {
+ if let mbe::TokenTree::Token(ident) = ident &&
+ let TokenKind::Ident(ident, _) = ident.kind &&
+ ident == sym::compile_error &&
+ let mbe::TokenTree::Token(bang) = bang &&
+ let TokenKind::Not = bang.kind &&
+ let mbe::TokenTree::Delimited(_, del) = args &&
+ del.delim != Delimiter::Invisible
+ {
+ true
+ } else {
+ false
+ }
+ });
+ if has_compile_error { true } else { d.tts.iter().any(has_compile_error_macro) }
+ }
+ _ => false,
+ }
+}
+
// `The FirstSets` for a matcher is a mapping from subsequences in the
// matcher to the FIRST set for that subsequence.
//
ident: Ident,
def_id: LocalDefId,
node_id: NodeId,
- rule_spans: &[Span],
+ rule_spans: &[(usize, 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));
+ for (rule_i, rule_span) in rule_spans.iter() {
+ self.r.unused_macro_rules.insert((def_id, *rule_i), (ident, *rule_span));
}
}
}
&mut self,
item: &ast::Item,
edition: Edition,
- ) -> (SyntaxExtension, Vec<Span>) {
+ ) -> (SyntaxExtension, Vec<(usize, Span)>) {
let (mut result, mut rule_spans) = compile_declarative_macro(
&self.session,
self.session.features_untracked(),
--- /dev/null
+#![deny(unused_macro_rules)]
+// To make sure we are not hitting this
+#![deny(unused_macros)]
+
+macro_rules! num {
+ (one) => { 1 };
+ // Most simple (and common) case
+ (two) => { compile_error!("foo"); };
+ // Some nested use
+ (two_) => { foo(compile_error!("foo")); };
+ (three) => { 3 };
+ (four) => { 4 }; //~ ERROR: rule of macro
+}
+const _NUM: u8 = num!(one) + num!(three);
+
+// compile_error not used as a macro invocation
+macro_rules! num2 {
+ (one) => { 1 };
+ // Only identifier present
+ (two) => { fn compile_error() {} }; //~ ERROR: rule of macro
+ // Only identifier and bang present
+ (two_) => { compile_error! }; //~ ERROR: rule of macro
+ (three) => { 3 };
+}
+const _NUM2: u8 = num2!(one) + num2!(three);
+
+fn main() {}
--- /dev/null
+error: 5th rule of macro `num` is never used
+ --> $DIR/unused-macro-rules-compile-error.rs:12:5
+ |
+LL | (four) => { 4 };
+ | ^^^^^^
+ |
+note: the lint level is defined here
+ --> $DIR/unused-macro-rules-compile-error.rs:1:9
+ |
+LL | #![deny(unused_macro_rules)]
+ | ^^^^^^^^^^^^^^^^^^
+
+error: 3rd rule of macro `num2` is never used
+ --> $DIR/unused-macro-rules-compile-error.rs:22:5
+ |
+LL | (two_) => { compile_error! };
+ | ^^^^^^
+
+error: 2nd rule of macro `num2` is never used
+ --> $DIR/unused-macro-rules-compile-error.rs:20:5
+ |
+LL | (two) => { fn compile_error() {} };
+ | ^^^^^
+
+error: aborting due to 3 previous errors
+