1 | use clippy_utils::diagnostics::span_lint; |
2 | use clippy_utils::is_lint_allowed; |
3 | use rustc_ast::LitKind; |
4 | use rustc_hir as hir; |
5 | use rustc_hir::def::DefKind; |
6 | use rustc_lint::{LateContext, LateLintPass}; |
7 | use rustc_middle::ty::Ty; |
8 | use rustc_session::declare_lint_pass; |
9 | |
10 | declare_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 | } |
39 | declare_lint_pass!(RedundantTypeAnnotations => [REDUNDANT_TYPE_ANNOTATIONS]); |
40 | |
41 | fn 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 | |
60 | fn 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 | |
71 | fn 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}` |
80 | fn 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 | |
104 | fn 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 | |
122 | fn 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 | |
133 | impl 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 | |