Skip to content

Commit cd30783

Browse files
committed
skip generic contexts and macro expansions in needless_type_cast
1 parent 48f7f36 commit cd30783

File tree

4 files changed

+447
-186
lines changed

4 files changed

+447
-186
lines changed
Lines changed: 141 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,74 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::source::snippet;
3-
use clippy_utils::visitors::for_each_expr_without_closures;
2+
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures};
43
use core::ops::ControlFlow;
54
use rustc_data_structures::fx::FxHashMap;
65
use rustc_errors::Applicability;
7-
use rustc_hir::def::Res;
8-
use rustc_hir::{Block, Body, ExprKind, HirId, LetStmt, PatKind, StmtKind};
6+
use rustc_hir::def::{DefKind, Res};
7+
use rustc_hir::{Body, Expr, ExprKind, HirId, LetStmt, PatKind, StmtKind};
98
use rustc_lint::LateContext;
10-
use rustc_middle::ty::Ty;
9+
use rustc_middle::ty::{Ty, TypeVisitableExt};
1110
use rustc_span::Span;
1211

1312
use super::NEEDLESS_TYPE_CAST;
1413

1514
struct BindingInfo<'a> {
1615
source_ty: Ty<'a>,
17-
ty_span: Option<Span>,
18-
pat_span: Span,
16+
ty_span: Span,
1917
}
2018

2119
struct UsageInfo<'a> {
22-
is_cast: bool,
2320
cast_to: Option<Ty<'a>>,
21+
in_generic_context: bool,
2422
}
2523

2624
pub(super) fn check<'a>(cx: &LateContext<'a>, body: &Body<'a>) {
2725
let mut bindings: FxHashMap<HirId, BindingInfo<'a>> = FxHashMap::default();
2826

29-
collect_bindings_from_block(cx, body.value, &mut bindings);
30-
3127
for_each_expr_without_closures(body.value, |expr| {
32-
if let ExprKind::Let(let_expr) = expr.kind {
33-
collect_binding_from_let(cx, let_expr, &mut bindings);
28+
match expr.kind {
29+
ExprKind::Block(block, _) => {
30+
for stmt in block.stmts {
31+
if let StmtKind::Let(let_stmt) = stmt.kind {
32+
collect_binding_from_local(cx, let_stmt, &mut bindings);
33+
}
34+
}
35+
},
36+
ExprKind::Let(let_expr) => {
37+
collect_binding_from_let(cx, let_expr, &mut bindings);
38+
},
39+
_ => {},
3440
}
3541
ControlFlow::<()>::Continue(())
3642
});
3743

3844
#[allow(rustc::potential_query_instability)]
3945
let mut binding_vec: Vec<_> = bindings.into_iter().collect();
40-
binding_vec.sort_by_key(|(_, info)| info.pat_span.lo());
46+
binding_vec.sort_by_key(|(_, info)| info.ty_span.lo());
4147

4248
for (hir_id, binding_info) in binding_vec {
4349
check_binding_usages(cx, body, hir_id, &binding_info);
4450
}
4551
}
4652

