Merge commit 'a5d597637dcb78dc73f93561ce474f23d4177c35' into clippyup
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user