Skip to content

Commit f065d4b

Browse files
committed
Auto merge of #5279 - DevinR528:macro-use-sugg, r=flip1995
Macro use sugg changelog: Add auto applicable suggstion to macro_use_imports fixes #5275 <s>Where exactly is the `wildcard_imports_helper` I haven't been able to import anything ex. `use lazy_static;` or something like for that I get version/compiler conflicts?</s> Found it. Should we also check for `#[macro_use] extern crate`, although this is still depended on for stuff like `rustc_private`?
2 parents ff0993c + e521a4e commit f065d4b

File tree

6 files changed

+360
-28
lines changed

6 files changed

+360
-28
lines changed

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10621062
store.register_early_pass(|| box option_env_unwrap::OptionEnvUnwrap);
10631063
let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
10641064
store.register_late_pass(move || box wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports));
1065-
store.register_early_pass(|| box macro_use::MacroUseImports);
10661065
store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
10671066
store.register_late_pass(|| box redundant_pub_crate::RedundantPubCrate::default());
10681067
store.register_late_pass(|| box unnamed_address::UnnamedAddress);
@@ -1080,6 +1079,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10801079
single_char_binding_names_threshold,
10811080
});
10821081
store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns);
1082+
store.register_late_pass(|| box macro_use::MacroUseImports::default());
10831083

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

clippy_lints/src/macro_use.rs

+196-17
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
use crate::utils::{snippet, span_lint_and_sugg};
1+
use crate::utils::{in_macro, snippet, span_lint_and_sugg};
2+
use hir::def::{DefKind, Res};
23
use if_chain::if_chain;
34
use rustc_ast::ast;
5+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
46
use rustc_errors::Applicability;
5-
use rustc_lint::{EarlyContext, EarlyLintPass};
6-
use rustc_session::{declare_lint_pass, declare_tool_lint};
7-
use rustc_span::edition::Edition;
7+
use rustc_hir as hir;
8+
use rustc_lint::{LateContext, LateLintPass, LintContext};
9+
use rustc_session::{declare_tool_lint, impl_lint_pass};
10+
use rustc_span::{edition::Edition, Span};
811

912
declare_clippy_lint! {
1013
/// **What it does:** Checks for `#[macro_use] use...`.
1114
///
1215
/// **Why is this bad?** Since the Rust 2018 edition you can import
1316
/// macro's directly, this is considered idiomatic.
1417
///
15-
/// **Known problems:** This lint does not generate an auto-applicable suggestion.
18+
/// **Known problems:** None.
1619
///
1720
/// **Example:**
1821
/// ```rust
@@ -24,29 +27,205 @@ declare_clippy_lint! {
2427
"#[macro_use] is no longer needed"
2528
}
2629

27-
declare_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]);
30+
const BRACKETS: &[char] = &['<', '>'];
2831

