]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use rustc_ast::ast::Attribute; |
2 | use rustc_errors::Applicability; | |
94222f64 | 3 | use rustc_hir::def_id::{DefIdSet, LocalDefId}; |
2b03887a | 4 | use rustc_hir::{self as hir, def::Res, QPath}; |
cdc7bbd5 XL |
5 | use rustc_lint::{LateContext, LintContext}; |
6 | use rustc_middle::{ | |
cdc7bbd5 XL |
7 | lint::in_external_macro, |
8 | ty::{self, Ty}, | |
9 | }; | |
2b03887a | 10 | use rustc_span::{sym, Span, Symbol}; |
cdc7bbd5 XL |
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; | |
2b03887a FG |
16 | use clippy_utils::visitors::for_each_expr; |
17 | use clippy_utils::{return_ty, trait_ref_of_method}; | |
18 | ||
19 | use core::ops::ControlFlow; | |
cdc7bbd5 XL |
20 | |
21 | use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; | |
22 | ||
5099ac24 | 23 | pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { |
cdc7bbd5 | 24 | let attrs = cx.tcx.hir().attrs(item.hir_id()); |
2b03887a | 25 | let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use); |
04454e1e | 26 | if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind { |
2b03887a | 27 | let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); |
cdc7bbd5 XL |
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); | |
cdc7bbd5 XL |
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, | |
2b03887a | 37 | item.owner_id.def_id, |
cdc7bbd5 XL |
38 | item.span.with_hi(sig.decl.output.span().hi()), |
39 | "this function could have a `#[must_use]` attribute", | |
40 | ); | |
41 | } | |
42 | } | |
43 | } | |
44 | ||
5099ac24 | 45 | pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { |
cdc7bbd5 | 46 | if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { |
2b03887a | 47 | let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); |
cdc7bbd5 XL |
48 | let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); |
49 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
2b03887a | 50 | let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use); |
cdc7bbd5 XL |
51 | if let Some(attr) = attr { |
52 | check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); | |
6522a427 EL |
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() | |
2b03887a | 56 | { |
cdc7bbd5 XL |
57 | check_must_use_candidate( |
58 | cx, | |
59 | sig.decl, | |
60 | cx.tcx.hir().body(*body_id), | |
61 | item.span, | |
2b03887a | 62 | item.owner_id.def_id, |
cdc7bbd5 XL |
63 | item.span.with_hi(sig.decl.output.span().hi()), |
64 | "this method could have a `#[must_use]` attribute", | |
65 | ); | |
66 | } | |
67 | } | |
68 | } | |
69 | ||
5099ac24 | 70 | pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { |
cdc7bbd5 | 71 | if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { |
2b03887a | 72 | let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); |
cdc7bbd5 XL |
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()); | |
2b03887a | 76 | let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use); |
cdc7bbd5 XL |
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, | |
2b03887a | 87 | item.owner_id.def_id, |
cdc7bbd5 XL |
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| { | |
04454e1e | 114 | diag.span_suggestion(attr.span, "remove the attribute", "", Applicability::MachineApplicable); |
cdc7bbd5 XL |
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, | |
94222f64 | 134 | item_id: LocalDefId, |
cdc7bbd5 XL |
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) | |
2b03887a | 142 | || !cx.effective_visibilities.is_exported(item_id) |
94222f64 | 143 | || is_must_use_ty(cx, return_ty(cx, cx.tcx.hir().local_def_id_to_hir_id(item_id))) |
cdc7bbd5 XL |
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", | |
2b03887a | 152 | format!("#[must_use] {snippet}"), |
cdc7bbd5 XL |
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()) { | |
6522a427 | 180 | is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner.def_id).pat_ty(pat), tys) |
cdc7bbd5 XL |
181 | } else { |
182 | false | |
183 | } | |
184 | } | |
185 | ||
2b03887a | 186 | static KNOWN_WRAPPER_TYS: &[Symbol] = &[sym::Rc, sym::Arc]; |
cdc7bbd5 | 187 | |
6522a427 | 188 | fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, tys: &mut DefIdSet) -> bool { |
cdc7bbd5 XL |
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) => { | |
2b03887a FG |
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())) | |
6522a427 | 197 | && substs.types().any(|ty| is_mutable_ty(cx, ty, tys)) |
cdc7bbd5 | 198 | }, |
6522a427 EL |
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), | |
cdc7bbd5 | 201 | ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { |
6522a427 | 202 | mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, tys) |
cdc7bbd5 XL |
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 | ||
2b03887a FG |
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 | } | |
cdc7bbd5 XL |
219 | } |
220 | ||
2b03887a FG |
221 | fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool { |
222 | for_each_expr(body.value, |e| { | |
cdc7bbd5 XL |
223 | use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall}; |
224 | ||
2b03887a | 225 | match e.kind { |
f2b60f7d | 226 | Call(_, args) => { |
cdc7bbd5 XL |
227 | let mut tys = DefIdSet::default(); |
228 | for arg in args { | |
2b03887a | 229 | if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) |
6522a427 | 230 | && is_mutable_ty(cx, cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg), &mut tys) |
cdc7bbd5 XL |
231 | && is_mutated_static(arg) |
232 | { | |
2b03887a | 233 | return ControlFlow::Break(()); |
cdc7bbd5 XL |
234 | } |
235 | tys.clear(); | |
236 | } | |
2b03887a | 237 | ControlFlow::Continue(()) |
cdc7bbd5 | 238 | }, |
f2b60f7d FG |
239 | MethodCall(_, receiver, args, _) => { |
240 | let mut tys = DefIdSet::default(); | |
241 | for arg in std::iter::once(receiver).chain(args.iter()) { | |
2b03887a | 242 | if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) |
6522a427 | 243 | && is_mutable_ty(cx, cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg), &mut tys) |
f2b60f7d FG |
244 | && is_mutated_static(arg) |
245 | { | |
2b03887a | 246 | return ControlFlow::Break(()); |
f2b60f7d FG |
247 | } |
248 | tys.clear(); | |
249 | } | |
2b03887a | 250 | ControlFlow::Continue(()) |
f2b60f7d | 251 | }, |
2b03887a FG |
252 | Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) |
253 | if is_mutated_static(target) => | |
254 | { | |
255 | ControlFlow::Break(()) | |
cdc7bbd5 | 256 | }, |
2b03887a | 257 | _ => ControlFlow::Continue(()), |
cdc7bbd5 | 258 | } |
2b03887a FG |
259 | }) |
260 | .is_some() | |
cdc7bbd5 | 261 | } |