Merge commit 'a5d597637dcb78dc73f93561ce474f23d4177c35' into clippyup

This commit is contained in:
flip1995
2021-12-06 12:33:31 +01:00
parent 35a0060aba
commit 8fea1d94f3
491 changed files with 9888 additions and 3255 deletions

View File

@@ -243,6 +243,7 @@ pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> b
eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
}
#[allow(clippy::too_many_lines)] // Just a big match statement
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
use ItemKind::*;
match (l, r) {
@@ -250,8 +251,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
(Use(l), Use(r)) => eq_use_tree(l, r),
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }),
Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => {
(
Fn(box ast::Fn {
defaultness: ld,
sig: lf,
generics: lg,
body: lb,
}),
Fn(box ast::Fn {
defaultness: rd,
sig: rf,
generics: rg,
body: rb,
}),
) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(Mod(lu, lmk), Mod(ru, rmk)) => {
@@ -267,8 +280,20 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
(ForeignMod(l), ForeignMod(r)) => {
both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
},
(TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }),
TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => {
(
TyAlias(box ast::TyAlias {
defaultness: ld,
generics: lg,
bounds: lb,
ty: lt,
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, eq_generic_bound)
@@ -278,8 +303,22 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
(Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
eq_variant_data(lv, rv) && eq_generics(lg, rg)
},
(Trait(box ast::Trait { is_auto: la, unsafety: lu, generics: lg, bounds: lb, items: li }),
Trait(box ast::Trait { is_auto: ra, unsafety: ru, generics: rg, bounds: rb, items: ri })) => {
(
Trait(box ast::Trait {
is_auto: la,
unsafety: lu,
generics: lg,
bounds: lb,
items: li,
}),
Trait(box ast::Trait {
is_auto: ra,
unsafety: ru,
generics: rg,
bounds: rb,
items: ri,
}),
) => {
la == ra
&& matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& eq_generics(lg, rg)
@@ -328,12 +367,36 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
use ForeignItemKind::*;
match (l, r) {
(Static(lt, lm, le), Static(rt, rm, re)) => lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }),
Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => {
(
Fn(box ast::Fn {
defaultness: ld,
sig: lf,
generics: lg,
body: lb,
}),
Fn(box ast::Fn {
defaultness: rd,
sig: rf,
generics: rg,
body: rb,
}),
) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }),
TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => {
(
TyAlias(box ast::TyAlias {
defaultness: ld,
generics: lg,
bounds: lb,
ty: lt,
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, eq_generic_bound)
@@ -348,12 +411,36 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
use AssocItemKind::*;
match (l, r) {
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }),
Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => {
(
Fn(box ast::Fn {
defaultness: ld,
sig: lf,
generics: lg,
body: lb,
}),
Fn(box ast::Fn {
defaultness: rd,
sig: rf,
generics: rg,
body: rb,
}),
) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }),
TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => {
(
TyAlias(box ast::TyAlias {
defaultness: ld,
generics: lg,
bounds: lb,
ty: lt,
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, eq_generic_bound)

View File

@@ -14,15 +14,14 @@ pub enum DeprecationStatus {
None,
}
#[rustfmt::skip]
pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
("author", DeprecationStatus::None),
("cognitive_complexity", DeprecationStatus::None),
(
"cyclomatic_complexity",
DeprecationStatus::Replaced("cognitive_complexity"),
),
("dump", DeprecationStatus::None),
("msrv", DeprecationStatus::None),
("author", DeprecationStatus::None),
("version", DeprecationStatus::None),
("cognitive_complexity", DeprecationStatus::None),
("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")),
("dump", DeprecationStatus::None),
("msrv", DeprecationStatus::None),
];
pub struct LimitStack {

View File

@@ -9,128 +9,227 @@
//! - or-fun-call
//! - option-if-let-else
use crate::is_ctor_or_promotable_const_function;
use crate::ty::is_type_diagnostic_item;
use crate::ty::{all_predicates_of, is_copy};
use crate::visitors::is_const_evaluatable;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_span::sym;
use rustc_middle::ty::{self, PredicateKind};
use rustc_span::{sym, Symbol};
use std::cmp;
use std::ops;
/// Is the expr pure (is it free from side-effects)?
/// This function is named so to stress that it isn't exhaustive and returns FNs.
fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(identify_some_pure_patterns),
ExprKind::Struct(_, fields, expr) => {
fields.iter().all(|f| identify_some_pure_patterns(f.expr)) && expr.map_or(true, identify_some_pure_patterns)
},
ExprKind::Call(
&Expr {
kind:
ExprKind::Path(QPath::Resolved(
_,
Path {
res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
..
},
)),
..
},
args,
) => args.iter().all(identify_some_pure_patterns),
ExprKind::Block(
&Block {
stmts,
expr: Some(expr),
..
},
_,
) => stmts.is_empty() && identify_some_pure_patterns(expr),
ExprKind::Box(..)
| ExprKind::Array(..)
| ExprKind::Call(..)
| ExprKind::MethodCall(..)
| ExprKind::Binary(..)
| ExprKind::Unary(..)
| ExprKind::Let(..)
| ExprKind::Cast(..)
| ExprKind::Type(..)
| ExprKind::DropTemps(..)
| ExprKind::Loop(..)
| ExprKind::If(..)
| ExprKind::Match(..)
| ExprKind::Closure(..)
| ExprKind::Block(..)
| ExprKind::Assign(..)
| ExprKind::AssignOp(..)
| ExprKind::Index(..)
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::InlineAsm(..)
| ExprKind::LlvmInlineAsm(..)
| ExprKind::Repeat(..)
| ExprKind::Yield(..)
| ExprKind::Err => false,
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum EagernessSuggestion {
// The expression is cheap and should be evaluated eagerly
Eager,
// The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to
// eager evaluation.
NoChange,
// The expression is likely expensive and should be evaluated lazily.
Lazy,
// The expression cannot be placed into a closure.
ForceNoChange,
}
impl ops::BitOr for EagernessSuggestion {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
cmp::max(self, rhs)
}
}
impl ops::BitOrAssign for EagernessSuggestion {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
/// Identify some potentially computationally expensive patterns.
/// This function is named so to stress that its implementation is non-exhaustive.
/// It returns FNs and FPs.
fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
// Searches an expression for method calls or function calls that aren't ctors
struct FunCallFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
found: bool,
/// Determine the eagerness of the given function call.
fn fn_eagerness(cx: &LateContext<'tcx>, fn_id: DefId, name: Symbol, args: &'tcx [Expr<'_>]) -> EagernessSuggestion {
use EagernessSuggestion::{Eager, Lazy, NoChange};
let name = &*name.as_str();
let ty = match cx.tcx.impl_of_method(fn_id) {
Some(id) => cx.tcx.type_of(id),
None => return Lazy,
};
if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 {
if matches!(
cx.tcx.crate_name(fn_id.krate),
sym::std | sym::core | sym::alloc | sym::proc_macro
) {
Eager
} else {
NoChange
}
} else if let ty::Adt(def, subs) = ty.kind() {
// Types where the only fields are generic types (or references to) with no trait bounds other
// than marker traits.
// Due to the limited operations on these types functions should be fairly cheap.
if def
.variants
.iter()
.flat_map(|v| v.fields.iter())
.any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
&& all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker,
_ => true,
})
&& subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
{
// Limit the function to either `(self) -> bool` or `(&self) -> bool`
match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output {
[arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
_ => Lazy,
}
} else {
Lazy
}
} else {
Lazy
}
}
#[allow(clippy::too_many_lines)]
fn expr_eagerness(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
eagerness: EagernessSuggestion,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
let call_found = match &expr.kind {
// ignore enum and struct constructors
ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
ExprKind::Index(obj, _) => {
let ty = self.cx.typeck_results().expr_ty(obj);
is_type_diagnostic_item(self.cx, ty, sym::HashMap)
|| is_type_diagnostic_item(self.cx, ty, sym::BTreeMap)
},
ExprKind::MethodCall(..) => true,
_ => false,
};
if call_found {
self.found |= true;
}
if !self.found {
intravisit::walk_expr(self, expr);
}
}
impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
if self.eagerness == ForceNoChange {
return;
}
match e.kind {
ExprKind::Call(
&Expr {
kind: ExprKind::Path(ref path),
hir_id,
..
},
args,
) => match self.cx.qpath_res(path, hir_id) {
Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (),
Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (),
// No need to walk the arguments here, `is_const_evaluatable` already did
Res::Def(..) if is_const_evaluatable(self.cx, e) => {
self.eagerness |= NoChange;
return;
},
Res::Def(_, id) => match path {
QPath::Resolved(_, p) => {
self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args);
},
QPath::TypeRelative(_, name) => {
self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args);
},
QPath::LangItem(..) => self.eagerness = Lazy,
},
_ => self.eagerness = Lazy,
},
// No need to walk the arguments here, `is_const_evaluatable` already did
ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => {
self.eagerness |= NoChange;
return;
},
ExprKind::MethodCall(name, _, args, _) => {
self.eagerness |= self
.cx
.typeck_results()
.type_dependent_def_id(e.hir_id)
.map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args));
},
ExprKind::Index(_, e) => {
let ty = self.cx.typeck_results().expr_ty_adjusted(e);
if is_copy(self.cx, ty) && !ty.is_ref() {
self.eagerness |= NoChange;
} else {
self.eagerness = Lazy;
}
},
// Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe.
ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (),
ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
ExprKind::Unary(_, e)
if matches!(
self.cx.typeck_results().expr_ty(e).kind(),
ty::Bool | ty::Int(_) | ty::Uint(_),
) => {},
ExprKind::Binary(_, lhs, rhs)
if self.cx.typeck_results().expr_ty(lhs).is_primitive()
&& self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
// Can't be moved into a closure
ExprKind::Break(..)
| ExprKind::Continue(_)
| ExprKind::Ret(_)
| ExprKind::InlineAsm(_)
| ExprKind::LlvmInlineAsm(_)
| ExprKind::Yield(..)
| ExprKind::Err => {
self.eagerness = ForceNoChange;
return;
},
// Memory allocation, custom operator, loop, or call to an unknown function
ExprKind::Box(_)
| ExprKind::Unary(..)
| ExprKind::Binary(..)
| ExprKind::Loop(..)
| ExprKind::Call(..) => self.eagerness = Lazy,
ExprKind::ConstBlock(_)
| ExprKind::Array(_)
| ExprKind::Tup(_)
| ExprKind::Lit(_)
| ExprKind::Cast(..)
| ExprKind::Type(..)
| ExprKind::DropTemps(_)
| ExprKind::Let(..)
| ExprKind::If(..)
| ExprKind::Match(..)
| ExprKind::Closure(..)
| ExprKind::Field(..)
| ExprKind::Path(_)
| ExprKind::AddrOf(..)
| ExprKind::Struct(..)
| ExprKind::Repeat(..)
| ExprKind::Block(Block { stmts: [], .. }, _) => (),
// Assignment might be to a local defined earlier, so don't eagerly evaluate.
// Blocks with multiple statements might be expensive, so don't eagerly evaluate.
// TODO: Actually check if either of these are true here.
ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
}
walk_expr(self, e);
}
}
let mut finder = FunCallFinder { cx, found: false };
finder.visit_expr(expr);
finder.found
let mut v = V {
cx,
eagerness: EagernessSuggestion::Eager,
};
v.visit_expr(e);
v.eagerness
}
pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
!identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
/// Whether the given expression should be changed to evaluate eagerly
pub fn switch_to_eager_eval(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
expr_eagerness(cx, expr) == EagernessSuggestion::Eager
}
pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
identify_some_potentially_expensive_patterns(cx, expr)
/// Whether the given expression should be changed to evaluate lazily
pub fn switch_to_lazy_eval(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
}

