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