From c37916501db2c649f57d2cc0d84ee41db34094c4 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Sat, 27 Mar 2021 22:41:55 +0900 Subject: [PATCH] Move lints related to must_use to their own module --- clippy_lints/src/functions/mod.rs | 259 +---------------------- clippy_lints/src/functions/must_use.rs | 272 +++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 248 deletions(-) create mode 100644 clippy_lints/src/functions/must_use.rs diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index f53edc1b3c77..a1f4a453608d 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -1,22 +1,17 @@ +mod must_use; mod not_unsafe_ptr_arg_deref; mod too_many_arguments; mod too_many_lines; -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{is_must_use_ty, is_type_diagnostic_item}; -use clippy_utils::{attr_by_name, attrs::is_proc_macro, match_def_path, must_use_attr, return_ty, trait_ref_of_method}; +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::trait_ref_of_method; +use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; -use rustc_ast::ast::Attribute; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit; -use rustc_hir::{def::Res, def_id::DefId, QPath}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, Span}; use rustc_typeck::hir_ty_to_ty; @@ -260,88 +255,38 @@ impl<'tcx> LateLintPass<'tcx> for Functions { } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let attr = must_use_attr(attrs); - if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind { + must_use::check_item(cx, item); + if let hir::ItemKind::Fn(ref sig, ref _generics, _) = item.kind { let is_public = cx.access_levels.is_exported(item.hir_id()); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if is_public { check_result_unit_err(cx, &sig.decl, item.span, fn_header_span); } - if let Some(attr) = attr { - check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); - return; - } - if is_public && !is_proc_macro(cx.sess(), attrs) && attr_by_name(attrs, "no_mangle").is_none() { - check_must_use_candidate( - cx, - &sig.decl, - cx.tcx.hir().body(*body_id), - item.span, - item.hir_id(), - item.span.with_hi(sig.decl.output.span().hi()), - "this function could have a `#[must_use]` attribute", - ); - } } } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { - if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { + must_use::check_impl_item(cx, item); + if let hir::ImplItemKind::Fn(ref sig, _) = item.kind { let is_public = cx.access_levels.is_exported(item.hir_id()); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() { check_result_unit_err(cx, &sig.decl, item.span, fn_header_span); } - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let attr = must_use_attr(attrs); - if let Some(attr) = attr { - check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); - } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() - { - check_must_use_candidate( - cx, - &sig.decl, - cx.tcx.hir().body(*body_id), - item.span, - item.hir_id(), - item.span.with_hi(sig.decl.output.span().hi()), - "this method could have a `#[must_use]` attribute", - ); - } } } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold); not_unsafe_ptr_arg_deref::check_trait_item(cx, item); + must_use::check_trait_item(cx, item); - if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { + if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { let is_public = cx.access_levels.is_exported(item.hir_id()); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if is_public { check_result_unit_err(cx, &sig.decl, item.span, fn_header_span); } - - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let attr = must_use_attr(attrs); - if let Some(attr) = attr { - check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); - } - if let hir::TraitFn::Provided(eid) = *eid { - let body = cx.tcx.hir().body(eid); - if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) { - check_must_use_candidate( - cx, - &sig.decl, - body, - item.span, - item.hir_id(), - item.span.with_hi(sig.decl.output.span().hi()), - "this method could have a `#[must_use]` attribute", - ); - } - } } } } @@ -367,185 +312,3 @@ fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span } } } - -fn check_needless_must_use( - cx: &LateContext<'_>, - decl: &hir::FnDecl<'_>, - item_id: hir::HirId, - item_span: Span, - fn_header_span: Span, - attr: &Attribute, -) { - if in_external_macro(cx.sess(), item_span) { - return; - } - if returns_unit(decl) { - span_lint_and_then( - cx, - MUST_USE_UNIT, - fn_header_span, - "this unit-returning function has a `#[must_use]` attribute", - |diag| { - diag.span_suggestion( - attr.span, - "remove the attribute", - "".into(), - Applicability::MachineApplicable, - ); - }, - ); - } else if !attr.is_value_str() && is_must_use_ty(cx, return_ty(cx, item_id)) { - span_lint_and_help( - cx, - DOUBLE_MUST_USE, - fn_header_span, - "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`", - None, - "either add some descriptive text or remove the attribute", - ); - } -} - -fn check_must_use_candidate<'tcx>( - cx: &LateContext<'tcx>, - decl: &'tcx hir::FnDecl<'_>, - body: &'tcx hir::Body<'_>, - item_span: Span, - item_id: hir::HirId, - fn_span: Span, - msg: &str, -) { - if has_mutable_arg(cx, body) - || mutates_static(cx, body) - || in_external_macro(cx.sess(), item_span) - || returns_unit(decl) - || !cx.access_levels.is_exported(item_id) - || is_must_use_ty(cx, return_ty(cx, item_id)) - { - return; - } - span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { - if let Some(snippet) = snippet_opt(cx, fn_span) { - diag.span_suggestion( - fn_span, - "add the attribute", - format!("#[must_use] {}", snippet), - Applicability::MachineApplicable, - ); - } - }); -} - -fn returns_unit(decl: &hir::FnDecl<'_>) -> bool { - match decl.output { - hir::FnRetTy::DefaultReturn(_) => true, - hir::FnRetTy::Return(ref ty) => match ty.kind { - hir::TyKind::Tup(ref tys) => tys.is_empty(), - hir::TyKind::Never => true, - _ => false, - }, - } -} - -fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool { - let mut tys = FxHashSet::default(); - body.params.iter().any(|param| is_mutable_pat(cx, ¶m.pat, &mut tys)) -} - -fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet) -> bool { - if let hir::PatKind::Wild = pat.kind { - return false; // ignore `_` patterns - } - if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) { - is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys) - } else { - false - } -} - -static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]]; - -fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet) -> bool { - match *ty.kind() { - // primitive types are never mutable - ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false, - ty::Adt(ref adt, ref substs) => { - tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env) - || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path)) - && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)) - }, - ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)), - ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys), - ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { - mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys) - }, - // calling something constitutes a side effect, so return true on all callables - // also never calls need not be used, so return true for them, too - _ => true, - } -} - -struct StaticMutVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - mutates_static: bool, -} - -impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { - use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall}; - - if self.mutates_static { - return; - } - match expr.kind { - Call(_, args) | MethodCall(_, _, args, _) => { - let mut tys = FxHashSet::default(); - for arg in args { - if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) - && is_mutable_ty( - self.cx, - self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg), - arg.span, - &mut tys, - ) - && is_mutated_static(arg) - { - self.mutates_static = true; - return; - } - tys.clear(); - } - }, - Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => { - self.mutates_static |= is_mutated_static(target) - }, - _ => {}, - } - } - - fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { - intravisit::NestedVisitorMap::None - } -} - -fn is_mutated_static(e: &hir::Expr<'_>) -> bool { - use hir::ExprKind::{Field, Index, Path}; - - match e.kind { - Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)), - Path(_) => true, - Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(inner), - _ => false, - } -} - -fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool { - let mut v = StaticMutVisitor { - cx, - mutates_static: false, - }; - intravisit::walk_expr(&mut v, &body.value); - v.mutates_static -} diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs new file mode 100644 index 000000000000..3825699936ff --- /dev/null +++ b/clippy_lints/src/functions/must_use.rs @@ -0,0 +1,272 @@ +use rustc_ast::ast::Attribute; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def::Res, def_id::DefId, intravisit, QPath}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::{ + hir::map::Map, + lint::in_external_macro, + ty::{self, Ty}, +}; +use rustc_span::Span; + +use clippy_utils::attrs::is_proc_macro; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::is_must_use_ty; +use clippy_utils::{attr_by_name, match_def_path, must_use_attr, return_ty, trait_ref_of_method}; + +use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; + +pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let attr = must_use_attr(attrs); + if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind { + let is_public = cx.access_levels.is_exported(item.hir_id()); + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + if let Some(attr) = attr { + check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); + return; + } else if is_public && !is_proc_macro(cx.sess(), attrs) && attr_by_name(attrs, "no_mangle").is_none() { + check_must_use_candidate( + cx, + &sig.decl, + cx.tcx.hir().body(*body_id), + item.span, + item.hir_id(), + item.span.with_hi(sig.decl.output.span().hi()), + "this function could have a `#[must_use]` attribute", + ); + } + } +} + +pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { + if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind { + let is_public = cx.access_levels.is_exported(item.hir_id()); + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let attr = must_use_attr(attrs); + if let Some(attr) = attr { + check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); + } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() { + check_must_use_candidate( + cx, + &sig.decl, + cx.tcx.hir().body(*body_id), + item.span, + item.hir_id(), + item.span.with_hi(sig.decl.output.span().hi()), + "this method could have a `#[must_use]` attribute", + ); + } + } +} + +pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { + if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind { + let is_public = cx.access_levels.is_exported(item.hir_id()); + let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); + + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let attr = must_use_attr(attrs); + if let Some(attr) = attr { + check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); + } else if let hir::TraitFn::Provided(eid) = *eid { + let body = cx.tcx.hir().body(eid); + if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) { + check_must_use_candidate( + cx, + &sig.decl, + body, + item.span, + item.hir_id(), + item.span.with_hi(sig.decl.output.span().hi()), + "this method could have a `#[must_use]` attribute", + ); + } + } + } +} + +fn check_needless_must_use( + cx: &LateContext<'_>, + decl: &hir::FnDecl<'_>, + item_id: hir::HirId, + item_span: Span, + fn_header_span: Span, + attr: &Attribute, +) { + if in_external_macro(cx.sess(), item_span) { + return; + } + if returns_unit(decl) { + span_lint_and_then( + cx, + MUST_USE_UNIT, + fn_header_span, + "this unit-returning function has a `#[must_use]` attribute", + |diag| { + diag.span_suggestion( + attr.span, + "remove the attribute", + "".into(), + Applicability::MachineApplicable, + ); + }, + ); + } else if !attr.is_value_str() && is_must_use_ty(cx, return_ty(cx, item_id)) { + span_lint_and_help( + cx, + DOUBLE_MUST_USE, + fn_header_span, + "this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`", + None, + "either add some descriptive text or remove the attribute", + ); + } +} + +fn check_must_use_candidate<'tcx>( + cx: &LateContext<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + body: &'tcx hir::Body<'_>, + item_span: Span, + item_id: hir::HirId, + fn_span: Span, + msg: &str, +) { + if has_mutable_arg(cx, body) + || mutates_static(cx, body) + || in_external_macro(cx.sess(), item_span) + || returns_unit(decl) + || !cx.access_levels.is_exported(item_id) + || is_must_use_ty(cx, return_ty(cx, item_id)) + { + return; + } + span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| { + if let Some(snippet) = snippet_opt(cx, fn_span) { + diag.span_suggestion( + fn_span, + "add the attribute", + format!("#[must_use] {}", snippet), + Applicability::MachineApplicable, + ); + } + }); +} + +fn returns_unit(decl: &hir::FnDecl<'_>) -> bool { + match decl.output { + hir::FnRetTy::DefaultReturn(_) => true, + hir::FnRetTy::Return(ref ty) => match ty.kind { + hir::TyKind::Tup(ref tys) => tys.is_empty(), + hir::TyKind::Never => true, + _ => false, + }, + } +} + +fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool { + let mut tys = FxHashSet::default(); + body.params.iter().any(|param| is_mutable_pat(cx, ¶m.pat, &mut tys)) +} + +fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet) -> bool { + if let hir::PatKind::Wild = pat.kind { + return false; // ignore `_` patterns + } + if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) { + is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys) + } else { + false + } +} + +static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]]; + +fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet) -> bool { + match *ty.kind() { + // primitive types are never mutable + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false, + ty::Adt(ref adt, ref substs) => { + tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env) + || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path)) + && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)) + }, + ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)), + ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys), + ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { + mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys) + }, + // calling something constitutes a side effect, so return true on all callables + // also never calls need not be used, so return true for them, too + _ => true, + } +} + +struct StaticMutVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + mutates_static: bool, +} + +impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall}; + + if self.mutates_static { + return; + } + match expr.kind { + Call(_, args) | MethodCall(_, _, args, _) => { + let mut tys = FxHashSet::default(); + for arg in args { + if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) + && is_mutable_ty( + self.cx, + self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg), + arg.span, + &mut tys, + ) + && is_mutated_static(arg) + { + self.mutates_static = true; + return; + } + tys.clear(); + } + }, + Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => { + self.mutates_static |= is_mutated_static(target) + }, + _ => {}, + } + } + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::None + } +} + +fn is_mutated_static(e: &hir::Expr<'_>) -> bool { + use hir::ExprKind::{Field, Index, Path}; + + match e.kind { + Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)), + Path(_) => true, + Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(inner), + _ => false, + } +} + +fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool { + let mut v = StaticMutVisitor { + cx, + mutates_static: false, + }; + intravisit::walk_expr(&mut v, &body.value); + v.mutates_static +}