]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/functions/must_use.rs
Auto merge of #87975 - m-ou-se:unused-import-attributes, r=nagisa
[rust.git] / src / tools / clippy / 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             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,
36                 item.def_id,
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 {
46         let is_public = cx.access_levels.is_exported(item.def_id);
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,
58                 item.def_id,
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 {
68         let is_public = cx.access_levels.is_exported(item.def_id);
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,
83                     item.def_id,
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,
135     item_id: LocalDefId,
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)
144         || is_must_use_ty(cx, return_ty(cx, cx.tcx.hir().local_def_id_to_hir_id(item_id)))
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) => {
243                 self.mutates_static |= is_mutated_static(target);
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 }