View File

@@ -1,6 +1,7 @@
#![feature(box_patterns)]
#![feature(in_band_lifetimes)]
#![feature(iter_zip)]
#![feature(let_else)]
#![feature(rustc_private)]
#![feature(control_flow_enum)]
#![recursion_limit = "512"]
@@ -68,7 +69,7 @@ use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::intravisit::{walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
use rustc_hir::{
@@ -96,6 +97,7 @@ use rustc_target::abi::Integer;
use crate::consts::{constant, Constant};
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
use crate::visitors::expr_visitor_no_bodies;
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
@@ -250,12 +252,6 @@ pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem
false
}
/// Returns `true` if this `span` was expanded by any macro.
#[must_use]
pub fn in_macro(span: Span) -> bool {
span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
}
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
@@ -1113,63 +1109,30 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
/// Returns `true` if `expr` contains a return expression
pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
struct RetCallFinder {
found: bool,
}
impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if self.found {
return;
}
let mut found = false;
expr_visitor_no_bodies(|expr| {
if !found {
if let hir::ExprKind::Ret(..) = &expr.kind {
self.found = true;
} else {
hir::intravisit::walk_expr(self, expr);
found = true;
}
}
fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
hir::intravisit::NestedVisitorMap::None
}
}
let mut visitor = RetCallFinder { found: false };
visitor.visit_expr(expr);
visitor.found
}
struct FindMacroCalls<'a, 'b> {
names: &'a [&'b str],
result: Vec<Span>,
}
impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
self.result.push(expr.span);
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
!found
})
.visit_expr(expr);
found
}
/// Finds calls of the specified macros in a function body.
pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
let mut fmc = FindMacroCalls {
names,
result: Vec::new(),
};
fmc.visit_expr(&body.value);
fmc.result
let mut result = Vec::new();
expr_visitor_no_bodies(|expr| {
if names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
result.push(expr.span);
}
true
})
.visit_expr(&body.value);
result
}
/// Extends the span to the beginning of the spans line, incl. whitespaces.
@@ -1439,7 +1402,7 @@ pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>
/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
/// implementations have.
pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
attrs.iter().any(|attr| attr.has_name(sym::automatically_derived))
has_attr(attrs, sym::automatically_derived)
}
/// Remove blocks around an expression.
@@ -1561,20 +1524,29 @@ pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::UintTy) -> u128 {
(u << amt) >> amt
}
pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool {
attrs.iter().any(|attr| attr.has_name(symbol))
}
pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
let map = &tcx.hir();
let mut prev_enclosing_node = None;
let mut enclosing_node = node;
while Some(enclosing_node) != prev_enclosing_node {
if is_automatically_derived(map.attrs(enclosing_node)) {
if has_attr(map.attrs(enclosing_node), symbol) {
return true;
}
prev_enclosing_node = Some(enclosing_node);
enclosing_node = map.get_parent_item(enclosing_node);
}
false
}
pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
any_parent_has_attr(tcx, node, sym::automatically_derived)
}
/// Matches a function call with the given path and returns the arguments.
///
/// Usage:
@@ -1625,6 +1597,14 @@ pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -
syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
}
/// Checks if the given `DefId` matches the `libc` item.
pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
let path = cx.get_def_path(did);
// libc is meant to be used as a flat list of names, but they're all actually defined in different
// modules based on the target platform. Ignore everything but crate name and the item name.
path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name)
}
pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Call(func, [arg]) = expr.kind {
expr_path_res(cx, func)
@@ -1837,6 +1817,16 @@ pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
}
pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
if !is_no_std_crate(cx) {
Some("std")
} else if !is_no_core_crate(cx) {
Some("core")
} else {
None
}
}
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
@@ -1847,6 +1837,16 @@ pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
})
}
pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
attr.path == sym::no_core
} else {
false
}
})
}
/// Check if parent of a hir node is a trait implementation block.
/// For example, `f` in
/// ```rust,ignore

