Skip to content

Commit 2b4fbfc

Browse files
committed
WIP auto-applicable suggestion macro-use-imports
1 parent 5deffa6 commit 2b4fbfc

File tree

4 files changed

+159
-87
lines changed

4 files changed

+159
-87
lines changed

clippy_lints/src/lib.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ extern crate rustc_target;
5353
#[allow(unused_extern_crates)]
5454
extern crate rustc_typeck;
5555

56+
#[allow(unused_extern_crates)]
57+
extern crate rustc_resolve;
58+
#[allow(unused_extern_crates)]
59+
extern crate rustc_codegen_llvm;
60+
5661
use rustc::session::Session;
5762
use rustc_data_structures::fx::FxHashSet;
5863
use rustc_lint::LintId;
@@ -1014,7 +1019,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10141019
store.register_early_pass(move || box excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools));
10151020
store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap);
10161021
store.register_late_pass(|| box wildcard_imports::WildcardImports);
1017-
store.register_early_pass(|| box macro_use::MacroUseImports::default());
1022+
// store.register_early_pass(|| box macro_use::MacroUseImports::default());
1023+
store.register_late_pass(|| box macro_use::MacroUseImports::default());
10181024

10191025
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10201026
LintId::of(&arithmetic::FLOAT_ARITHMETIC),

clippy_lints/src/macro_use.rs

+100-75
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
use crate::utils::{snippet, span_lint_and_sugg, in_macro};
1+
use crate::utils::{in_macro, snippet, span_lint_and_sugg};
22
use if_chain::if_chain;
3-
use rustc_ast::ast;
43
use rustc_data_structures::fx::FxHashMap;
54
use rustc_errors::Applicability;
6-
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext, Lint};
7-
use rustc_session::{impl_lint_pass, declare_tool_lint};
8-
use rustc_span::{edition::Edition, Span};
95
use rustc_hir as hir;
6+
use hir::def::{Res, DefKind};
7+
use rustc_lint::{LintContext, LateLintPass, LateContext};
8+
use rustc_session::{declare_tool_lint, impl_lint_pass};
9+
use rustc_span::{edition::Edition, Span};
1010

1111
const PRELUDE: &[&str] = &[
12-
"marker", "ops", "convert", "iter", "option", "result", "borrow", "boxed", "string", "vec",
13-
"macros"
12+
"marker", "ops", "convert", "iter", "option", "result", "borrow", "boxed", "string", "vec", "macros",
1413
];
1514
const BRACKETS: &[char] = &['<', '>'];
1615

