]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use rustc_ast::ast::Attribute; |
2 | use rustc_errors::Applicability; | |
94222f64 | 3 | use rustc_hir::def_id::{DefIdSet, LocalDefId}; |
cdc7bbd5 XL |
4 | use rustc_hir::{self as hir, def::Res, intravisit, QPath}; |
5 | use rustc_lint::{LateContext, LintContext}; | |
6 | use rustc_middle::{ | |
7 | hir::map::Map, | |
8 | lint::in_external_macro, | |
9 | ty::{self, Ty}, | |
10 | }; | |
11 | use rustc_span::{sym, Span}; | |
12 | ||
13 | use clippy_utils::attrs::is_proc_macro; | |
14 | use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; | |
15 | use clippy_utils::source::snippet_opt; | |
16 | use clippy_utils::ty::is_must_use_ty; | |
17 | use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method}; | |
18 | ||
19 | use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; | |
20 | ||
21 | pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { | |
22 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
23 | let attr = must_use_attr(attrs); | |
24 | if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind { | |
94222f64 | 25 | let is_public = cx.access_levels.is_exported(item.def_id); |
cdc7bbd5 XL |
26 | let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
27 | if let Some(attr) = attr { | |
28 | check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); | |
29 | return; | |
30 | } else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) { | |
31 | check_must_use_candidate( | |
32 | cx, | |
33 | sig.decl, | |
34 | cx.tcx.hir().body(*body_id), | |
35 | item.span, | |
94222f64 | 36 | item.def_id, |
cdc7bbd5 XL |
37 | item.span.with_hi(sig.decl.output.span().hi()), |
38 | "this function could have a `#[must_use]` attribute", | |
39 | ); | |
40 | } | |
41 | } | |
42 | } | |
43 | ||
44 | pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { | |
45 | if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { | |
94222f64 | 46 | let is_public = cx.access_levels.is_exported(item.def_id); |
cdc7bbd5 XL |
47 | let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
48 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
49 | let attr = must_use_attr(attrs); | |
50 | if let Some(attr) = attr { | |
51 | check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); | |
52 | } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() { | |
53 | check_must_use_candidate( | |
54 | cx, | |
55 | sig.decl, | |
56 | cx.tcx.hir().body(*body_id), | |
57 | item.span, | |
94222f64 | 58 | item.def_id, |
cdc7bbd5 XL |
59 | item.span.with_hi(sig.decl.output.span().hi()), |
60 | "this method could have a `#[must_use]` attribute", | |
61 | ); | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { | |
67 | if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { | |
94222f64 | 68 | let is_public = cx.access_levels.is_exported(item.def_id); |
cdc7bbd5 XL |
69 | let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
70 | ||
71 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
72 | let attr = must_use_attr(attrs); | |
73 | if let Some(attr) = attr { | |
74 | check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); | |
75 | } else if let hir::TraitFn::Provided(eid) = *eid { | |
76 | let body = cx.tcx.hir().body(eid); | |
77 | if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) { | |
78 | check_must_use_candidate( | |
79 | cx, | |
80 | sig.decl, | |
81 | body, | |
82 | item.span, | |
94222f64 | 83 | item.def_id, |
cdc7bbd5 XL |
84 | item.span.with_hi(sig.decl.output.span().hi()), |
85 | "this method could have a `#[must_use]` attribute", | |
86 | ); | |
87 | } | |
88 | } | |
89 | } | |
90 | } | |
91 | ||
92 | fn check_needless_must_use( | |
93 | cx: &LateContext<'_>, | |
94 | decl: &hir::FnDecl<'_>, | |
95 | item_id: hir::HirId, | |
96 | item_span: Span, | |
97 | fn_header_span: Span, | |
98 | attr: &Attribute, | |
99 | ) { | |
100 | if in_external_macro(cx.sess(), item_span) { | |
101 | return; | |
102 | } | |
103 | if returns_unit(decl) { | |
104 | span_lint_and_then( | |
105 | cx, | |
106 | MUST_USE_UNIT, | |
107 | fn_header_span, | |
108 | "this unit-returning function has a `#[must_use]` attribute", | |
109 | |diag| { | |
110 | diag.span_suggestion( | |
111 | attr.span, | |
112 | "remove the attribute", | |
113 | "".into(), | |
114 | Applicability::MachineApplicable, | |
115 | ); | |
116 | }, | |
117 | ); | |
118 | } else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) { | |
119 | span_lint_and_help( | |
120 | cx, | |
121 | DOUBLE_MUST_USE, | |
122 | fn_header_span, | |
123 | "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`", | |
124 | None, | |
125 | "either add some descriptive text or remove the attribute", | |
126 | ); | |
127 | } | |
128 | } | |
129 | ||
130 | fn check_must_use_candidate<'tcx>( | |
131 | cx: &LateContext<'tcx>, | |
132 | decl: &'tcx hir::FnDecl<'_>, | |
133 | body: &'tcx hir::Body<'_>, | |
134 | item_span: Span, | |
94222f64 | 135 | item_id: LocalDefId, |
cdc7bbd5 XL |
136 | fn_span: Span, |
137 | msg: &str, | |
138 | ) { | |
139 | if has_mutable_arg(cx, body) | |
140 | || mutates_static(cx, body) | |
141 | || in_external_macro(cx.sess(), item_span) | |
142 | || returns_unit(decl) | |
143 | || !cx.access_levels.is_exported(item_id) | |
94222f64 | 144 | || is_must_use_ty(cx, return_ty(cx, cx.tcx.hir().local_def_id_to_hir_id(item_id))) |
cdc7bbd5 XL |
145 | { |
146 | return; | |
147 | } | |
148 | span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { | |
149 | if let Some(snippet) = snippet_opt(cx, fn_span) { | |
150 | diag.span_suggestion( | |
151 | fn_span, | |
152 | "add the attribute", | |
153 | format!("#[must_use] {}", snippet), | |
154 | Applicability::MachineApplicable, | |
155 | ); | |
156 | } | |
157 | }); | |
158 | } | |
159 | ||
160 | fn returns_unit(decl: &hir::FnDecl<'_>) -> bool { | |
161 | match decl.output { | |
162 | hir::FnRetTy::DefaultReturn(_) => true, | |
163 | hir::FnRetTy::Return(ty) => match ty.kind { | |
164 | hir::TyKind::Tup(tys) => tys.is_empty(), | |
165 | hir::TyKind::Never => true, | |
166 | _ => false, | |
167 | }, | |
168 | } | |
169 | } | |
170 | ||
171 | fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool { | |
172 | let mut tys = DefIdSet::default(); | |
173 | body.params.iter().any(|param| is_mutable_pat(cx, param.pat, &mut tys)) | |
174 | } | |
175 | ||
176 | fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut DefIdSet) -> bool { | |
177 | if let hir::PatKind::Wild = pat.kind { | |
178 | return false; // ignore `_` patterns | |
179 | } | |
180 | if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) { | |
181 | is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys) | |
182 | } else { | |
183 | false | |
184 | } | |
185 | } | |
186 | ||
187 | static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]]; | |
188 | ||
189 | fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut DefIdSet) -> bool { | |
190 | match *ty.kind() { | |
191 | // primitive types are never mutable | |
192 | ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false, | |
193 | ty::Adt(adt, substs) => { | |
194 | tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env) | |
195 | || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path)) | |
196 | && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)) | |
197 | }, | |
198 | ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)), | |
199 | ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys), | |
200 | ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { | |
201 | mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys) | |
202 | }, | |
203 | // calling something constitutes a side effect, so return true on all callables | |
204 | // also never calls need not be used, so return true for them, too | |
205 | _ => true, | |
206 | } | |
207 | } | |
208 | ||
209 | struct StaticMutVisitor<'a, 'tcx> { | |
210 | cx: &'a LateContext<'tcx>, | |
211 | mutates_static: bool, | |
212 | } | |
213 | ||
214 | impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> { | |
215 | type Map = Map<'tcx>; | |
216 | ||
217 | fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { | |
218 | use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall}; | |
219 | ||
220 | if self.mutates_static { | |
221 | return; | |
222 | } | |
223 | match expr.kind { | |
224 | Call(_, args) | MethodCall(_, _, args, _) => { | |
225 | let mut tys = DefIdSet::default(); | |
226 | for arg in args { | |
227 | if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) | |
228 | && is_mutable_ty( | |
229 | self.cx, | |
230 | self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg), | |
231 | arg.span, | |
232 | &mut tys, | |
233 | ) | |
234 | && is_mutated_static(arg) | |
235 | { | |
236 | self.mutates_static = true; | |
237 | return; | |
238 | } | |
239 | tys.clear(); | |
240 | } | |
241 | }, | |
242 | Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => { | |
17df50a5 | 243 | self.mutates_static |= is_mutated_static(target); |
cdc7bbd5 XL |
244 | }, |
245 | _ => {}, | |
246 | } | |
247 | } | |
248 | ||
249 | fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> { | |
250 | intravisit::NestedVisitorMap::None | |
251 | } | |
252 | } | |
253 | ||
254 | fn is_mutated_static(e: &hir::Expr<'_>) -> bool { | |
255 | use hir::ExprKind::{Field, Index, Path}; | |
256 | ||
257 | match e.kind { | |
258 | Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)), | |
259 | Path(_) => true, | |
260 | Field(inner, _) | Index(inner, _) => is_mutated_static(inner), | |
261 | _ => false, | |
262 | } | |
263 | } | |
264 | ||
265 | fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool { | |
266 | let mut v = StaticMutVisitor { | |
267 | cx, | |
268 | mutates_static: false, | |
269 | }; | |
270 | intravisit::walk_expr(&mut v, &body.value); | |
271 | v.mutates_static | |
272 | } |