Skip to content

Commit 92f9e59

Browse files
wip: anon_trait_import
1 parent 04bded5 commit 92f9e59

7 files changed

+580
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5241,6 +5241,7 @@ Released 2018-09-13
52415241
[`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range
52425242
[`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range
52435243
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
5244+
[`anon_trait_import`]: https://rust-lang.github.io/rust-clippy/master/index.html#anon_trait_import
52445245
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
52455246
[`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync
52465247
[`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects

clippy_lints/src/anon_trait_import.rs

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
use std::ops::ControlFlow;
2+
3+
use clippy_utils::diagnostics::span_lint_and_sugg;
4+
use clippy_utils::source::snippet_opt;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::def::{DefKind, Res};
7+
use rustc_hir::def_id::DefId;
8+
use rustc_hir::definitions::DefPathData;
9+
use rustc_hir::intravisit::{walk_item, walk_mod, walk_path, Visitor};
10+
use rustc_hir::{HirId, Item, ItemKind, Node, Path, UseKind};
11+
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::hir::nested_filter;
13+
use rustc_middle::ty::Visibility;
14+
use rustc_session::declare_lint_pass;
15+
use rustc_span::symbol::kw;
16+
17+
declare_clippy_lint! {
18+
/// ### What it does
19+
/// Checks for `use Trait` where the Trait is not used directly.
20+
///
21+
/// ### Why is this bad?
22+
/// Traits imported that aren't used directly can be imported anonymously `use Trait as _`. It is more explicit and can be usedful to show which imports are required for traits.
23+
///
24+
/// ### Example
25+
/// ```no_run
26+
/// use std::fmt::Write;
27+
///
28+
/// fn main() {
29+
/// let mut s = String::new();
30+
/// let _ = write!(s, "hello, world!");
31+
/// println!("{s}");
32+
/// }
33+
/// ```
34+
/// Use instead:
35+
/// ```no_run
36+
/// use std::fmt::Write as _;
37+
///
38+
/// fn main() {
39+
/// let mut s = String::new();
40+
/// let _ = write!(s, "hello, world!");
41+
/// println!("{s}");
42+
/// }
43+
/// ```
44+
#[clippy::version = "1.80.0"]
45+
pub ANON_TRAIT_IMPORT,
46+
nursery,
47+
"use items that import a trait but only use it anonymously"
48+
}
49+
50+
declare_lint_pass!(AnonTraitImport => [ANON_TRAIT_IMPORT]);
51+
52+
impl<'tcx> LateLintPass<'tcx> for AnonTraitImport {
53+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
54+
let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id);
55+
if cx.tcx.visibility(item.owner_id.def_id) != Visibility::Restricted(module.to_def_id()) {
56+
return;
57+
}
58+
if let ItemKind::Use(path, UseKind::Single) = item.kind
59+
// Ignore imports that already use Underscore
60+
&& item.ident.name != kw::Underscore
61+
&& let Some(Res::Def(DefKind::Trait, def_id)) = path.res.first()
62+
&& let parent_id = cx.tcx.hir().get_parent_item(item.hir_id())
63+
&& let Node::Item(parent_item) = cx.tcx.hir_node_by_def_id(parent_id.def_id)
64+
&& !is_import_used_by_name_in_item(cx, *def_id, parent_item)
65+
&& let Some(last_segment) = path.segments.last()
66+
&& let Some(snip) = snippet_opt(cx, last_segment.ident.span)
67+
{
68+
let complete_span = last_segment.ident.span.to(item.ident.span);
69+
span_lint_and_sugg(
70+
cx,
71+
ANON_TRAIT_IMPORT,
72+
complete_span,
73+
"importing trait that is only used anonymously",
74+
"use",
75+
format!("{snip} as _"),
76+
Applicability::MaybeIncorrect,
77+
);
78+
}
79+
}
80+
}
81+
82+
fn is_import_used_by_name_in_item<'tcx>(cx: &LateContext<'tcx>, def_id: DefId, parent_item: &'tcx Item<'_>) -> bool {
83+
let module = find_module(cx, parent_item);
84+
let mut visitor = TraitUsedByNameVisitor {
85+
cx,
86+
id: def_id,
87+
module,
88+
module_depth: 0,
89+
};
90+
let result = if let ItemKind::Mod(parent_mod) = parent_item.kind {
91+
visitor.visit_mod(parent_mod, parent_item.span, parent_item.hir_id())
92+
} else {
93+
visitor.visit_item(parent_item)
94+
};
95+
matches!(result, ControlFlow::Break(()))
96+
}
97+
98+
fn find_module<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> HirId {
99+
if let ItemKind::Mod(_) = &item.kind {
100+
return item.hir_id();
101+
}
102+
let parent = cx.tcx.hir().get_parent_item(item.hir_id());
103+
let mut node = cx.tcx.hir_node_by_def_id(parent.def_id);
104+
loop {
105+
match node {
106+
Node::Crate(r#mod) => return r#mod.item_ids[0].hir_id(),
107+
Node::Item(item) => {
108+
if let ItemKind::Mod(_) = &item.kind {
109+
return item.hir_id();
110+
}
111+
let hir_id = item.hir_id();
112+
let parent = cx.tcx.hir().get_parent_item(hir_id);
113+
node = cx.tcx.hir_node_by_def_id(parent.def_id);
114+
},
115+
_ => panic!("not an item or crate: {node:?}"),
116+
};
117+
}
118+
}
119+
120+
struct TraitUsedByNameVisitor<'a, 'hir> {
121+
cx: &'a LateContext<'hir>,
122+
id: DefId,
123+
module: HirId,
124+
module_depth: usize,
125+
}
126+
127+
impl<'a, 'hir> Visitor<'hir> for TraitUsedByNameVisitor<'a, 'hir> {
128+
type NestedFilter = nested_filter::All;
129+
type Result = ControlFlow<()>;
130+
131+
fn nested_visit_map(&mut self) -> Self::Map {
132+
self.cx.tcx.hir()
133+
}
134+
135+
fn visit_item(&mut self, item: &'hir Item<'_>) -> Self::Result {
136+
match item.kind {
137+
ItemKind::Mod(m) => {
138+
self.module_depth += 1;
139+
let result = walk_mod(self, m, item.hir_id());
140+
self.module_depth -= 1;
141+
result
142+
},
143+
_ => walk_item(self, item),
144+
}
145+
}
146+
147+
fn visit_path(&mut self, path: &Path<'hir>, _: HirId) -> Self::Result {
148+
if self.module_depth > 0 {
149+
if let Some(segment) = path.segments.first()
150+
&& segment.ident.name == kw::Crate
151+
{
152+
if let Some(mod_segment) = path.segments.get(path.segments.len() - 2)
153+
&& Some(self.module.owner.to_def_id()) == mod_segment.res.mod_def_id()
154+
&& path.res.opt_def_id() == Some(self.id)
155+
{
156+
return ControlFlow::Break(());
157+
}
158+
} else {
159+
let mut super_count = 0;
160+
for segment in path.segments {
161+
if segment.ident.name == kw::Super {
162+
super_count += 1;
163+
if super_count > self.module_depth {
164+
break;
165+
}
166+
} else {
167+
if super_count == self.module_depth
168+
&& (path.res.opt_def_id() == Some(self.id) || segment.res.opt_def_id() == Some(self.id))
169+
{
170+
return ControlFlow::Break(());
171+
}
172+
break;
173+
}
174+
}
175+
}
176+
}
177+
if let Some(def_id) = self.first_path_segment_def_id(path)
178+
&& def_id == self.id
179+
&& self.module_depth == 0
180+
{
181+
return ControlFlow::Break(());
182+
}
183+
walk_path(self, path)
184+
}
185+
}
186+
187+
impl<'hir> TraitUsedByNameVisitor<'_, 'hir> {
188+
fn skip_def_id(&self, def_id: DefId) -> DefId {
189+
let def_key = self.cx.tcx.def_key(def_id);
190+
match def_key.disambiguated_data.data {
191+
DefPathData::Ctor => {
192+
if let Some(def_id) = self.cx.tcx.opt_parent(def_id) {
193+
self.skip_def_id(def_id)
194+
} else {
195+
def_id
196+
}
197+
},
198+
_ => def_id,
199+
}
200+
}
201+
202+
fn first_path_segment_def_id(&self, path: &Path<'_>) -> Option<DefId> {
203+
path.res.opt_def_id().and_then(|mut def_id| {
204+
def_id = self.skip_def_id(def_id);
205+
for _ in path.segments.iter().skip(1) {
206+
def_id = self.skip_def_id(def_id);
207+
if let Some(parent_def_id) = self.cx.tcx.opt_parent(def_id) {
208+
def_id = parent_def_id;
209+
} else {
210+
return None;
211+
}
212+
}
213+
214+
Some(def_id)
215+
})
216+
}
217+
}

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
3333
crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO,
3434
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
3535
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
36+
crate::anon_trait_import::ANON_TRAIT_IMPORT_INFO,
3637
crate::approx_const::APPROX_CONSTANT_INFO,
3738
crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO,
3839
crate::as_conversions::AS_CONVERSIONS_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ pub mod deprecated_lints;
7272
// begin lints modules, do not remove this comment, it’s used in `update_lints`
7373
mod absolute_paths;
7474
mod almost_complete_range;
75+
mod anon_trait_import;
7576
mod approx_const;
7677
mod arc_with_non_send_sync;
7778
mod as_conversions;
@@ -935,5 +936,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
935936
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
936937
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
937938
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
939+
store.register_late_pass(|_| Box::new(anon_trait_import::AnonTraitImport));
938940
// add lints here, do not remove this comment, it's used in `new_lint`
939941
}

0 commit comments

Comments
 (0)