diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index e681d55cf94b8..07140f71aebaa 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -76,6 +76,12 @@ declare_lint! { "detects unreachable patterns" } +declare_lint! { + pub UNUSED_MACROS, + Warn, + "detects macros that were not used" +} + declare_lint! { pub WARNINGS, Warn, @@ -259,6 +265,7 @@ impl LintPass for HardwiredLints { DEAD_CODE, UNREACHABLE_CODE, UNREACHABLE_PATTERNS, + UNUSED_MACROS, WARNINGS, UNUSED_FEATURES, STABLE_FEATURES, diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 8fddbe110b0e6..eff5d2b2f37f1 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -699,6 +699,8 @@ pub fn phase_2_configure_and_expand(sess: &Session, let krate = ecx.monotonic_expander().expand_crate(krate); + ecx.check_unused_macros(); + let mut missing_fragment_specifiers: Vec<_> = ecx.parse_sess.missing_fragment_specifiers.borrow().iter().cloned().collect(); missing_fragment_specifiers.sort(); diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs index 2d0b5a6a51c6b..479c7206cb4cb 100644 --- a/src/librustc_lint/lib.rs +++ b/src/librustc_lint/lib.rs @@ -171,7 +171,8 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) { UNUSED_MUST_USE, UNUSED_UNSAFE, PATH_STATEMENTS, - UNUSED_ATTRIBUTES); + UNUSED_ATTRIBUTES, + UNUSED_MACROS); // Guidelines for creating a future incompatibility lint: // diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index c4512cb38c4e2..f78dd4890e2ce 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -1195,6 +1195,12 @@ pub struct Resolver<'a> { pub whitelisted_legacy_custom_derives: Vec, pub found_unresolved_macro: bool, + // List of macros that we need to warn about as being unused. + // The bool is true if the macro is unused, and false if its used. + // Setting a bool to false should be much faster than removing a single + // element from a FxHashSet. + unused_macros: FxHashMap, + // Maps the `Mark` of an expansion to its containing module or block. invocations: FxHashMap>, @@ -1400,6 +1406,7 @@ impl<'a> Resolver<'a> { potentially_unused_imports: Vec::new(), struct_constructors: DefIdMap(), found_unresolved_macro: false, + unused_macros: FxHashMap(), } } diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index c08421cb9374e..29ca163b0b4b7 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -16,7 +16,7 @@ use resolve_imports::ImportResolver; use rustc::hir::def_id::{DefId, BUILTIN_MACROS_CRATE, CRATE_DEF_INDEX, DefIndex}; use rustc::hir::def::{Def, Export}; use rustc::hir::map::{self, DefCollector}; -use rustc::ty; +use rustc::{ty, lint}; use syntax::ast::{self, Name, Ident}; use syntax::attr::{self, HasAttrs}; use syntax::errors::DiagnosticBuilder; @@ -291,12 +291,35 @@ impl<'a> base::Resolver for Resolver<'a> { }, }; self.macro_defs.insert(invoc.expansion_data.mark, def.def_id()); + self.unused_macros.get_mut(&def.def_id()).map(|m| *m = false); Ok(Some(self.get_macro(def))) } fn resolve_macro(&mut self, scope: Mark, path: &ast::Path, kind: MacroKind, force: bool) -> Result, Determinacy> { - self.resolve_macro_to_def(scope, path, kind, force).map(|def| self.get_macro(def)) + self.resolve_macro_to_def(scope, path, kind, force).map(|def| { + self.unused_macros.get_mut(&def.def_id()).map(|m| *m = false); + self.get_macro(def) + }) + } + + fn check_unused_macros(&self) { + for (did, _) in self.unused_macros.iter().filter(|&(_, b)| *b) { + let span = match *self.macro_map[did] { + SyntaxExtension::NormalTT(_, sp, _) => sp, + SyntaxExtension::IdentTT(_, sp, _) => sp, + _ => None + }; + if let Some(span) = span { + let lint = lint::builtin::UNUSED_MACROS; + let msg = "unused macro".to_string(); + // We are using CRATE_NODE_ID here even though its inaccurate, as we + // sadly don't have the NodeId of the macro definition. + self.session.add_lint(lint, ast::CRATE_NODE_ID, span, msg); + } else { + bug!("attempted to create unused macro error, but span not available"); + } + } } } @@ -687,6 +710,8 @@ impl<'a> Resolver<'a> { if attr::contains_name(&item.attrs, "macro_export") { let def = Def::Macro(def_id, MacroKind::Bang); self.macro_exports.push(Export { name: ident.name, def: def, span: item.span }); + } else { + self.unused_macros.insert(def_id, true); } } diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index f731c5abdd6a1..b0253ec3905c9 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -589,6 +589,7 @@ pub trait Resolver { -> Result>, Determinacy>; fn resolve_macro(&mut self, scope: Mark, path: &ast::Path, kind: MacroKind, force: bool) -> Result, Determinacy>; + fn check_unused_macros(&self); } #[derive(Copy, Clone, Debug)] @@ -618,6 +619,7 @@ impl Resolver for DummyResolver { _force: bool) -> Result, Determinacy> { Err(Determinacy::Determined) } + fn check_unused_macros(&self) {} } #[derive(Clone)] @@ -800,6 +802,10 @@ impl<'a> ExtCtxt<'a> { pub fn name_of(&self, st: &str) -> ast::Name { Symbol::intern(st) } + + pub fn check_unused_macros(&self) { + self.resolver.check_unused_macros(); + } } /// Extract a string literal from the macro expanded version of `expr`,