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