View File

@@ -19,7 +19,7 @@ msrv_aliases! {
1,46,0 { CONST_IF_MATCH }
1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2 }
1,42,0 { MATCHES_MACRO }
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS }
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
1,38,0 { POINTER_CAST }
@@ -27,7 +27,8 @@ msrv_aliases! {
1,36,0 { ITERATOR_COPIED }
1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
1,34,0 { TRY_FROM }
1,30,0 { ITERATOR_FIND_MAP }
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
1,28,0 { FROM_BOOL }
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST }
1,16,0 { STR_REPEAT }
}

View File

@@ -28,6 +28,7 @@ pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
/// Preferably use the diagnostic item `sym::Borrow` where possible
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
pub const BORROW_MUT_TRAIT: [&str; 3] = ["core", "borrow", "BorrowMut"];
pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
@@ -85,7 +86,6 @@ pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tup
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal-lints")]
pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
pub const LIBC_STRLEN: [&str; 2] = ["libc", "strlen"];
#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
@@ -110,6 +110,8 @@ pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"];
pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"];
pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];
pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"];
pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"];
@@ -204,3 +206,4 @@ pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
pub const WRITE_MACRO: [&str; 3] = ["core", "macros", "write"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const WRITELN_MACRO: [&str; 3] = ["core", "macros", "writeln"];
pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"];

View File

@@ -1,9 +1,9 @@
use crate::source::snippet;
use crate::visitors::expr_visitor_no_bodies;
use crate::{path_to_local_id, strip_pat_refs};
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, PatKind};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_span::Span;
use std::borrow::Cow;
@@ -30,50 +30,28 @@ fn extract_clone_suggestions<'tcx>(
replace: &[(&'static str, &'static str)],
body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> {
let mut visitor = PtrCloneVisitor {
cx,
id,
replace,
spans: vec![],
abort: false,
};
visitor.visit_body(body);
if visitor.abort { None } else { Some(visitor.spans) }
}
struct PtrCloneVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
id: HirId,
replace: &'a [(&'static str, &'static str)],
spans: Vec<(Span, Cow<'static, str>)>,
abort: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.abort {
return;
let mut abort = false;
let mut spans = Vec::new();
expr_visitor_no_bodies(|expr| {
if abort {
return false;
}
if let ExprKind::MethodCall(seg, _, [recv], _) = expr.kind {
if path_to_local_id(recv, self.id) {
if path_to_local_id(recv, id) {
if seg.ident.name.as_str() == "capacity" {
self.abort = true;
return;
abort = true;
return false;
}
for &(fn_name, suffix) in self.replace {
for &(fn_name, suffix) in replace {
if seg.ident.name.as_str() == fn_name {
self.spans.push((expr.span, snippet(self.cx, recv.span, "_") + suffix));
return;
spans.push((expr.span, snippet(cx, recv.span, "_") + suffix));
return false;
}
}
}
}
walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
!abort
})
.visit_body(body);
if abort { None } else { Some(spans) }
}

View File

@@ -128,7 +128,7 @@ pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<us
fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
let x = s
.lines()
.skip(ignore_first as usize)
.skip(usize::from(ignore_first))
.filter_map(|l| {
if l.is_empty() {
None
@@ -155,14 +155,22 @@ fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>,
.join("\n")
}
/// Converts a span to a code snippet if available, otherwise use default.
/// Converts a span to a code snippet if available, otherwise returns the default.
///
/// This is useful if you want to provide suggestions for your lint or more generally, if you want
/// to convert a given `Span` to a `str`.
/// to convert a given `Span` to a `str`. To create suggestions consider using
/// [`snippet_with_applicability`] to ensure that the applicability stays correct.
///
/// # Example
/// ```rust,ignore
/// snippet(cx, expr.span, "..")
/// // Given two spans one for `value` and one for the `init` expression.
/// let value = Vec::new();
/// // ^^^^^ ^^^^^^^^^^
/// // span1 span2
///
/// // The snipped call would return the corresponding code snippet
/// snippet(cx, span1, "..") // -> "value"
/// snippet(cx, span2, "..") // -> "Vec::new()"
/// ```
pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from)

View File

@@ -1,19 +1,27 @@
//! Contains utility functions to generate suggestions.
#![deny(clippy::missing_docs_in_private_items)]
use crate::higher;
use crate::source::{snippet, snippet_opt, snippet_with_context, snippet_with_macro_callsite};
use crate::source::{
snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite,
};
use crate::{get_parent_expr_for_hir, higher};
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{ast, token};
use rustc_ast_pretty::pprust::token_kind_to_string;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{ExprKind, HirId, MutTy, TyKind};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{EarlyContext, LateContext, LintContext};
use rustc_span::source_map::{CharPos, Span};
use rustc_span::{BytePos, Pos, SyntaxContext};
use rustc_middle::hir::place::ProjectionKind;
use rustc_middle::mir::{FakeReadCause, Mutability};
use rustc_middle::ty;
use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext};
use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use std::borrow::Cow;
use std::convert::TryInto;
use std::fmt::Display;
use std::iter;
use std::ops::{Add, Neg, Not, Sub};
/// A helper type to build suggestion correctly handling parentheses.
@@ -716,6 +724,267 @@ impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder
}
}
/// Suggestion results for handling closure
/// args dereferencing and borrowing
pub struct DerefClosure {
/// confidence on the built suggestion
pub applicability: Applicability,
/// gradually built suggestion
pub suggestion: String,
}
/// Build suggestion gradually by handling closure arg specific usages,
/// such as explicit deref and borrowing cases.
/// Returns `None` if no such use cases have been triggered in closure body
///
/// note: this only works on single line immutable closures with exactly one input parameter.
pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'_>) -> Option<DerefClosure> {
if let hir::ExprKind::Closure(_, fn_decl, body_id, ..) = closure.kind {
let closure_body = cx.tcx.hir().body(body_id);
// is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`)
// a type annotation is present if param `kind` is different from `TyKind::Infer`
let closure_arg_is_type_annotated_double_ref = if let TyKind::Rptr(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind
{
matches!(ty.kind, TyKind::Rptr(_, MutTy { .. }))
} else {
false
};
let mut visitor = DerefDelegate {
cx,
closure_span: closure.span,
closure_arg_is_type_annotated_double_ref,
next_pos: closure.span.lo(),
suggestion_start: String::new(),
applicability: Applicability::MaybeIncorrect,
};
let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id);
cx.tcx.infer_ctxt().enter(|infcx| {
ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
.consume_body(closure_body);
});
if !visitor.suggestion_start.is_empty() {
return Some(DerefClosure {
applicability: visitor.applicability,
suggestion: visitor.finish(),
});
}
}
None
}
/// Visitor struct used for tracking down
/// dereferencing and borrowing of closure's args
struct DerefDelegate<'a, 'tcx> {
/// The late context of the lint
cx: &'a LateContext<'tcx>,
/// The span of the input closure to adapt
closure_span: Span,
/// Indicates if the arg of the closure is a type annotated double reference
closure_arg_is_type_annotated_double_ref: bool,
/// last position of the span to gradually build the suggestion
next_pos: BytePos,
/// starting part of the gradually built suggestion
suggestion_start: String,
/// confidence on the built suggestion
applicability: Applicability,
}
impl DerefDelegate<'_, 'tcx> {
/// build final suggestion:
/// - create the ending part of suggestion
/// - concatenate starting and ending parts
/// - potentially remove needless borrowing
pub fn finish(&mut self) -> String {
let end_span = Span::new(self.next_pos, self.closure_span.hi(), self.closure_span.ctxt(), None);
let end_snip = snippet_with_applicability(self.cx, end_span, "..", &mut self.applicability);
let sugg = format!("{}{}", self.suggestion_start, end_snip);
if self.closure_arg_is_type_annotated_double_ref {
sugg.replacen('&', "", 1)
} else {
sugg
}
}
/// indicates whether the function from `parent_expr` takes its args by double reference
fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
let (call_args, inputs) = match parent_expr.kind {
ExprKind::MethodCall(_, _, call_args, _) => {
if let Some(method_did) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) {
(call_args, self.cx.tcx.fn_sig(method_did).skip_binder().inputs())
} else {
return false;
}
},
ExprKind::Call(func, call_args) => {
let typ = self.cx.typeck_results().expr_ty(func);
(call_args, typ.fn_sig(self.cx.tcx).skip_binder().inputs())
},
_ => return false,
};
iter::zip(call_args, inputs)
.any(|(arg, ty)| arg.hir_id == cmt_hir_id && matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
}
}
impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
#[allow(clippy::too_many_lines)]
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
if let PlaceBase::Local(id) = cmt.place.base {
let map = self.cx.tcx.hir();
let span = map.span(cmt.hir_id);
let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
// identifier referring to the variable currently triggered (i.e.: `fp`)
let ident_str = map.name(id).to_string();
// full identifier that includes projection (i.e.: `fp.field`)
let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
if cmt.place.projections.is_empty() {
// handle item without any projection, that needs an explicit borrowing
// i.e.: suggest `&x` instead of `x`
self.suggestion_start.push_str(&format!("{}&{}", start_snip, ident_str));
} else {
// cases where a parent `Call` or `MethodCall` is using the item
// i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()`
//
// Note about method calls:
// - compiler automatically dereference references if the target type is a reference (works also for
// function call)
// - `self` arguments in the case of `x.is_something()` are also automatically (de)referenced, and
// no projection should be suggested
if let Some(parent_expr) = get_parent_expr_for_hir(self.cx, cmt.hir_id) {
match &parent_expr.kind {
// given expression is the self argument and will be handled completely by the compiler
// i.e.: `|x| x.is_something()`
ExprKind::MethodCall(_, _, [self_expr, ..], _) if self_expr.hir_id == cmt.hir_id => {
self.suggestion_start
.push_str(&format!("{}{}", start_snip, ident_str_with_proj));
self.next_pos = span.hi();
return;
},
// item is used in a call
// i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)`
ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, _, [_, call_args @ ..], _) => {
let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id);
let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind();
if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) {
// suggest ampersand if call function is taking args by double reference
let takes_arg_by_double_ref =
self.func_takes_arg_by_double_ref(parent_expr, cmt.hir_id);
// compiler will automatically dereference field or index projection, so no need
// to suggest ampersand, but full identifier that includes projection is required
let has_field_or_index_projection =
cmt.place.projections.iter().any(|proj| {
matches!(proj.kind, ProjectionKind::Field(..) | ProjectionKind::Index)
});
// no need to bind again if the function doesn't take arg by double ref
// and if the item is already a double ref
let ident_sugg = if !call_args.is_empty()
&& !takes_arg_by_double_ref
&& (self.closure_arg_is_type_annotated_double_ref || has_field_or_index_projection)
{
let ident = if has_field_or_index_projection {
ident_str_with_proj
} else {
ident_str
};
format!("{}{}", start_snip, ident)
} else {
format!("{}&{}", start_snip, ident_str)
};
self.suggestion_start.push_str(&ident_sugg);
self.next_pos = span.hi();
return;
}
self.applicability = Applicability::Unspecified;
},
_ => (),
}
}
let mut replacement_str = ident_str;
let mut projections_handled = false;
cmt.place.projections.iter().enumerate().for_each(|(i, proj)| {
match proj.kind {
// Field projection like `|v| v.foo`
// no adjustment needed here, as field projections are handled by the compiler
ProjectionKind::Field(..) => match cmt.place.ty_before_projection(i).kind() {
ty::Adt(..) | ty::Tuple(_) => {
replacement_str = ident_str_with_proj.clone();
projections_handled = true;
},
_ => (),
},
// Index projection like `|x| foo[x]`
// the index is dropped so we can't get it to build the suggestion,
// so the span is set-up again to get more code, using `span.hi()` (i.e.: `foo[x]`)
// instead of `span.lo()` (i.e.: `foo`)
ProjectionKind::Index => {
let start_span = Span::new(self.next_pos, span.hi(), span.ctxt(), None);
start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
replacement_str.clear();
projections_handled = true;
},
// note: unable to trigger `Subslice` kind in tests
ProjectionKind::Subslice => (),
ProjectionKind::Deref => {
// Explicit derefs are typically handled later on, but
// some items do not need explicit deref, such as array accesses,
// so we mark them as already processed
// i.e.: don't suggest `*sub[1..4].len()` for `|sub| sub[1..4].len() == 3`
if let ty::Ref(_, inner, _) = cmt.place.ty_before_projection(i).kind() {
if matches!(inner.kind(), ty::Ref(_, innermost, _) if innermost.is_array()) {
projections_handled = true;
}
}
},
}
});
// handle `ProjectionKind::Deref` by removing one explicit deref
// if no special case was detected (i.e.: suggest `*x` instead of `**x`)
if !projections_handled {
let last_deref = cmt
.place
.projections
.iter()
.rposition(|proj| proj.kind == ProjectionKind::Deref);
if let Some(pos) = last_deref {
let mut projections = cmt.place.projections.clone();
projections.truncate(pos);
for item in projections {
if item.kind == ProjectionKind::Deref {
replacement_str = format!("*{}", replacement_str);
}
}
}
}
self.suggestion_start
.push_str(&format!("{}{}", start_snip, replacement_str));
}
self.next_pos = span.hi();
}
}
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
}
#[cfg(test)]
mod test {
use super::Sugg;

View File

@@ -10,15 +10,16 @@ use rustc_hir::{TyKind, Unsafety};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, TypeFoldable, UintTy};
use rustc_span::sym;
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::DUMMY_SP;
use rustc_middle::ty::{self, AdtDef, IntTy, Predicate, Ty, TyCtxt, TypeFoldable, UintTy};
use rustc_span::symbol::Ident;
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::query::normalize::AtExt;
use std::iter;
use crate::{match_def_path, must_use_attr};
// Checks if the given type implements copy.
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env)
}
@@ -114,7 +115,12 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<
/// Checks whether a type implements a trait.
/// The function returns false in case the type contains an inference variable.
/// See also [`get_trait_def_id`](super::get_trait_def_id).
///
/// See:
/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`].
/// * [Common tools for writing lints] for an example how to use this function and other options.
///
/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
pub fn implements_trait<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
@@ -254,9 +260,17 @@ pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_ite
}
}
/// Checks if the type is equal to a diagnostic item
/// Checks if the type is equal to a diagnostic item. To check if a type implements a
/// trait marked with a diagnostic item use [`implements_trait`].
///
/// For a further exploitation what diagnostic items are see [diagnostic items] in
/// rustc-dev-guide.
///
/// ---
///
/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
///
/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
match ty.kind() {
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
@@ -377,3 +391,16 @@ pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
_ => false,
}
}
/// Gets an iterator over all predicates which apply to the given item.
pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(Predicate<'_>, Span)> {
let mut next_id = Some(id);
iter::from_fn(move || {
next_id.take().map(|id| {
let preds = tcx.predicates_of(id);
next_id = preds.parent;
preds.predicates.iter()
})
})
.flatten()
}

View File

@@ -1,7 +1,7 @@
use crate as utils;
use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::HirIdSet;
use rustc_hir::{Expr, ExprKind, HirId};
use rustc_infer::infer::TyCtxtInferExt;
@@ -148,96 +148,47 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
}
}
struct ReturnBreakContinueMacroVisitor {
seen_return_break_continue: bool,
}
impl ReturnBreakContinueMacroVisitor {
fn new() -> ReturnBreakContinueMacroVisitor {
ReturnBreakContinueMacroVisitor {
seen_return_break_continue: false,
}
}
}
impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if self.seen_return_break_continue {
// No need to look farther if we've already seen one of them
return;
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
let mut seen_return_break_continue = false;
expr_visitor_no_bodies(|ex| {
if seen_return_break_continue {
return false;
}
match &ex.kind {
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
self.seen_return_break_continue = true;
seen_return_break_continue = true;
},
// Something special could be done here to handle while or for loop
// desugaring, as this will detect a break if there's a while loop
// or a for loop inside the expression.
_ => {
if utils::in_macro(ex.span) {
self.seen_return_break_continue = true;
} else {
rustc_hir::intravisit::walk_expr(self, ex);
if ex.span.from_expansion() {
seen_return_break_continue = true;
}
},
}
}
!seen_return_break_continue
})
.visit_expr(expression);
seen_return_break_continue
}
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
recursive_visitor.visit_expr(expression);
recursive_visitor.seen_return_break_continue
}
pub struct UsedAfterExprVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
definition: HirId,
past_expr: bool,
used_after_expr: bool,
}
impl<'a, 'tcx> UsedAfterExprVisitor<'a, 'tcx> {
pub fn is_found(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
utils::path_to_local(expr).map_or(false, |definition| {
let mut visitor = UsedAfterExprVisitor {
cx,
expr,
definition,
past_expr: false,
used_after_expr: false,
};
utils::get_enclosing_block(cx, definition).map_or(false, |block| {
visitor.visit_block(block);
visitor.used_after_expr
})
})
}
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedAfterExprVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.used_after_expr {
return;
pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false };
let mut used_after_expr = false;
let mut past_expr = false;
expr_visitor(cx, |expr| {
if used_after_expr {
return false;
}
if expr.hir_id == self.expr.hir_id {
self.past_expr = true;
} else if self.past_expr && utils::path_to_local_id(expr, self.definition) {
self.used_after_expr = true;
} else {
intravisit::walk_expr(self, expr);
if expr.hir_id == after.hir_id {
past_expr = true;
} else if past_expr && utils::path_to_local_id(expr, local_id) {
used_after_expr = true;
}
}
!used_after_expr
})
.visit_block(block);
used_after_expr
}

View File

@@ -1,38 +1,70 @@
use crate::path_to_local_id;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{self, walk_block, walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, Unsafety,
};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use std::ops::ControlFlow;
use rustc_middle::ty;
/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
/// bodies (i.e. closures) are visited.
/// If the callback returns `true`, the expr just provided to the callback is walked.
#[must_use]
pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
struct V<'tcx, F> {
hir: Map<'tcx>,
f: F,
}
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.hir)
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if (self.f)(expr) {
walk_expr(self, expr);
}
}
}
V { hir: cx.tcx.hir(), f }
}
/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
/// bodies (i.e. closures) are not visited.
/// If the callback returns `true`, the expr just provided to the callback is walked.
#[must_use]
pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
struct V<F>(F);
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
type Map = intravisit::ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if (self.0)(e) {
walk_expr(self, e);
}
}
}
V(f)
}
/// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool {
struct TryFinder {
found: bool,
}
impl<'hir> intravisit::Visitor<'hir> for TryFinder {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
let mut found = false;
expr_visitor_no_bodies(|e| {
if !found {
found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
}
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
if self.found {
return;
}
match expr.kind {
hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
_ => intravisit::walk_expr(self, expr),
}
}
}
let mut visitor = TryFinder { found: false };
visitor.visit_expr(expr);
visitor.found
!found
})
.visit_expr(expr);
found
}
pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
@@ -165,103 +197,186 @@ visitable_ref!(Stmt, visit_stmt);
// }
// }
/// Calls the given function for each break expression.
pub fn visit_break_exprs<'tcx>(
node: impl Visitable<'tcx>,
f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
) {
struct V<F>(F);
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V<F> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if let ExprKind::Break(dest, sub_expr) = e.kind {
self.0(e, dest, sub_expr);
}
walk_expr(self, e);
}
}
node.visit(&mut V(f));
}
/// Checks if the given resolved path is used in the given body.
pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
let mut found = false;
expr_visitor(cx, |e| {
if found {
return false;
}
if let ExprKind::Path(p) = &e.kind {
if cx.qpath_res(p, e.hir_id) == res {
found = true;
}
}
!found
})
.visit_expr(&cx.tcx.hir().body(body).value);
found
}
/// Checks if the given local is used.
pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
let mut is_used = false;
let mut visitor = expr_visitor(cx, |expr| {
if !is_used {
is_used = path_to_local_id(expr, id);
}
!is_used
});
visitable.visit(&mut visitor);
drop(visitor);
is_used
}
/// Checks if the given expression is a constant.
pub fn is_const_evaluatable(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
struct V<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
res: Res,
found: bool,
is_const: bool,
}
impl Visitor<'tcx> for V<'_, 'tcx> {
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.found {
if !self.is_const {
return;
}
match e.kind {
ExprKind::ConstBlock(_) => return,
ExprKind::Call(
&Expr {
kind: ExprKind::Path(ref p),
hir_id,
..
},
_,
) if self
.cx
.qpath_res(p, hir_id)
.opt_def_id()
.map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
ExprKind::MethodCall(..)
if self
.cx
.typeck_results()
.type_dependent_def_id(e.hir_id)
.map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
ExprKind::Binary(_, lhs, rhs)
if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
&& self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
ExprKind::Index(base, _)
if matches!(
self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
ty::Slice(_) | ty::Array(..)
) => {},
ExprKind::Path(ref p)
if matches!(
self.cx.qpath_res(p, e.hir_id),
Res::Def(
DefKind::Const
| DefKind::AssocConst
| DefKind::AnonConst
| DefKind::ConstParam
| DefKind::Ctor(..)
| DefKind::Fn
| DefKind::AssocFn,
_
) | Res::SelfCtor(_)
) => {},
if let ExprKind::Path(p) = &e.kind {
if self.cx.qpath_res(p, e.hir_id) == self.res {
self.found = true;
}
} else {
walk_expr(self, e);
ExprKind::AddrOf(..)
| ExprKind::Array(_)
| ExprKind::Block(..)
| ExprKind::Cast(..)
| ExprKind::DropTemps(_)
| ExprKind::Field(..)
| ExprKind::If(..)
| ExprKind::Let(..)
| ExprKind::Lit(_)
| ExprKind::Match(..)
| ExprKind::Repeat(..)
| ExprKind::Struct(..)
| ExprKind::Tup(_)
| ExprKind::Type(..) => (),
_ => {
self.is_const = false;
return;
},
}
walk_expr(self, e);
}
}
let mut v = V { cx, res, found: false };
v.visit_expr(&cx.tcx.hir().body(body).value);
v.found
let mut v = V { cx, is_const: true };
v.visit_expr(e);
v.is_const
}
/// Calls the given function for each usage of the given local.
pub fn for_each_local_usage<'tcx, B>(
cx: &LateContext<'tcx>,
visitable: impl Visitable<'tcx>,
id: HirId,
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
) -> ControlFlow<B> {
struct V<'tcx, B, F> {
map: Map<'tcx>,
id: HirId,
f: F,
res: ControlFlow<B>,
/// Checks if the given expression performs an unsafe operation outside of an unsafe block.
pub fn is_expr_unsafe(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
struct V<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
is_unsafe: bool,
}
impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.map)
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.res.is_continue() {
if path_to_local_id(e, self.id) {
self.res = (self.f)(e);
} else {
walk_expr(self, e);
}
if self.is_unsafe {
return;
}
match e.kind {
ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => {
self.is_unsafe = true;
},
ExprKind::MethodCall(..)
if self
.cx
.typeck_results()
.type_dependent_def_id(e.hir_id)
.map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) =>
{
self.is_unsafe = true;
},
ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
_ => walk_expr(self, e),
},
ExprKind::Path(ref p)
if self
.cx
.qpath_res(p, e.hir_id)
.opt_def_id()
.map_or(false, |id| self.cx.tcx.is_mutable_static(id)) =>
{
self.is_unsafe = true;
},
_ => walk_expr(self, e),
}
}
fn visit_block(&mut self, b: &'tcx Block<'_>) {
if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
walk_block(self, b);
}
}
fn visit_nested_item(&mut self, id: ItemId) {
if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind {
self.is_unsafe = i.unsafety == Unsafety::Unsafe;
}
}
}
let mut v = V {
map: cx.tcx.hir(),
id,
f,
res: ControlFlow::CONTINUE,
};
visitable.visit(&mut v);
v.res
}
/// Checks if the given local is used.
pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
let mut v = V { cx, is_unsafe: false };
v.visit_expr(e);
v.is_unsafe
}