47-
fn collect_bindings_from_block<'a>(
48-
cx: &LateContext<'a>,
49-
expr: &rustc_hir::Expr<'a>,
50-
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
51-
) {
52-
if let ExprKind::Block(block, _) = expr.kind {
53-
collect_bindings_from_block_inner(cx, block, bindings);
54-
}
55-
}
56-
57-
fn collect_bindings_from_block_inner<'a>(
58-
cx: &LateContext<'a>,
59-
block: &Block<'a>,
60-
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
61-
) {
62-
for stmt in block.stmts {
63-
if let StmtKind::Let(let_stmt) = stmt.kind {
64-
collect_binding_from_local(cx, let_stmt, bindings);
65-
}
66-
}
67-
}
68-
6953
fn collect_binding_from_let<'a>(
7054
cx: &LateContext<'a>,
7155
let_expr: &rustc_hir::LetExpr<'a>,
7256
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
7357
) {
74-
if let_expr.ty.is_none() {
58+
if let_expr.ty.is_none() || let_expr.span.from_expansion() || has_generic_return_type(cx, let_expr.init) {
7559
return;
7660
}
7761

78-
if let PatKind::Binding(_, hir_id, _, _) = let_expr.pat.kind {
62+
if let PatKind::Binding(_, hir_id, _, _) = let_expr.pat.kind
63+
&& let Some(ty_hir) = let_expr.ty
64+
{
7965
let ty = cx.typeck_results().pat_ty(let_expr.pat);
8066
if ty.is_numeric() {
8167
bindings.insert(
8268
hir_id,
8369
BindingInfo {
8470
source_ty: ty,
85-
ty_span: let_expr.ty.map(|t| t.span),
86-
pat_span: let_expr.pat.span,
71+
ty_span: ty_hir.span,
8772
},
8873
);
8974
}
@@ -95,105 +80,165 @@ fn collect_binding_from_local<'a>(
9580
let_stmt: &LetStmt<'a>,
9681
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
9782
) {
98-
// Only check bindings with explicit type annotations
99-
// Otherwise, the suggestion to change the type may not be valid
100-
// (e.g., `let x = 42u8;` cannot just change to `let x: i64 = 42u8;`)
101-
if let_stmt.ty.is_none() {
83+
if let_stmt.ty.is_none()
84+
|| let_stmt.span.from_expansion()
85+
|| let_stmt.init.is_some_and(|init| has_generic_return_type(cx, init))
86+
{
10287
return;
10388
}
10489

105-
if let PatKind::Binding(_, hir_id, _, _) = let_stmt.pat.kind {
90+
if let PatKind::Binding(_, hir_id, _, _) = let_stmt.pat.kind
91+
&& let Some(ty_hir) = let_stmt.ty
92+
{
10693
let ty = cx.typeck_results().pat_ty(let_stmt.pat);
10794
if ty.is_numeric() {
10895
bindings.insert(
10996
hir_id,
11097
BindingInfo {
11198
source_ty: ty,
112-
ty_span: let_stmt.ty.map(|t| t.span),
113-
pat_span: let_stmt.pat.span,
99+
ty_span: ty_hir.span,
114100
},
115101
);
116102
}
117103
}
118104
}
119105

106+
fn has_generic_return_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
107+
match &expr.kind {
108+
ExprKind::MethodCall(..) => {
109+
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
110+
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
111+
let ret_ty = sig.output().skip_binder();
112+
return ret_ty.has_param();
113+
}
114+
false
115+
},
116+
ExprKind::Call(callee, _) => {
117+
if let ExprKind::Path(qpath) = &callee.kind {
118+
let res = cx.qpath_res(qpath, callee.hir_id);
119+
if let Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) = res {
120+
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
121+
let ret_ty = sig.output().skip_binder();
122+
return ret_ty.has_param();
123+
}
124+
}
125+
false
126+
},
127+
_ => false,
128+
}
129+
}
130+
131+
fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool {
132+
let has_type_params = |def_id| {
133+
cx.tcx
134+
.generics_of(def_id)
135+
.own_params
136+
.iter()
137+
.any(|p| p.kind.is_ty_or_const())
138+
};
139+
match res {
140+
Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => has_type_params(def_id),
141+
// Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
142+
Res::Def(DefKind::Ctor(..), def_id) => has_type_params(cx.tcx.parent(cx.tcx.parent(def_id))),
143+
_ => false,
144+
}
145+
}
146+
147+
fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> bool {
148+
let mut current_id = cast_expr.hir_id;
149+
150+
loop {
151+
let parent_id = cx.tcx.parent_hir_id(current_id);
152+
if parent_id == current_id {
153+
return false;
154+
}
155+
156+
let parent = cx.tcx.hir_node(parent_id);
157+
158+
match parent {
159+
rustc_hir::Node::Expr(parent_expr) => {
160+
match &parent_expr.kind {
161+
// Closure body is a separate type inference context
162+
ExprKind::Closure(_) => return false,
163+
ExprKind::Call(callee, _) => {
164+
if let ExprKind::Path(qpath) = &callee.kind {
165+
let res = cx.qpath_res(qpath, callee.hir_id);
166+
if is_generic_res(cx, res) {
167+
return true;
168+
}
169+
}
170+
},
171+
ExprKind::MethodCall(..) => {
172+
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
173+
&& cx
174+
.tcx
175+
.generics_of(def_id)
176+
.own_params
177+
.iter()
178+
.any(|p| p.kind.is_ty_or_const())
179+
{
180+
return true;
181+
}
182+
},
183+
_ => {},
184+
}
185+
current_id = parent_id;
186+
},
187+
_ => return false,
188+
}
189+
}
190+
}
191+
120192
fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId, binding_info: &BindingInfo<'a>) {
121-
let mut usages: Vec<UsageInfo<'a>> = Vec::new();
193+
let mut usages = Vec::new();
122194

123-
for_each_expr_without_closures(body.value, |expr| {
195+
for_each_expr(cx, body.value, |expr| {
124196
if let ExprKind::Path(ref qpath) = expr.kind
197+
&& !expr.span.from_expansion()
125198
&& let Res::Local(id) = cx.qpath_res(qpath, expr.hir_id)
126199
&& id == hir_id
127200
{
128201
let parent_id = cx.tcx.parent_hir_id(expr.hir_id);
129202
let parent = cx.tcx.hir_node(parent_id);
130203

131-
if let rustc_hir::Node::Expr(parent_expr) = parent {
132-
if let ExprKind::Cast(_, _) = parent_expr.kind {
133-
let target_ty = cx.typeck_results().expr_ty(parent_expr);
134-
usages.push(UsageInfo {
135-
is_cast: true,
136-
cast_to: Some(target_ty),
137-
});
138-
} else {
139-
usages.push(UsageInfo {
140-
is_cast: false,
141-
cast_to: None,
142-
});
204+
let usage = if let rustc_hir::Node::Expr(parent_expr) = parent
205+
&& let ExprKind::Cast(..) = parent_expr.kind
206+
&& !parent_expr.span.from_expansion()
207+
{
208+
UsageInfo {
209+
cast_to: Some(cx.typeck_results().expr_ty(parent_expr)),
210+
in_generic_context: is_cast_in_generic_context(cx, parent_expr),
143211
}
144212
} else {
145-
usages.push(UsageInfo {
146-
is_cast: false,
213+
UsageInfo {
147214
cast_to: None,
148-
});
149-
}
215+
in_generic_context: false,
216+
}
217+
};
218+
usages.push(usage);
150219
}
151220
ControlFlow::<()>::Continue(())
152221
});
153222

154-
if usages.is_empty() {
155-
return;
156-
}
157-
158-
if !usages.iter().all(|u| u.is_cast) {
159-
return;
160-
}
161-
162-
let Some(first_target) = usages.first().and_then(|u| u.cast_to) else {
223+
let Some(first_target) = usages
224+
.first()
225+
.and_then(|u| u.cast_to)
226+
.filter(|&t| t != binding_info.source_ty)
227+
.filter(|&t| usages.iter().all(|u| u.cast_to == Some(t) && !u.in_generic_context))
228+
else {
163229
return;
164230
};
165231

166-
if !usages.iter().all(|u| u.cast_to == Some(first_target)) {
167-
return;
168-
}
169-
170-
if first_target == binding_info.source_ty {
171-
return;
172-
}
173-
174-
let suggestion = if binding_info.ty_span.is_some() {
175-
format!("{first_target}")
176-
} else {
177-
format!(": {first_target}")
178-
};
179-
180-
let span = binding_info.ty_span.unwrap_or(binding_info.pat_span);
181-
let current_snippet = snippet(cx, span, "_");
182-
183232
span_lint_and_sugg(
184233
cx,
185234
NEEDLESS_TYPE_CAST,
186-
span,
235+
binding_info.ty_span,
187236
format!(
188237
"this binding is defined as `{}` but is always cast to `{}`",
189238
binding_info.source_ty, first_target
190239
),
191240
"consider defining it as",
192-
if binding_info.ty_span.is_some() {
193-
suggestion
194-
} else {
195-
format!("{current_snippet}{suggestion}")
196-
},
241+
first_target.to_string(),
197242
Applicability::MaybeIncorrect,
198243
);
199244
}

0 commit comments

Comments
 (0)