Skip to content

Commit 01b1057

Browse files
committed
Add redundant type annotations lint
1 parent ae880e4 commit 01b1057

File tree

6 files changed

+443
-0
lines changed

6 files changed

+443
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5110,6 +5110,7 @@ Released 2018-09-13
51105110
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
51115111
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
51125112
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
5113+
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
51135114
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
51145115
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
51155116
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
543543
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
544544
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
545545
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
546+
crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO,
546547
crate::ref_option_ref::REF_OPTION_REF_INFO,
547548
crate::ref_patterns::REF_PATTERNS_INFO,
548549
crate::reference::DEREF_ADDROF_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ mod redundant_field_names;
267267
mod redundant_pub_crate;
268268
mod redundant_slicing;
269269
mod redundant_static_lifetimes;
270+
mod redundant_type_annotations;
270271
mod ref_option_ref;
271272
mod ref_patterns;
272273
mod reference;
@@ -1012,6 +1013,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10121013
store.register_early_pass(|| Box::new(needless_else::NeedlessElse));
10131014
store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug));
10141015
store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes));
1016+
store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations));
10151017
// add lints here, do not remove this comment, it's used in `new_lint`
10161018
}
10171019

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use rustc_ast::LitKind;
3+
use rustc_hir as hir;
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_middle::ty::Ty;
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
/// Warns about needless / redundant type annotations.
11+
///
12+
/// ### Why is this bad?
13+
/// Code without type annotations is shorter and in most cases
14+
/// more idiomatic and easier to modify.
15+
///
16+
/// ### Example
17+
/// ```rust
18+
/// let foo: String = String::new();
19+
/// ```
20+
/// Use instead:
21+
/// ```rust
22+
/// let foo = String::new();
23+
/// ```
24+
#[clippy::version = "1.70.0"]
25+
pub REDUNDANT_TYPE_ANNOTATIONS,
26+
pedantic,
27+
"warns about needless / redundant type annotations."
28+
}
29+
declare_lint_pass!(RedundantTypeAnnotations => [REDUNDANT_TYPE_ANNOTATIONS]);
30+
31+
fn is_same_type<'tcx>(cx: &LateContext<'tcx>, ty_resolved_path: hir::def::Res, func_return_type: Ty<'tcx>) -> bool {
32+
// type annotation is primitive
33+
if let hir::def::Res::PrimTy(primty) = ty_resolved_path
34+
&& func_return_type.is_primitive()
35+
&& let Some(func_return_type_sym) = func_return_type.primitive_symbol()
36+
{
37+
return primty.name() == func_return_type_sym;
38+
}
39+
40+
// type annotation is any other non generic type
41+
if let hir::def::Res::Def(_, defid) = ty_resolved_path
42+
&& let Some(annotation_ty) = cx.tcx.type_of(defid).no_bound_vars()
43+
{
44+
return annotation_ty == func_return_type;
45+
}
46+
47+
false
48+
}
49+
50+
fn func_hir_id_to_func_ty<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::hir_id::HirId) -> Option<Ty<'tcx>> {
51+
if let Some((defkind, func_defid)) = cx.typeck_results().type_dependent_def(hir_id)
52+
&& defkind == hir::def::DefKind::AssocFn
53+
&& let Some(init_ty) = cx.tcx.type_of(func_defid).no_bound_vars()
54+
{
55+
Some(init_ty)
56+
} else {
57+
None
58+
}
59+
}
60+
61+
fn func_ty_to_return_type<'tcx>(cx: &LateContext<'tcx>, func_ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
62+
if func_ty.is_fn()
63+
&& let Some(return_type) = func_ty.fn_sig(cx.tcx).output().no_bound_vars()
64+
{
65+
Some(return_type)
66+
} else {
67+
None
68+
}
69+
}
70+
71+
/// Extracts the fn Ty, e.g. `fn() -> std::string::String {f}`
72+
fn extract_fn_ty<'tcx>(
73+
cx: &LateContext<'tcx>,
74+
call: &hir::Expr<'tcx>,
75+
func_return_path: &hir::QPath<'tcx>,
76+
) -> Option<Ty<'tcx>> {
77+
match func_return_path {
78+
// let a: String = f(); where f: fn f() -> String
79+
hir::QPath::Resolved(_, resolved_path) => {
80+
if let hir::def::Res::Def(_, defid) = resolved_path.res
81+
&& let Some(middle_ty_init) = cx.tcx.type_of(defid).no_bound_vars()
82+
{
83+
Some(middle_ty_init)
84+
} else {
85+
None
86+
}
87+
},
88+
// Associated functions like
89+
// let a: String = String::new();
90+
// let a: String = String::get_string();
91+
hir::QPath::TypeRelative(..) => func_hir_id_to_func_ty(cx, call.hir_id),
92+
hir::QPath::LangItem(..) => None,
93+
}
94+
}
95+
96+
fn is_redundant_in_func_call<'tcx>(
97+
cx: &LateContext<'tcx>,
98+
ty_resolved_path: hir::def::Res,
99+
call: &hir::Expr<'tcx>,
100+
) -> bool {
101+
if let hir::ExprKind::Path(init_path) = &call.kind {
102+
let func_type = extract_fn_ty(cx, call, init_path);
103+
104+
if let Some(func_type) = func_type
105+
&& let Some(init_return_type) = func_ty_to_return_type(cx, func_type)
106+
{
107+
return is_same_type(cx, ty_resolved_path, init_return_type);
108+
}
109+
}
110+
111+
false
112+
}
113+
114+
impl LateLintPass<'_> for RedundantTypeAnnotations {
115+
fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx rustc_hir::Local<'tcx>) {
116+
// type annotation part
117+
if !local.span.from_expansion()
118+
&& let Some(ty) = &local.ty
119+
120+
// initialization part
121+
&& let Some(init) = local.init
122+
{
123+
match &init.kind {
124+
// When the initialization is a call to a function
125+
hir::ExprKind::Call(init_call, _) => {
126+
if let hir::TyKind::Path(ty_path) = &ty.kind
127+
&& let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
128+
129+
&& is_redundant_in_func_call(cx, resolved_path_ty.res, init_call) {
130+
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
131+
}
132+
},
133+
hir::ExprKind::MethodCall(_, _, _, _) => {
134+
if let hir::TyKind::Path(ty_path) = &ty.kind
135+
&& let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
136+
137+
&& let Some(func_ty) = func_hir_id_to_func_ty(cx, init.hir_id)
138+
&& let Some(return_type) = func_ty_to_return_type(cx, func_ty)
139+
&& is_same_type(cx, resolved_path_ty.res, return_type)
140+
{
141+
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
142+
}
143+
},
144+
// When the initialization is a path for example u32::MAX
145+
hir::ExprKind::Path(init_path) => {
146+
// TODO: check for non primty
147+
if let hir::TyKind::Path(ty_path) = &ty.kind
148+
&& let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
149+
&& let hir::def::Res::PrimTy(primty) = resolved_path_ty.res
150+
151+
&& let hir::QPath::TypeRelative(init_ty, _) = init_path
152+
&& let hir::TyKind::Path(init_ty_path) = &init_ty.kind
153+
&& let hir::QPath::Resolved(_, resolved_init_ty_path) = init_ty_path
154+
&& let hir::def::Res::PrimTy(primty_init) = resolved_init_ty_path.res
155+
156+
&& primty == primty_init
157+
{
158+
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
159+
}
160+
},
161+
hir::ExprKind::Lit(init_lit) => {
162+
match init_lit.node {
163+
// In these cases the annotation is redundant
164+
LitKind::Str(..)
165+
| LitKind::ByteStr(..)
166+
| LitKind::Byte(..)
167+
| LitKind::Char(..)
168+
| LitKind::Bool(..)
169+
| LitKind::CStr(..) => {
170+
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
171+
},
172+
LitKind::Int(..) | LitKind::Float(..) => {
173+
// If the initialization value is a suffixed literal we lint
174+
if init_lit.node.is_suffixed() {
175+
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
176+
}
177+
},
178+
LitKind::Err => (),
179+
}
180+
}
181+
_ => ()
182+
}
183+
};
184+
}
185+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#![allow(unused)]
2+
#![warn(clippy::redundant_type_annotations)]
3+
4+
#[derive(Debug, Default)]
5+
struct Cake<T> {
6+
_data: T,
7+
}
8+
9+
fn make_something<T: Default>() -> T {
10+
T::default()
11+
}
12+
13+
fn make_cake<T: Default>() -> Cake<T> {
14+
Cake::<T>::default()
15+
}
16+
17+
fn plus_one<T: std::ops::Add<u8, Output = T>>(val: T) -> T {
18+
val + 1
19+
}
20+
21+
struct Pie {
22+
inner: u32,
23+
}
24+
25+
enum Pizza {
26+
One,
27+
Two,
28+
}
29+
30+
fn return_a_string() -> String {
31+
String::new()
32+
}
33+
34+
fn return_a_struct() -> Pie {
35+
Pie { inner: 5 }
36+
}
37+
38+
fn return_an_enum() -> Pizza {
39+
Pizza::One
40+
}
41+
42+
fn return_an_int() -> u32 {
43+
5
44+
}
45+
46+
impl Pie {
47+
fn return_an_int(&self) -> u32 {
48+
self.inner
49+
}
50+
51+
fn associated_return_an_int() -> u32 {
52+
5
53+
}
54+
55+
fn new() -> Self {
56+
Self { inner: 5 }
57+
}
58+
59+
fn associated_return_a_string() -> String {
60+
String::from("")
61+
}
62+
63+
fn test_method_call(&self) {
64+
let v: u32 = self.return_an_int(); // Should lint
65+
}
66+
}
67+
68+
fn test_generics() {
69+
// The type annotation is needed to determine T
70+
let _c: Cake<i32> = make_something();
71+
72+
// The type annotation is needed to determine the topic
73+
let _c: Cake<u8> = make_cake();
74+
75+
// This should lint (doesn't)
76+
let _c: Cake<u8> = make_cake::<u8>();
77+
78+
// This should lint (doesn't)
79+
let _c: u8 = make_something::<u8>();
80+
81+
// This should lint (doesn't)
82+
let _c: u8 = plus_one(5_u8);
83+
84+
// Annotation needed otherwise T is i32
85+
let _c: u8 = plus_one(5);
86+
}
87+
88+
fn test_non_locals() {
89+
// This shouldn't lint
90+
fn _arg(x: u32) -> u32 {
91+
x
92+
}
93+
94+
// This could lint, but probably shouldn't
95+
let _closure_arg = |x: u32| x;
96+
}
97+
98+
fn test_complex_types() {
99+
// Shouldn't lint, since the literal will be i32 otherwise
100+
let _u8: u8 = 128;
101+
102+
// Should lint (doesn't)
103+
let _tuple_i32: (i32, i32) = (12, 13);
104+
105+
// Shouldn't lint, since the tuple will be i32 otherwise
106+
let _tuple_u32: (u32, u32) = (1, 2);
107+
108+
// Should lint, since the type is determined by the init value (doesn't)
109+
let _tuple_u32: (u32, u32) = (3_u32, 4_u32);
110+
111+
// Should lint (doesn't)
112+
let _array: [i32; 3] = [5, 6, 7];
113+
114+
// Shouldn't lint
115+
let _array: [u32; 2] = [8, 9];
116+
}
117+
118+
fn test_functions() {
119+
// Everything here should lint
120+
121+
let _return: String = return_a_string();
122+
123+
let _return: Pie = return_a_struct();
124+
125+
let _return: Pizza = return_an_enum();
126+
127+
let _return: u32 = return_an_int();
128+
129+
let _return: String = String::new();
130+
131+
let new_pie: Pie = Pie::new();
132+
133+
let _return: u32 = new_pie.return_an_int();
134+
135+
let _return: String = String::from("test");
136+
137+
let _return: u32 = Pie::associated_return_an_int();
138+
139+
let _return: String = Pie::associated_return_a_string();
140+
}
141+
142+
fn test_simple_types() {
143+
let _var: u32 = u32::MAX;
144+
145+
// Should lint
146+
let _var: u32 = 5_u32;
147+
148+
// Should lint
149+
let _var: &str = "test";
150+
151+
// Should lint
152+
let _var: &[u8] = b"test";
153+
154+
// Should lint
155+
let _var: bool = false;
156+
}
157+
158+
fn main() {}
159+
160+
// TODO: test refs

0 commit comments

Comments
 (0)