]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/functions/must_use.rs
New upstream version 1.56.0~beta.4+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / functions / must_use.rs
CommitLineData
cdc7bbd5
XL
1use rustc_ast::ast::Attribute;
2use rustc_errors::Applicability;
94222f64 3use rustc_hir::def_id::{DefIdSet, LocalDefId};
cdc7bbd5
XL
4use rustc_hir::{self as hir, def::Res, intravisit, QPath};
5use rustc_lint::{LateContext, LintContext};
6use rustc_middle::{
7 hir::map::Map,
8 lint::in_external_macro,
9 ty::{self, Ty},
10};
11use rustc_span::{sym, Span};
12
13use clippy_utils::attrs::is_proc_macro;
14use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
15use clippy_utils::source::snippet_opt;
16use clippy_utils::ty::is_must_use_ty;
17use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method};
18
19use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
20
21pub(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
44pub(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
66pub(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
92fn 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
130fn 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
160fn 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
171fn 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
176fn 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
187static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
188
189fn 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
209struct StaticMutVisitor<'a, 'tcx> {
210 cx: &'a LateContext<'tcx>,
211 mutates_static: bool,
212}
213
214impl<'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
254fn 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
265fn 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}