1 use rustc_ast
::ast
::Attribute
;
2 use rustc_errors
::Applicability
;
3 use rustc_hir
::def_id
::DefIdSet
;
4 use rustc_hir
::{self as hir, def::Res, intravisit, QPath}
;
5 use rustc_lint
::{LateContext, LintContext}
;
8 lint
::in_external_macro
,
11 use rustc_span
::{sym, Span}
;
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}
;
19 use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}
;
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
.hir_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
);
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(
34 cx
.tcx
.hir().body(*body_id
),
37 item
.span
.with_hi(sig
.decl
.output
.span().hi()),
38 "this function could have a `#[must_use]` attribute",
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
.hir_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(
56 cx
.tcx
.hir().body(*body_id
),
59 item
.span
.with_hi(sig
.decl
.output
.span().hi()),
60 "this method could have a `#[must_use]` attribute",
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
.hir_id());
69 let fn_header_span
= item
.span
.with_hi(sig
.decl
.output
.span().hi());
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(
84 item
.span
.with_hi(sig
.decl
.output
.span().hi()),
85 "this method could have a `#[must_use]` attribute",
92 fn check_needless_must_use(
94 decl
: &hir
::FnDecl
<'_
>,
100 if in_external_macro(cx
.sess(), item_span
) {
103 if returns_unit(decl
) {
108 "this unit-returning function has a `#[must_use]` attribute",
110 diag
.span_suggestion(
112 "remove the attribute",
114 Applicability
::MachineApplicable
,
118 } else if attr
.value_str().is_none() && is_must_use_ty(cx
, return_ty(cx
, item_id
)) {
123 "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
125 "either add some descriptive text or remove the attribute",
130 fn check_must_use_candidate
<'tcx
>(
131 cx
: &LateContext
<'tcx
>,
132 decl
: &'tcx hir
::FnDecl
<'_
>,
133 body
: &'tcx hir
::Body
<'_
>,
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
, item_id
))
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(
153 format
!("#[must_use] {}", snippet
),
154 Applicability
::MachineApplicable
,
160 fn returns_unit(decl
: &hir
::FnDecl
<'_
>) -> bool
{
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,
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
))
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
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
)
187 static KNOWN_WRAPPER_TYS
: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
189 fn is_mutable_ty
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>, span
: Span
, tys
: &mut DefIdSet
) -> bool
{
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
))
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
)
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
209 struct StaticMutVisitor
<'a
, 'tcx
> {
210 cx
: &'a LateContext
<'tcx
>,
211 mutates_static
: bool
,
214 impl<'a
, 'tcx
> intravisit
::Visitor
<'tcx
> for StaticMutVisitor
<'a
, 'tcx
> {
215 type Map
= Map
<'tcx
>;
217 fn visit_expr(&mut self, expr
: &'tcx hir
::Expr
<'_
>) {
218 use hir
::ExprKind
::{AddrOf, Assign, AssignOp, Call, MethodCall}
;
220 if self.mutates_static
{
224 Call(_
, args
) | MethodCall(_
, _
, args
, _
) => {
225 let mut tys
= DefIdSet
::default();
227 if self.cx
.tcx
.has_typeck_results(arg
.hir_id
.owner
.to_def_id())
230 self.cx
.tcx
.typeck(arg
.hir_id
.owner
).expr_ty(arg
),
234 && is_mutated_static(arg
)
236 self.mutates_static
= true;
242 Assign(target
, ..) | AssignOp(_
, target
, _
) | AddrOf(_
, hir
::Mutability
::Mut
, target
) => {
243 self.mutates_static
|= is_mutated_static(target
)
249 fn nested_visit_map(&mut self) -> intravisit
::NestedVisitorMap
<Self::Map
> {
250 intravisit
::NestedVisitorMap
::None
254 fn is_mutated_static(e
: &hir
::Expr
<'_
>) -> bool
{
255 use hir
::ExprKind
::{Field, Index, Path}
;
258 Path(QPath
::Resolved(_
, path
)) => !matches
!(path
.res
, Res
::Local(_
)),
260 Field(inner
, _
) | Index(inner
, _
) => is_mutated_static(inner
),
265 fn mutates_static
<'tcx
>(cx
: &LateContext
<'tcx
>, body
: &'tcx hir
::Body
<'_
>) -> bool
{
266 let mut v
= StaticMutVisitor
{
268 mutates_static
: false,
270 intravisit
::walk_expr(&mut v
, &body
.value
);