@@ -34,14 +33,15 @@ declare_clippy_lint! {
3433

3534
/// MacroRefData includes the name of the macro
3635
/// and the path from `SourceMap::span_to_filename`.
36+
#[derive(Debug, Clone)]
3737
pub struct MacroRefData {
3838
name: String,
3939
path: String,
4040
}
4141

4242
impl MacroRefData {
43-
pub fn new(name: String, span: Span, ecx: &EarlyContext<'_>) -> Self {
44-
let mut path = ecx.sess.source_map().span_to_filename(span).to_string();
43+
pub fn new(name: String, span: Span, ecx: &LateContext<'_, '_>) -> Self {
44+
let mut path = ecx.sess().source_map().span_to_filename(span).to_string();
4545

4646
// std lib paths are <::std::module::file type>
4747
// so remove brackets and space
@@ -51,7 +51,10 @@ impl MacroRefData {
5151
if path.contains(' ') {
5252
path = path.split(' ').next().unwrap().to_string();
5353
}
54-
Self { name: name.to_string(), path, }
54+
Self {
55+
name: name.to_string(),
56+
path,
57+
}
5558
}
5659
}
5760

@@ -62,122 +65,144 @@ pub struct MacroUseImports {
6265
/// the span of the macro reference and the `MacroRefData`
6366
/// for the use of the macro.
6467
collected: FxHashMap<Span, MacroRefData>,
68+
mac_refs: Vec<(Span, MacroRefData)>,
6569
}
6670

67-
impl MacroUseImports {
68-
fn import_path_mac(&self, use_path: &str) -> String {
69-
for mac in self.collected.values() {
70-
if paths_match(mac, use_path) {
71-
return make_path(mac, use_path)
72-
}
73-
}
74-
format!("{}::<macro name>", use_path)
75-
}
76-
}
77-
78-
fn paths_match(mac: &MacroRefData, use_path: &str) -> bool {
79-
let segs = mac.path.split("::")
80-
.filter(|s| *s != "")
81-
.collect::<Vec<_>>();
71+
/// This is somewhat of a fallback for imports from `std::prelude` because they
72+
/// are not recognized by `LateLintPass::check_item` `lcx.tcx.item_children(id)`
73+
fn make_path(mac: &MacroRefData, use_path: &str) -> String {
74+
let segs = mac.path.split("::").filter(|s| *s != "").collect::<Vec<_>>();
8275

83-
if segs.starts_with(&["std"]) {
84-
return PRELUDE.iter().any(|m| segs.contains(m))
76+
if segs.starts_with(&["std"]) && PRELUDE.iter().any(|m| segs.contains(m)) {
77+
return format!("std::prelude::{} is imported by default, remove `use` statement", mac.name);
8578
}
86-
87-
segs.starts_with(&use_path.split("::").collect::<Vec<_>>())
88-
}
8979

90-
fn make_path(mac: &MacroRefData, use_path: &str) -> String {
9180
if use_path.split("::").count() == 1 {
9281
return format!("{}::{}", use_path, mac.name);
9382
}
9483

95-
let segs = mac.path.split("::")
96-
.filter(|s| *s != "")
97-
.collect::<Vec<_>>();
98-
99-
if segs.starts_with(&["std"]) && PRELUDE.iter().any(|m| segs.contains(m)) {
100-
return format!("std::prelude::{}", mac.name);
101-
}
102-
10384
mac.path.clone()
10485
}
10586

10687
impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]);
10788

108-
impl EarlyLintPass for MacroUseImports {
109-
fn check_item(&mut self, ecx: &EarlyContext<'_>, item: &ast::Item) {
89+
impl<'l, 'txc> LateLintPass<'l, 'txc> for MacroUseImports {
90+
fn check_item(&mut self, lcx: &LateContext<'_, '_>, item: &hir::Item<'_>) {
11091
if_chain! {
111-
if ecx.sess.opts.edition == Edition::Edition2018;
112-
if let ast::ItemKind::Use(use_tree) = &item.kind;
92+
if lcx.sess().opts.edition == Edition::Edition2018;
93+
if let hir::ItemKind::Use(path, _kind) = &item.kind;
11394
if let Some(mac_attr) = item
11495
.attrs
11596
.iter()
11697
.find(|attr| attr.ident().map(|s| s.to_string()) == Some("macro_use".to_string()));
98+
if let Res::Def(DefKind::Mod, id) = path.res;
11799
then {
118-
let import_path = snippet(ecx, use_tree.span, "_");
119-
let span = mac_attr.span.clone();
120-
self.imports.push((import_path.to_string(), span));
100+
for kid in lcx.tcx.item_children(id).iter() {
101+
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
102+
let span = mac_attr.span.clone();
103+
println!("{:#?}", lcx.tcx.def_path_str(mac_id));
104+
self.imports.push((lcx.tcx.def_path_str(mac_id), span));
105+
}
106+
}
107+
} else {
108+
if in_macro(item.span) {
109+
let call_site = item.span.source_callsite();
110+
let name = snippet(lcx, lcx.sess().source_map().span_until_char(call_site, '!'), "_");
111+
if let Some(callee) = item.span.source_callee() {
112+
if !self.collected.contains_key(&call_site) {
113+
let mac = MacroRefData::new(name.to_string(), callee.def_site, lcx);
114+
self.mac_refs.push((call_site, mac.clone()));
115+
self.collected.insert(call_site, mac);
116+
// println!("EXPR {:?} {:?}", name, lcx.sess().source_map().span_to_filename(callee.def_site));
117+
}
118+
}
119+
}
121120
}
122121
}
123122
}
124123

125-
fn check_expr(&mut self, ecx: &EarlyContext<'_>, expr: &ast::Expr) {
124+
fn check_expr(&mut self, lcx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) {
126125
if in_macro(expr.span) {
127126
let call_site = expr.span.source_callsite();
128-
let name = snippet(ecx, ecx.sess.source_map().span_until_char(call_site, '!'), "_");
127+
let name = snippet(lcx, lcx.sess().source_map().span_until_char(call_site, '!'), "_");
129128
if let Some(callee) = expr.span.source_callee() {
130-
self.collected.entry(call_site)
131-
.or_insert_with(|| {
132-
MacroRefData::new(name.to_string(), callee.def_site, ecx)
133-
});
129+
if !self.collected.contains_key(&call_site) {
130+
let mac = MacroRefData::new(name.to_string(), callee.def_site, lcx);
131+
self.mac_refs.push((call_site, mac.clone()));
132+
self.collected.insert(call_site, mac);
133+
// println!("EXPR {:?} {:?}", name, lcx.sess().source_map().span_to_filename(callee.def_site));
134+
}
134135
}
135136
}
136137
}
137-
fn check_stmt(&mut self, ecx: &EarlyContext<'_>, stmt: &ast::Stmt) {
138+
fn check_stmt(&mut self, lcx: &LateContext<'_, '_>, stmt: &hir::Stmt<'_>) {
138139
if in_macro(stmt.span) {
139140
let call_site = stmt.span.source_callsite();
140-
let name = snippet(ecx, ecx.sess.source_map().span_until_char(call_site, '!'), "_");
141+
let name = snippet(lcx, lcx.sess().source_map().span_until_char(call_site, '!'), "_");
141142
if let Some(callee) = stmt.span.source_callee() {
142-
self.collected.entry(call_site)
143-
.or_insert_with(|| {
144-
MacroRefData::new(name.to_string(), callee.def_site, ecx)
145-
});
143+
if !self.collected.contains_key(&call_site) {
144+
let mac = MacroRefData::new(name.to_string(), callee.def_site, lcx);
145+
self.mac_refs.push((call_site, mac.clone()));
146+
self.collected.insert(call_site, mac);
147+
// println!("STMT {:?} {:?}", name, lcx.sess().source_map().span_to_filename(callee.def_site));
148+
}
146149
}
147150
}
148151
}
149-
fn check_pat(&mut self, ecx: &EarlyContext<'_>, pat: &ast::Pat) {
152+
fn check_pat(&mut self, lcx: &LateContext<'_, '_>, pat: &hir::Pat<'_>) {
150153
if in_macro(pat.span) {
151154
let call_site = pat.span.source_callsite();
152-
let name = snippet(ecx, ecx.sess.source_map().span_until_char(call_site, '!'), "_");
155+
let name = snippet(lcx, lcx.sess().source_map().span_until_char(call_site, '!'), "_");
153156
if let Some(callee) = pat.span.source_callee() {
154-
self.collected.entry(call_site)
155-
.or_insert_with(|| {
156-
MacroRefData::new(name.to_string(), callee.def_site, ecx)
157-
});
157+
if !self.collected.contains_key(&call_site) {
158+
let mac = MacroRefData::new(name.to_string(), callee.def_site, lcx);
159+
self.mac_refs.push((call_site, mac.clone()));
160+
self.collected.insert(call_site, mac);
161+
// println!("PAT {:?} {:?}", name, lcx.sess().source_map().span_to_filename(callee.def_site));
162+
}
158163
}
159164
}
160165
}
161-
fn check_ty(&mut self, ecx: &EarlyContext<'_>, ty: &ast::Ty) {
166+
fn check_ty(&mut self, lcx: &LateContext<'_, '_>, ty: &hir::Ty<'_>) {
162167
if in_macro(ty.span) {
163168
let call_site = ty.span.source_callsite();
164-
let name = snippet(ecx, ecx.sess.source_map().span_until_char(call_site, '!'), "_");
169+
let name = snippet(lcx, lcx.sess().source_map().span_until_char(call_site, '!'), "_");
165170
if let Some(callee) = ty.span.source_callee() {
166-
self.collected.entry(call_site)
167-
.or_insert_with(|| {
168-
MacroRefData::new(name.to_string(), callee.def_site, ecx)
169-
});
171+
if !self.collected.contains_key(&call_site) {
172+
let mac = MacroRefData::new(name.to_string(), callee.def_site, lcx);
173+
self.mac_refs.push((call_site, mac.clone()));
174+
self.collected.insert(call_site, mac);
175+
// println!("TYPE {:?} {:?}", name, lcx.sess().source_map().span_to_filename(callee.def_site));
176+
}
170177
}
171178
}
172179
}
173180

174-
fn check_crate_post(&mut self, ecx: &EarlyContext<'_>, _krate: &ast::Crate) {
175-
for (name, span) in self.imports.iter() {
176-
let import_path = self.import_path_mac(&name);
181+
fn check_crate_post(&mut self, lcx: &LateContext<'_, '_>, _krate: &hir::Crate<'_>) {
182+
for (import, span) in self.imports.iter() {
183+
184+
let matched = self.mac_refs.iter().find(|(_span, mac)| !import.ends_with(&mac.name)).is_some();
185+
if matched {
186+
self.mac_refs.retain(|(_span, mac)| !import.ends_with(&mac.name));
187+
let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition";
188+
let help = format!("use {}", import);
189+
span_lint_and_sugg(
190+
lcx,
191+
MACRO_USE_IMPORTS,
192+
*span,
193+
msg,
194+
"remove the attribute and import the macro directly, try",
195+
help,
196+
Applicability::HasPlaceholders,
197+
)
198+
}
199+
}
200+
201+
for (span, mac) in self.mac_refs.iter() {
177202
let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition";
178-
let help = format!("use {}", import_path);
203+
let help = make_path(mac, "hello");
179204
span_lint_and_sugg(
180-
ecx,
205+
lcx,
181206
MACRO_USE_IMPORTS,
182207
*span,
183208
msg,
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// pub use macro_rules::foofoo;
2+
// pub use crate::extern_exports::*;
3+
4+
#[macro_export]
5+
macro_rules! pub_macro {
6+
() => {
7+
let _ = "hello Mr. Vonnegut";
8+
};
9+
}
10+
11+
12+
pub mod inner {
13+
#[macro_export]
14+
macro_rules! inner_mod {
15+
() => {
16+
#[allow(dead_code)]
17+
pub struct Tardis;
18+
};
19+
}
20+
}
21+
22+
mod extern_exports {
23+
24+
pub(super) mod private_inner {
25+
#[macro_export]
26+
macro_rules! pub_in_private {
27+
($name:ident) => {
28+
let $name = String::from("secrets and lies");
29+
};
30+
}
31+
}
32+
}

tests/ui/macro_use_imports.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
// compile-flags: --edition 2018
2+
// aux-build:macro_use_helper.rs
3+
4+
// aux-build:macro_rules.rs
5+
#![allow(clippy::single_component_path_imports)]
26
#![warn(clippy::macro_use_imports)]
37
#![feature(rustc_private)]
48

5-
use std::collections::HashMap;
69
#[macro_use]
7-
use std::{prelude, sync::Mutex};
10+
extern crate macro_use_helper as mac;
811
#[macro_use]
9-
extern crate rustc_session;
12+
use std::prelude;
13+
14+
mod a {
15+
use std::collections::HashMap;
16+
#[macro_use]
17+
use mac;
18+
19+
fn main() {
20+
let _ = HashMap::<u8, u8>::new();
21+
println!();
22+
pub_macro!();
23+
inner_mod!();
24+
pub_in_private!(_var);
25+
}
26+
}
1027

1128
fn main() {
12-
let _ = Mutex::new(8_u8);
13-
let _ = HashMap::<u8, u8>::new();
1429
println!();
15-
declare_tool_lint! {
16-
pub clippy::TEST_LINT,
17-
Warn,
18-
"",
19-
report_in_external_macro: true
20-
}
2130
}

0 commit comments

Comments
 (0)