29-
impl EarlyLintPass for MacroUseImports {
30-
fn check_item(&mut self, ecx: &EarlyContext<'_>, item: &ast::Item) {
32+
#[derive(Clone, Debug, PartialEq, Eq)]
33+
struct PathAndSpan {
34+
path: String,
35+
span: Span,
36+
}
37+
38+
/// `MacroRefData` includes the name of the macro
39+
/// and the path from `SourceMap::span_to_filename`.
40+
#[derive(Debug, Clone)]
41+
pub struct MacroRefData {
42+
name: String,
43+
path: String,
44+
}
45+
46+
impl MacroRefData {
47+
pub fn new(name: String, callee: Span, cx: &LateContext<'_, '_>) -> Self {
48+
let mut path = cx.sess().source_map().span_to_filename(callee).to_string();
49+
50+
// std lib paths are <::std::module::file type>
51+
// so remove brackets, space and type.
52+
if path.contains('<') {
53+
path = path.replace(BRACKETS, "");
54+
}
55+
if path.contains(' ') {
56+
path = path.split(' ').next().unwrap().to_string();
57+
}
58+
Self { name, path }
59+
}
60+
}
61+
62+
#[derive(Default)]
63+
#[allow(clippy::module_name_repetitions)]
64+
pub struct MacroUseImports {
65+
/// the actual import path used and the span of the attribute above it.
66+
imports: Vec<(String, Span)>,
67+
/// the span of the macro reference, kept to ensure only one reference is used per macro call.
68+
collected: FxHashSet<Span>,
69+
mac_refs: Vec<MacroRefData>,
70+
}
71+
72+
impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]);
73+
74+
impl MacroUseImports {
75+
fn push_unique_macro(&mut self, cx: &LateContext<'_, '_>, span: Span) {
76+
let call_site = span.source_callsite();
77+
let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
78+
if let Some(callee) = span.source_callee() {
79+
if !self.collected.contains(&call_site) {
80+
let name = if name.contains("::") {
81+
name.split("::").last().unwrap().to_string()
82+
} else {
83+
name.to_string()
84+
};
85+
86+
self.mac_refs.push(MacroRefData::new(name, callee.def_site, cx));
87+
self.collected.insert(call_site);
88+
}
89+
}
90+
}
91+
92+
fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_, '_>, span: Span) {
93+
let call_site = span.source_callsite();
94+
let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
95+
if let Some(callee) = span.source_callee() {
96+
if !self.collected.contains(&call_site) {
97+
self.mac_refs
98+
.push(MacroRefData::new(name.to_string(), callee.def_site, cx));
99+
self.collected.insert(call_site);
100+
}
101+
}
102+
}
103+
}
104+
105+
impl<'l, 'txc> LateLintPass<'l, 'txc> for MacroUseImports {
106+
fn check_item(&mut self, cx: &LateContext<'_, '_>, item: &hir::Item<'_>) {
31107
if_chain! {
32-
if ecx.sess.opts.edition == Edition::Edition2018;
33-
if let ast::ItemKind::Use(use_tree) = &item.kind;
108+
if cx.sess().opts.edition == Edition::Edition2018;
109+
if let hir::ItemKind::Use(path, _kind) = &item.kind;
34110
if let Some(mac_attr) = item
35111
.attrs
36112
.iter()
37113
.find(|attr| attr.ident().map(|s| s.to_string()) == Some("macro_use".to_string()));
114+
if let Res::Def(DefKind::Mod, id) = path.res;
38115
then {
39-
let msg = "`macro_use` attributes are no longer needed in the Rust 2018 edition";
40-
let help = format!("use {}::<macro name>", snippet(ecx, use_tree.span, "_"));
116+
for kid in cx.tcx.item_children(id).iter() {
117+
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
118+
let span = mac_attr.span;
119+
let def_path = cx.tcx.def_path_str(mac_id);
120+
self.imports.push((def_path, span));
121+
}
122+
}
123+
} else {
124+
if in_macro(item.span) {
125+
self.push_unique_macro_pat_ty(cx, item.span);
126+
}
127+
}
128+
}
129+
}
130+
fn check_attribute(&mut self, cx: &LateContext<'_, '_>, attr: &ast::Attribute) {
131+
if in_macro(attr.span) {
132+
self.push_unique_macro(cx, attr.span);
133+
}
134+
}
135+
fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>) {
136+
if in_macro(expr.span) {
137+
self.push_unique_macro(cx, expr.span);
138+
}
139+
}
140+
fn check_stmt(&mut self, cx: &LateContext<'_, '_>, stmt: &hir::Stmt<'_>) {
141+
if in_macro(stmt.span) {
142+
self.push_unique_macro(cx, stmt.span);
143+
}
144+
}
145+
fn check_pat(&mut self, cx: &LateContext<'_, '_>, pat: &hir::Pat<'_>) {
146+
if in_macro(pat.span) {
147+
self.push_unique_macro_pat_ty(cx, pat.span);
148+
}
149+
}
150+
fn check_ty(&mut self, cx: &LateContext<'_, '_>, ty: &hir::Ty<'_>) {
151+
if in_macro(ty.span) {
152+
self.push_unique_macro_pat_ty(cx, ty.span);
153+
}
154+
}
155+
#[allow(clippy::too_many_lines)]
156+
fn check_crate_post(&mut self, cx: &LateContext<'_, '_>, _krate: &hir::Crate<'_>) {
157+
let mut used = FxHashMap::default();
158+
let mut check_dup = vec![];
159+
for (import, span) in &self.imports {
160+
let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
161+
162+
if let Some(idx) = found_idx {
163+
let _ = self.mac_refs.remove(idx);
164+
let seg = import.split("::").collect::<Vec<_>>();
165+
166+
match seg.as_slice() {
167+
// an empty path is impossible
168+
// a path should always consist of 2 or more segments
169+
[] | [_] => return,
170+
[root, item] => {
171+
if !check_dup.contains(&(*item).to_string()) {
172+
used.entry(((*root).to_string(), span))
173+
.or_insert_with(Vec::new)
174+
.push((*item).to_string());
175+
check_dup.push((*item).to_string());
176+
}
177+
},
178+
[root, rest @ ..] => {
179+
if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) {
180+
let filtered = rest
181+
.iter()
182+
.filter_map(|item| {
183+
if check_dup.contains(&(*item).to_string()) {
184+
None
185+
} else {
186+
Some((*item).to_string())
187+
}
188+
})
189+
.collect::<Vec<_>>();
190+
used.entry(((*root).to_string(), span))
191+
.or_insert_with(Vec::new)
192+
.push(filtered.join("::"));
193+
check_dup.extend(filtered);
194+
} else {
195+
let rest = rest.to_vec();
196+
used.entry(((*root).to_string(), span))
197+
.or_insert_with(Vec::new)
198+
.push(rest.join("::"));
199+
check_dup.extend(rest.iter().map(ToString::to_string));
200+
}
201+
},
202+
}
203+
}
204+
}
205+
206+
let mut suggestions = vec![];
207+
for ((root, span), path) in used {
208+
if path.len() == 1 {
209+
suggestions.push((span, format!("{}::{}", root, path[0])))
210+
} else {
211+
suggestions.push((span, format!("{}::{{{}}}", root, path.join(", "))))
212+
}
213+
}
214+
215+
// If mac_refs is not empty we have encountered an import we could not handle
216+
// such as `std::prelude::v1::foo` or some other macro that expands to an import.
217+
if self.mac_refs.is_empty() {
218+
for (span, import) in suggestions {
219+
let help = format!("use {};", import);
41220
span_lint_and_sugg(
42-
ecx,
221+
cx,
43222
MACRO_USE_IMPORTS,
44-
mac_attr.span,
45-
msg,
223+
*span,
224+
"`macro_use` attributes are no longer needed in the Rust 2018 edition",
46225
"remove the attribute and import the macro directly, try",
47226
help,
48-
Applicability::HasPlaceholders,
49-
);
227+
Applicability::MaybeIncorrect,
228+
)
50229
}
51230
}
52231
}
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
extern crate macro_rules;
2+
3+
// STMT
4+
#[macro_export]
5+
macro_rules! pub_macro {
6+
() => {
7+
let _ = "hello Mr. Vonnegut";
8+
};
9+
}
10+
11+
pub mod inner {
12+
pub use super::*;
13+
14+
// RE-EXPORT
15+
// this will stick in `inner` module
16+
pub use macro_rules::foofoo;
17+
pub use macro_rules::try_err;
18+
19+
pub mod nested {
20+
pub use macro_rules::string_add;
21+
}
22+
23+
// ITEM
24+
#[macro_export]
25+
macro_rules! inner_mod_macro {
26+
() => {
27+
#[allow(dead_code)]
28+
pub struct Tardis;
29+
};
30+
}
31+
}
32+
33+
// EXPR
34+
#[macro_export]
35+
macro_rules! function_macro {
36+
() => {
37+
if true {
38+
} else {
39+
}
40+
};
41+
}
42+
43+
// TYPE
44+
#[macro_export]
45+
macro_rules! ty_macro {
46+
() => {
47+
Vec<u8>
48+
};
49+
}
50+
51+
mod extern_exports {
52+
pub(super) mod private_inner {
53+
#[macro_export]
54+
macro_rules! pub_in_private_macro {
55+
($name:ident) => {
56+
let $name = String::from("secrets and lies");
57+
};
58+
}
59+
}
60+
}

