]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/functions/must_use.rs
Merge commit 'a5d597637dcb78dc73f93561ce474f23d4177c35' into clippyup
[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     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 {
25         let is_public = cx.access_levels.is_exported(item.def_id);
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         } else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
30             check_must_use_candidate(
31                 cx,
32                 sig.decl,
33                 cx.tcx.hir().body(*body_id),
34                 item.span,
35                 item.def_id,
36                 item.span.with_hi(sig.decl.output.span().hi()),
37                 "this function could have a `#[must_use]` attribute",
38             );
39         }
40     }
41 }
42
43 pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
44     if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
45         let is_public = cx.access_levels.is_exported(item.def_id);
46         let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
47         let attrs = cx.tcx.hir().attrs(item.hir_id());
48         let attr = must_use_attr(attrs);
49         if let Some(attr) = attr {
50             check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
51         } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() {
52             check_must_use_candidate(
53                 cx,
54                 sig.decl,
55                 cx.tcx.hir().body(*body_id),
56                 item.span,
57                 item.def_id,
58                 item.span.with_hi(sig.decl.output.span().hi()),
59                 "this method could have a `#[must_use]` attribute",
60             );
61         }
62     }
63 }
64
65 pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
66     if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
67         let is_public = cx.access_levels.is_exported(item.def_id);
68         let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
69
70         let attrs = cx.tcx.hir().attrs(item.hir_id());
71         let attr = must_use_attr(attrs);
72         if let Some(attr) = attr {
73             check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
74         } else if let hir::TraitFn::Provided(eid) = *eid {
75             let body = cx.tcx.hir().body(eid);
76             if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
77                 check_must_use_candidate(
78                     cx,
79                     sig.decl,
80                     body,
81                     item.span,
82                     item.def_id,
83                     item.span.with_hi(sig.decl.output.span().hi()),
84                     "this method could have a `#[must_use]` attribute",
85                 );
86             }
87         }
88     }
89 }
90
91 fn check_needless_must_use(
92     cx: &LateContext<'_>,
93     decl: &hir::FnDecl<'_>,
94     item_id: hir::HirId,
95     item_span: Span,
96     fn_header_span: Span,
97     attr: &Attribute,
98 ) {
99     if in_external_macro(cx.sess(), item_span) {
100         return;
101     }
102     if returns_unit(decl) {
103         span_lint_and_then(
104             cx,
105             MUST_USE_UNIT,
106             fn_header_span,
107             "this unit-returning function has a `#[must_use]` attribute",
108             |diag| {
109                 diag.span_suggestion(
110                     attr.span,
111                     "remove the attribute",
112                     "".into(),
113                     Applicability::MachineApplicable,
114                 );
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.access_levels.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).pat_ty(pat), pat.span, tys)
181     } else {
182         false
183     }
184 }
185
186 static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
187
188 fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, 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.at(span), cx.param_env)
194                 || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
195                     && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
196         },
197         ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
198         ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
199         ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
200             mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
201         },
202         // calling something constitutes a side effect, so return true on all callables
203         // also never calls need not be used, so return true for them, too
204         _ => true,
205     }
206 }
207
208 struct StaticMutVisitor<'a, 'tcx> {
209     cx: &'a LateContext<'tcx>,
210     mutates_static: bool,
211 }
212
213 impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
214     type Map = Map<'tcx>;
215
216     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
217         use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
218
219         if self.mutates_static {
220             return;
221         }
222         match expr.kind {
223             Call(_, args) | MethodCall(_, _, args, _) => {
224                 let mut tys = DefIdSet::default();
225                 for arg in args {
226                     if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
227                         && is_mutable_ty(
228                             self.cx,
229                             self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
230                             arg.span,
231                             &mut tys,
232                         )
233                         && is_mutated_static(arg)
234                     {
235                         self.mutates_static = true;
236                         return;
237                     }
238                     tys.clear();
239                 }
240             },
241             Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
242                 self.mutates_static |= is_mutated_static(target);
243             },
244             _ => {},
245         }
246     }
247
248     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
249         intravisit::NestedVisitorMap::None
250     }
251 }
252
253 fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
254     use hir::ExprKind::{Field, Index, Path};
255
256     match e.kind {
257         Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
258         Path(_) => true,
259         Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
260         _ => false,
261     }
262 }
263
264 fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
265     let mut v = StaticMutVisitor {
266         cx,
267         mutates_static: false,
268     };
269     intravisit::walk_expr(&mut v, &body.value);
270     v.mutates_static
271 }