1use clippy_utils::diagnostics::span_lint;
2use clippy_utils::is_lint_allowed;
3use rustc_ast::LitKind;
4use rustc_hir as hir;
5use rustc_hir::def::DefKind;
6use rustc_lint::{LateContext, LateLintPass};
7use rustc_middle::ty::Ty;
8use rustc_session::declare_lint_pass;
9
10declare_clippy_lint! {
11 /// ### What it does
12 /// Warns about needless / redundant type annotations.
13 ///
14 /// ### Why restrict this?
15 /// Code without type annotations is shorter and in most cases
16 /// more idiomatic and easier to modify.
17 ///
18 /// ### Limitations
19 /// This lint doesn't support:
20 ///
21 /// - Generics
22 /// - Refs returned from anything else than a `MethodCall`
23 /// - Complex types (tuples, arrays, etc...)
24 /// - `Path` to anything else than a primitive type.
25 ///
26 /// ### Example
27 /// ```no_run
28 /// let foo: String = String::new();
29 /// ```
30 /// Use instead:
31 /// ```no_run
32 /// let foo = String::new();
33 /// ```
34 #[clippy::version = "1.72.0"]
35 pub REDUNDANT_TYPE_ANNOTATIONS,
36 restriction,
37 "warns about needless / redundant type annotations."
38}
39declare_lint_pass!(RedundantTypeAnnotations => [REDUNDANT_TYPE_ANNOTATIONS]);
40
41fn is_same_type<'tcx>(cx: &LateContext<'tcx>, ty_resolved_path: hir::def::Res, func_return_type: Ty<'tcx>) -> bool {
42 // type annotation is primitive
43 if let hir::def::Res::PrimTy(primty) = ty_resolved_path
44 && func_return_type.is_primitive()
45 && let Some(func_return_type_sym) = func_return_type.primitive_symbol()
46 {
47 return primty.name() == func_return_type_sym;
48 }
49
50 // type annotation is a non generic type
51 if let hir::def::Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, defid) = ty_resolved_path
52 && let Some(annotation_ty) = cx.tcx.type_of(defid).no_bound_vars()
53 {
54 return annotation_ty == func_return_type;
55 }
56
57 false
58}
59
60fn func_hir_id_to_func_ty<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::hir_id::HirId) -> Option<Ty<'tcx>> {
61 if let Some((defkind, func_defid)) = cx.typeck_results().type_dependent_def(hir_id)
62 && defkind == DefKind::AssocFn
63 && let Some(init_ty) = cx.tcx.type_of(func_defid).no_bound_vars()
64 {
65 Some(init_ty)
66 } else {
67 None
68 }
69}
70
71fn func_ty_to_return_type<'tcx>(cx: &LateContext<'tcx>, func_ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
72 if func_ty.is_fn() {
73 Some(func_ty.fn_sig(cx.tcx).output().skip_binder())
74 } else {
75 None
76 }
77}
78
79/// Extracts the fn Ty, e.g. `fn() -> std::string::String {f}`
80fn extract_fn_ty<'tcx>(
81 cx: &LateContext<'tcx>,
82 call: &hir::Expr<'tcx>,
83 func_return_path: &hir::QPath<'tcx>,
84) -> Option<Ty<'tcx>> {
85 match func_return_path {
86 // let a: String = f(); where f: fn f() -> String
87 hir::QPath::Resolved(_, resolved_path: &{unknown}) => {
88 if let hir::def::Res::Def(_, defid) = resolved_path.res
89 && let Some(middle_ty_init) = cx.tcx.type_of(defid).no_bound_vars()
90 {
91 Some(middle_ty_init)
92 } else {
93 None
94 }
95 },
96 // Associated functions like
97 // let a: String = String::new();
98 // let a: String = String::get_string();
99 hir::QPath::TypeRelative(..) => func_hir_id_to_func_ty(cx, call.hir_id),
100 hir::QPath::LangItem(..) => None,
101 }
102}
103
104fn is_redundant_in_func_call<'tcx>(
105 cx: &LateContext<'tcx>,
106 ty_resolved_path: hir::def::Res,
107 call: &hir::Expr<'tcx>,
108) -> bool {
109 if let hir::ExprKind::Path(init_path: &{unknown}) = &call.kind {
110 let func_type: Option<{unknown}> = extract_fn_ty(cx, call, func_return_path:init_path);
111
112 if let Some(func_type) = func_type
113 && let Some(init_return_type) = func_ty_to_return_type(cx, func_ty:func_type)
114 {
115 return is_same_type(cx, ty_resolved_path, func_return_type:init_return_type);
116 }
117 }
118
119 false
120}
121
122fn extract_primty(ty_kind: &hir::TyKind<'_>) -> Option<hir::PrimTy> {
123 if let hir::TyKind::Path(ty_path: &{unknown}) = ty_kind
124 && let hir::QPath::Resolved(_, resolved_path_ty: &{unknown}) = ty_path
125 && let hir::def::Res::PrimTy(primty) = resolved_path_ty.res
126 {
127 Some(primty)
128 } else {
129 None
130 }
131}
132
133impl LateLintPass<'_> for RedundantTypeAnnotations {
134 fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx rustc_hir::LetStmt<'tcx>) {
135 if !is_lint_allowed(cx, REDUNDANT_TYPE_ANNOTATIONS, local.hir_id)
136 // type annotation part
137 && !local.span.from_expansion()
138 && let Some(ty) = &local.ty
139
140 // initialization part
141 && let Some(init) = local.init
142 {
143 match &init.kind {
144 // When the initialization is a call to a function
145 hir::ExprKind::Call(init_call, _) => {
146 if let hir::TyKind::Path(ty_path) = &ty.kind
147 && let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
148 && is_redundant_in_func_call(cx, resolved_path_ty.res, init_call)
149 {
150 span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
151 }
152 },
153 hir::ExprKind::MethodCall(_, _, _, _) => {
154 let mut is_ref = false;
155 let mut ty_kind = &ty.kind;
156
157 // If the annotation is a ref we "peel" it
158 if let hir::TyKind::Ref(_, mut_ty) = &ty.kind {
159 is_ref = true;
160 ty_kind = &mut_ty.ty.kind;
161 }
162
163 if let hir::TyKind::Path(ty_path) = ty_kind
164 && let hir::QPath::Resolved(_, resolved_path_ty) = ty_path
165 && let Some(func_ty) = func_hir_id_to_func_ty(cx, init.hir_id)
166 && let Some(return_type) = func_ty_to_return_type(cx, func_ty)
167 && is_same_type(
168 cx,
169 resolved_path_ty.res,
170 if is_ref { return_type.peel_refs() } else { return_type },
171 )
172 {
173 span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
174 }
175 },
176 // When the initialization is a path for example u32::MAX
177 hir::ExprKind::Path(init_path) => {
178 // TODO: check for non primty
179 if let Some(primty) = extract_primty(&ty.kind)
180 && let hir::QPath::TypeRelative(init_ty, _) = init_path
181 && let Some(primty_init) = extract_primty(&init_ty.kind)
182 && primty == primty_init
183 {
184 span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
185 }
186 },
187 hir::ExprKind::Lit(init_lit) => {
188 match init_lit.node {
189 // In these cases the annotation is redundant
190 LitKind::Str(..)
191 | LitKind::Byte(..)
192 | LitKind::Char(..)
193 | LitKind::Bool(..)
194 | LitKind::CStr(..) => {
195 span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
196 },
197 LitKind::Int(..) | LitKind::Float(..) => {
198 // If the initialization value is a suffixed literal we lint
199 if init_lit.node.is_suffixed() {
200 span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
201 }
202 },
203 LitKind::Err(_) => (),
204 LitKind::ByteStr(..) => {
205 // We only lint if the type annotation is an array type (e.g. &[u8; 4]).
206 // If instead it is a slice (e.g. &[u8]) it may not be redundant, so we
207 // don't lint.
208 if let hir::TyKind::Ref(_, mut_ty) = ty.kind
209 && matches!(mut_ty.ty.kind, hir::TyKind::Array(..))
210 {
211 span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
212 }
213 },
214 }
215 },
216 _ => (),
217 }
218 }
219 }
220}
221

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more