tests/ui/macro_use_imports.fixed

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// compile-flags: --edition 2018
2+
// aux-build:macro_rules.rs
3+
// aux-build:macro_use_helper.rs
4+
// run-rustfix
5+
// ignore-32bit
6+
7+
#![allow(unused_imports, unreachable_code, unused_variables, dead_code)]
8+
#![allow(clippy::single_component_path_imports)]
9+
#![warn(clippy::macro_use_imports)]
10+
11+
#[macro_use]
12+
extern crate macro_use_helper as mac;
13+
14+
#[macro_use]
15+
extern crate clippy_mini_macro_test as mini_mac;
16+
17+
mod a {
18+
use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro};
19+
use mac;
20+
use mini_mac::ClippyMiniMacroTest;
21+
use mini_mac;
22+
use mac::{inner::foofoo, inner::try_err};
23+
use mac::inner;
24+
use mac::inner::nested::string_add;
25+
use mac::inner::nested;
26+
27+
#[derive(ClippyMiniMacroTest)]
28+
struct Test;
29+
30+
fn test() {
31+
pub_macro!();
32+
inner_mod_macro!();
33+
pub_in_private_macro!(_var);
34+
function_macro!();
35+
let v: ty_macro!() = Vec::default();
36+
37+
inner::try_err!();
38+
inner::foofoo!();
39+
nested::string_add!();
40+
}
41+
}
42+
43+
fn main() {}

0 commit comments

Comments
 (0)