Skip to content

Commit 73efce9

Browse files
committed
Auto merge of rust-lang#9770 - sgued:missnamed-getters, r=llogiq
Add new lint [`misnamed-getters`] ``` changelog: Add new lint [`misnamed-getters`] ``` Closes rust-lang#9769 The current lint matches all methods with a body of just one expression under the form `(&mut?)? <expr>.field` where field doesn't match the name of the method but there is a field of the same type in `<expr>` that matches the name. This allows matching nested structs, for example for newtype wrappers. This may cast the net a bit too wide and cause false positives. I'll run [clippy_lint_tester](https://github.com/mikerite/clippy_lint_tester) on the top crates to see how frequently false positives happen. There also may be room for improvement by checking that the replacement field would work taking into account implementations of `Deref` and `DerefMut` even if the types don't exactly match but I don't know yet how this could be done.
2 parents f4850f7 + 1f2f50c commit 73efce9

File tree

6 files changed

+462
-0
lines changed

6 files changed

+462
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4188,6 +4188,7 @@ Released 2018-09-13
41884188
[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
41894189
[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
41904190
[`mismatching_type_param_order`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatching_type_param_order
4191+
[`misnamed_getters`]: https://rust-lang.github.io/rust-clippy/master/index.html#misnamed_getters
41914192
[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op
41924193
[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
41934194
[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
177177
crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO,
178178
crate::from_str_radix_10::FROM_STR_RADIX_10_INFO,
179179
crate::functions::DOUBLE_MUST_USE_INFO,
180+
crate::functions::MISNAMED_GETTERS_INFO,
180181
crate::functions::MUST_USE_CANDIDATE_INFO,
181182
crate::functions::MUST_USE_UNIT_INFO,
182183
crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::source::snippet;
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{intravisit::FnKind, Body, ExprKind, FnDecl, HirId, ImplicitSelfKind, Unsafety};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty;
7+
use rustc_span::Span;
8+
9+
use std::iter;
10+
11+
use super::MISNAMED_GETTERS;
12+
13+
pub fn check_fn(
14+
cx: &LateContext<'_>,
15+
kind: FnKind<'_>,
16+
decl: &FnDecl<'_>,
17+
body: &Body<'_>,
18+
span: Span,
19+
_hir_id: HirId,
20+
) {
21+
let FnKind::Method(ref ident, sig) = kind else {
22+
return;
23+
};
24+
25+
// Takes only &(mut) self
26+
if decl.inputs.len() != 1 {
27+
return;
28+
}
29+
30+
let name = ident.name.as_str();
31+
32+
let name = match decl.implicit_self {
33+
ImplicitSelfKind::MutRef => {
34+
let Some(name) = name.strip_suffix("_mut") else {
35+
return;
36+
};
37+
name
38+
},
39+
ImplicitSelfKind::Imm | ImplicitSelfKind::Mut | ImplicitSelfKind::ImmRef => name,
40+
ImplicitSelfKind::None => return,
41+
};
42+
43+
let name = if sig.header.unsafety == Unsafety::Unsafe {
44+
name.strip_suffix("_unchecked").unwrap_or(name)
45+
} else {
46+
name
47+
};
48+
49+
// Body must be &(mut) <self_data>.name
50+
// self_data is not neccessarilly self, to also lint sub-getters, etc…
51+
52+
let block_expr = if_chain! {
53+
if let ExprKind::Block(block,_) = body.value.kind;
54+
if block.stmts.is_empty();
55+
if let Some(block_expr) = block.expr;
56+
then {
57+
block_expr
58+
} else {
59+
return;
60+
}
61+
};
62+
let expr_span = block_expr.span;
63+
64+
// Accept &<expr>, &mut <expr> and <expr>
65+
let expr = if let ExprKind::AddrOf(_, _, tmp) = block_expr.kind {
66+
tmp
67+
} else {
68+
block_expr
69+
};
70+
let (self_data, used_ident) = if_chain! {
71+
if let ExprKind::Field(self_data, ident) = expr.kind;
72+
if ident.name.as_str() != name;
73+
then {
74+
(self_data, ident)
75+
} else {
76+
return;
77+
}
78+
};
79+
80+
let mut used_field = None;
81+
let mut correct_field = None;
82+
let typeck_results = cx.typeck_results();
83+
for adjusted_type in iter::once(typeck_results.expr_ty(self_data))
84+
.chain(typeck_results.expr_adjustments(self_data).iter().map(|adj| adj.target))
85+
{
86+
let ty::Adt(def,_) = adjusted_type.kind() else {
87+
continue;
88+
};
89+
90+
for f in def.all_fields() {
91+
if f.name.as_str() == name {
92+
correct_field = Some(f);
93+
}
94+
if f.name == used_ident.name {
95+
used_field = Some(f);
96+
}
97+
}
98+
}
99+
100+
let Some(used_field) = used_field else {
101+
// Can happen if the field access is a tuple. We don't lint those because the getter name could not start with a number.
102+
return;
103+
};
104+
105+
let Some(correct_field) = correct_field else {
106+
// There is no field corresponding to the getter name.
107+
// FIXME: This can be a false positive if the correct field is reachable trought deeper autodereferences than used_field is
108+
return;
109+
};
110+
111+
if cx.tcx.type_of(used_field.did) == cx.tcx.type_of(correct_field.did) {
112+
let left_span = block_expr.span.until(used_ident.span);
113+
let snippet = snippet(cx, left_span, "..");
114+
let sugg = format!("{snippet}{name}");
115+
span_lint_and_then(
116+
cx,
117+
MISNAMED_GETTERS,
118+
span,
119+
"getter function appears to return the wrong field",
120+
|diag| {
121+
diag.span_suggestion(expr_span, "consider using", sugg, Applicability::MaybeIncorrect);
122+
},
123+
);
124+
}
125+
}

clippy_lints/src/functions/mod.rs

+45
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod misnamed_getters;
12
mod must_use;
23
mod not_unsafe_ptr_arg_deref;
34
mod result;
@@ -260,6 +261,48 @@ declare_clippy_lint! {
260261
"function returning `Result` with large `Err` type"
261262
}
262263

264+
declare_clippy_lint! {
265+
/// ### What it does
266+
/// Checks for getter methods that return a field that doesn't correspond
267+
/// to the name of the method, when there is a field's whose name matches that of the method.
268+
///
269+
/// ### Why is this bad?
270+
/// It is most likely that such a method is a bug caused by a typo or by copy-pasting.
271+
///
272+
/// ### Example
273+
274+
/// ```rust
275+
/// struct A {
276+
/// a: String,
277+
/// b: String,
278+
/// }
279+
///
280+
/// impl A {
281+
/// fn a(&self) -> &str{
282+
/// &self.b
283+
/// }
284+
/// }
285+
286+
/// ```
287+
/// Use instead:
288+
/// ```rust
289+
/// struct A {
290+
/// a: String,
291+
/// b: String,
292+
/// }
293+
///
294+
/// impl A {
295+
/// fn a(&self) -> &str{
296+
/// &self.a
297+
/// }
298+
/// }
299+
/// ```
300+
#[clippy::version = "1.67.0"]
301+
pub MISNAMED_GETTERS,
302+
suspicious,
303+
"getter method returning the wrong field"
304+
}
305+
263306
#[derive(Copy, Clone)]
264307
pub struct Functions {
265308
too_many_arguments_threshold: u64,
@@ -286,6 +329,7 @@ impl_lint_pass!(Functions => [
286329
MUST_USE_CANDIDATE,
287330
RESULT_UNIT_ERR,
288331
RESULT_LARGE_ERR,
332+
MISNAMED_GETTERS,
289333
]);
290334

291335
impl<'tcx> LateLintPass<'tcx> for Functions {
@@ -301,6 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
301345
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
302346
too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold);
303347
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
348+
misnamed_getters::check_fn(cx, kind, decl, body, span, hir_id);
304349
}
305350

306351
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {

tests/ui/misnamed_getters.rs

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#![allow(unused)]
2+
#![warn(clippy::misnamed_getters)]
3+
4+
struct A {
5+
a: u8,
6+
b: u8,
7+
c: u8,
8+
}
9+
10+
impl A {
11+
fn a(&self) -> &u8 {
12+
&self.b
13+
}
14+
fn a_mut(&mut self) -> &mut u8 {
15+
&mut self.b
16+
}
17+
18+
fn b(self) -> u8 {
19+
self.a
20+
}
21+
22+
fn b_mut(&mut self) -> &mut u8 {
23+
&mut self.a
24+
}
25+
26+
fn c(&self) -> &u8 {
27+
&self.b
28+
}
29+
30+
fn c_mut(&mut self) -> &mut u8 {
31+
&mut self.a
32+
}
33+
}
34+
35+
union B {
36+
a: u8,
37+
b: u8,
38+
}
39+
40+
impl B {
41+
unsafe fn a(&self) -> &u8 {
42+
&self.b
43+
}
44+
unsafe fn a_mut(&mut self) -> &mut u8 {
45+
&mut self.b
46+
}
47+
48+
unsafe fn b(self) -> u8 {
49+
self.a
50+
}
51+
52+
unsafe fn b_mut(&mut self) -> &mut u8 {
53+
&mut self.a
54+
}
55+
56+
unsafe fn c(&self) -> &u8 {
57+
&self.b
58+
}
59+
60+
unsafe fn c_mut(&mut self) -> &mut u8 {
61+
&mut self.a
62+
}
63+
64+
unsafe fn a_unchecked(&self) -> &u8 {
65+
&self.b
66+
}
67+
unsafe fn a_unchecked_mut(&mut self) -> &mut u8 {
68+
&mut self.b
69+
}
70+
71+
unsafe fn b_unchecked(self) -> u8 {
72+
self.a
73+
}
74+
75+
unsafe fn b_unchecked_mut(&mut self) -> &mut u8 {
76+
&mut self.a
77+
}
78+
79+
unsafe fn c_unchecked(&self) -> &u8 {
80+
&self.b
81+
}
82+
83+
unsafe fn c_unchecked_mut(&mut self) -> &mut u8 {
84+
&mut self.a
85+
}
86+
}
87+
88+
struct D {
89+
d: u8,
90+
inner: A,
91+
}
92+
93+
impl core::ops::Deref for D {
94+
type Target = A;
95+
fn deref(&self) -> &A {
96+
&self.inner
97+
}
98+
}
99+
100+
impl core::ops::DerefMut for D {
101+
fn deref_mut(&mut self) -> &mut A {
102+
&mut self.inner
103+
}
104+
}
105+
106+
impl D {
107+
fn a(&self) -> &u8 {
108+
&self.b
109+
}
110+
fn a_mut(&mut self) -> &mut u8 {
111+
&mut self.b
112+
}
113+
114+
fn d(&self) -> &u8 {
115+
&self.b
116+
}
117+
fn d_mut(&mut self) -> &mut u8 {
118+
&mut self.b
119+
}
120+
}
121+
122+
fn main() {
123+
// test code goes here
124+
}

0 commit comments

Comments
 (0)