Merge commit '928e72dd10749875cbd412f74bfbfd7765dbcd8a' into clippyup
This commit is contained in:
571
clippy_utils/src/ast_utils.rs
Normal file
571
clippy_utils/src/ast_utils.rs
Normal file
@@ -0,0 +1,571 @@
|
||||
//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
|
||||
//!
|
||||
//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
|
||||
|
||||
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
|
||||
|
||||
use crate::{both, over};
|
||||
use rustc_ast::ptr::P;
|
||||
use rustc_ast::{self as ast, *};
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::mem;
|
||||
|
||||
pub mod ident_iter;
|
||||
pub use ident_iter::IdentIter;
|
||||
|
||||
pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
|
||||
use BinOpKind::*;
|
||||
matches!(
|
||||
kind,
|
||||
Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
|
||||
pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
|
||||
left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
|
||||
}
|
||||
|
||||
pub fn eq_id(l: Ident, r: Ident) -> bool {
|
||||
l.name == r.name
|
||||
}
|
||||
|
||||
pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
|
||||
use PatKind::*;
|
||||
match (&l.kind, &r.kind) {
|
||||
(Paren(l), _) => eq_pat(l, r),
|
||||
(_, Paren(r)) => eq_pat(l, r),
|
||||
(Wild, Wild) | (Rest, Rest) => true,
|
||||
(Lit(l), Lit(r)) => eq_expr(l, r),
|
||||
(Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
|
||||
(Range(lf, lt, le), Range(rf, rt, re)) => {
|
||||
eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
|
||||
},
|
||||
(Box(l), Box(r))
|
||||
| (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
|
||||
| (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
|
||||
(Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
|
||||
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
|
||||
(TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)),
|
||||
(Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => {
|
||||
lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf))
|
||||
},
|
||||
(Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
|
||||
match (l, r) {
|
||||
(RangeEnd::Excluded, RangeEnd::Excluded) => true,
|
||||
(RangeEnd::Included(l), RangeEnd::Included(r)) => {
|
||||
matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool {
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& eq_id(l.ident, r.ident)
|
||||
&& eq_pat(&l.pat, &r.pat)
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
|
||||
l.position == r.position && eq_ty(&l.ty, &r.ty)
|
||||
}
|
||||
|
||||
pub fn eq_path(l: &Path, r: &Path) -> bool {
|
||||
over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
|
||||
eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
|
||||
match (l, r) {
|
||||
(GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => {
|
||||
over(&l.args, &r.args, |l, r| eq_angle_arg(l, r))
|
||||
},
|
||||
(GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
|
||||
over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
|
||||
match (l, r) {
|
||||
(AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
|
||||
(AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
|
||||
match (l, r) {
|
||||
(GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
|
||||
(GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
|
||||
(GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
|
||||
both(l, r, |l, r| eq_expr(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
|
||||
match (l, r) {
|
||||
(StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
|
||||
(StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
|
||||
use ExprKind::*;
|
||||
if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) {
|
||||
return false;
|
||||
}
|
||||
match (&l.kind, &r.kind) {
|
||||
(Paren(l), _) => eq_expr(l, r),
|
||||
(_, Paren(r)) => eq_expr(l, r),
|
||||
(Err, Err) => true,
|
||||
(Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
|
||||
(Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
|
||||
(Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
|
||||
(Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
|
||||
(MethodCall(lc, la, _), MethodCall(rc, ra, _)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
|
||||
(Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
|
||||
(Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
|
||||
(Lit(l), Lit(r)) => l.kind == r.kind,
|
||||
(Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
|
||||
(Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re),
|
||||
(If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
|
||||
(While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
|
||||
(ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
|
||||
eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
|
||||
},
|
||||
(Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
|
||||
(Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
|
||||
(TryBlock(l), TryBlock(r)) => eq_block(l, r),
|
||||
(Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
|
||||
(Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
|
||||
(Continue(ll), Continue(rl)) => eq_label(ll, rl),
|
||||
(Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
|
||||
(AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
|
||||
(Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
|
||||
(Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)),
|
||||
(Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => {
|
||||
lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb)
|
||||
},
|
||||
(Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
|
||||
(Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
|
||||
(AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
|
||||
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
(Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => {
|
||||
eq_path(lp, rp) && eq_struct_rest(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_field(l: &Field, r: &Field) -> bool {
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& eq_id(l.ident, r.ident)
|
||||
&& eq_expr(&l.expr, &r.expr)
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& eq_pat(&l.pat, &r.pat)
|
||||
&& eq_expr(&l.body, &r.body)
|
||||
&& eq_expr_opt(&l.guard, &r.guard)
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
|
||||
both(l, r, |l, r| eq_id(l.ident, r.ident))
|
||||
}
|
||||
|
||||
pub fn eq_block(l: &Block, r: &Block) -> bool {
|
||||
l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
|
||||
use StmtKind::*;
|
||||
match (&l.kind, &r.kind) {
|
||||
(Local(l), Local(r)) => {
|
||||
eq_pat(&l.pat, &r.pat)
|
||||
&& both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
|
||||
&& eq_expr_opt(&l.init, &r.init)
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
},
|
||||
(Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
|
||||
(Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
|
||||
(Empty, Empty) => true,
|
||||
(MacCall(l), MacCall(r)) => {
|
||||
l.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
|
||||
eq_id(l.ident, r.ident)
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
&& eq_vis(&l.vis, &r.vis)
|
||||
&& eq_kind(&l.kind, &r.kind)
|
||||
}
|
||||
|
||||
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
|
||||
use ItemKind::*;
|
||||
match (l, r) {
|
||||
(ExternCrate(l), ExternCrate(r)) => l == r,
|
||||
(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 FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, 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)) => {
|
||||
lu == ru
|
||||
&& match (lmk, rmk) {
|
||||
(ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => {
|
||||
linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind))
|
||||
},
|
||||
(ModKind::Unloaded, ModKind::Unloaded) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
(ForeignMod(l), ForeignMod(r)) => {
|
||||
both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
|
||||
&& over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
|
||||
},
|
||||
(TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_generics(lg, rg)
|
||||
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
|
||||
&& both(lt, rt, |l, r| eq_ty(l, r))
|
||||
},
|
||||
(Enum(le, lg), Enum(re, rg)) => {
|
||||
over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
|
||||
},
|
||||
(Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
|
||||
eq_variant_data(lv, rv) && eq_generics(lg, rg)
|
||||
},
|
||||
(Trait(box TraitKind(la, lu, lg, lb, li)), Trait(box TraitKind(ra, ru, rg, rb, ri))) => {
|
||||
la == ra
|
||||
&& matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
|
||||
&& eq_generics(lg, rg)
|
||||
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
|
||||
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
|
||||
},
|
||||
(TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
|
||||
(
|
||||
Impl(box ImplKind {
|
||||
unsafety: lu,
|
||||
polarity: lp,
|
||||
defaultness: ld,
|
||||
constness: lc,
|
||||
generics: lg,
|
||||
of_trait: lot,
|
||||
self_ty: lst,
|
||||
items: li,
|
||||
}),
|
||||
Impl(box ImplKind {
|
||||
unsafety: ru,
|
||||
polarity: rp,
|
||||
defaultness: rd,
|
||||
constness: rc,
|
||||
generics: rg,
|
||||
of_trait: rot,
|
||||
self_ty: rst,
|
||||
items: ri,
|
||||
}),
|
||||
) => {
|
||||
matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
|
||||
&& matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
|
||||
&& eq_defaultness(*ld, *rd)
|
||||
&& matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
|
||||
&& eq_generics(lg, rg)
|
||||
&& both(lot, rot, |l, r| eq_path(&l.path, &r.path))
|
||||
&& eq_ty(lst, rst)
|
||||
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
|
||||
},
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
(MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
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 FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, 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 TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_generics(lg, rg)
|
||||
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
|
||||
&& both(lt, rt, |l, r| eq_ty(l, r))
|
||||
},
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
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 FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, 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 TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_generics(lg, rg)
|
||||
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
|
||||
&& both(lt, rt, |l, r| eq_ty(l, r))
|
||||
},
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
&& eq_vis(&l.vis, &r.vis)
|
||||
&& eq_id(l.ident, r.ident)
|
||||
&& eq_variant_data(&l.data, &r.data)
|
||||
&& both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
|
||||
}
|
||||
|
||||
pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
|
||||
use VariantData::*;
|
||||
match (l, r) {
|
||||
(Unit(_), Unit(_)) => true,
|
||||
(Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
&& eq_vis(&l.vis, &r.vis)
|
||||
&& both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
|
||||
&& eq_ty(&l.ty, &r.ty)
|
||||
}
|
||||
|
||||
pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
|
||||
eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
|
||||
}
|
||||
|
||||
pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
|
||||
matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
|
||||
&& l.asyncness.is_async() == r.asyncness.is_async()
|
||||
&& matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
|
||||
&& eq_ext(&l.ext, &r.ext)
|
||||
}
|
||||
|
||||
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
|
||||
over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
|
||||
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
|
||||
eq_where_predicate(l, r)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
|
||||
use WherePredicate::*;
|
||||
match (l, r) {
|
||||
(BoundPredicate(l), BoundPredicate(r)) => {
|
||||
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
|
||||
eq_generic_param(l, r)
|
||||
}) && eq_ty(&l.bounded_ty, &r.bounded_ty)
|
||||
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
|
||||
},
|
||||
(RegionPredicate(l), RegionPredicate(r)) => {
|
||||
eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
|
||||
},
|
||||
(EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
|
||||
eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
|
||||
}
|
||||
|
||||
pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
|
||||
use UseTreeKind::*;
|
||||
match (l, r) {
|
||||
(Glob, Glob) => true,
|
||||
(Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
|
||||
(Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
|
||||
eq_expr(&l.value, &r.value)
|
||||
}
|
||||
|
||||
pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
|
||||
matches!(
|
||||
(l, r),
|
||||
(Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
|
||||
use VisibilityKind::*;
|
||||
match (&l.kind, &r.kind) {
|
||||
(Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
|
||||
(Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
|
||||
eq_fn_ret_ty(&l.output, &r.output)
|
||||
&& over(&l.inputs, &r.inputs, |l, r| {
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& eq_pat(&l.pat, &r.pat)
|
||||
&& eq_ty(&l.ty, &r.ty)
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
|
||||
match (l, r) {
|
||||
(FnRetTy::Default(_), FnRetTy::Default(_)) => true,
|
||||
(FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
|
||||
use TyKind::*;
|
||||
match (&l.kind, &r.kind) {
|
||||
(Paren(l), _) => eq_ty(l, r),
|
||||
(_, Paren(r)) => eq_ty(l, r),
|
||||
(Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
|
||||
(Slice(l), Slice(r)) => eq_ty(l, r),
|
||||
(Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
|
||||
(Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
|
||||
(Rptr(ll, l), Rptr(rl, r)) => {
|
||||
both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
|
||||
},
|
||||
(BareFn(l), BareFn(r)) => {
|
||||
l.unsafety == r.unsafety
|
||||
&& eq_ext(&l.ext, &r.ext)
|
||||
&& over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
|
||||
&& eq_fn_decl(&l.decl, &r.decl)
|
||||
},
|
||||
(Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
|
||||
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
|
||||
(TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
|
||||
(ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
|
||||
(Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
|
||||
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
|
||||
use Extern::*;
|
||||
match (l, r) {
|
||||
(None, None) | (Implicit, Implicit) => true,
|
||||
(Explicit(l), Explicit(r)) => eq_str_lit(l, r),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
|
||||
l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
|
||||
}
|
||||
|
||||
pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
|
||||
eq_path(&l.trait_ref.path, &r.trait_ref.path)
|
||||
&& over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
|
||||
eq_generic_param(l, r)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
|
||||
use GenericParamKind::*;
|
||||
l.is_placeholder == r.is_placeholder
|
||||
&& eq_id(l.ident, r.ident)
|
||||
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
|
||||
&& match (&l.kind, &r.kind) {
|
||||
(Lifetime, Lifetime) => true,
|
||||
(Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
|
||||
(
|
||||
Const {
|
||||
ty: lt,
|
||||
kw_span: _,
|
||||
default: ld,
|
||||
},
|
||||
Const {
|
||||
ty: rt,
|
||||
kw_span: _,
|
||||
default: rd,
|
||||
},
|
||||
) => eq_ty(lt, rt) && both(ld, rd, |ld, rd| eq_anon_const(ld, rd)),
|
||||
_ => false,
|
||||
}
|
||||
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
|
||||
use GenericBound::*;
|
||||
match (l, r) {
|
||||
(Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
|
||||
(Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
|
||||
use AssocTyConstraintKind::*;
|
||||
eq_id(l.ident, r.ident)
|
||||
&& match (&l.kind, &r.kind) {
|
||||
(Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
|
||||
(Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
|
||||
eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
|
||||
}
|
||||
|
||||
pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
|
||||
use AttrKind::*;
|
||||
l.style == r.style
|
||||
&& match (&l.kind, &r.kind) {
|
||||
(DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
|
||||
(Normal(l, _), Normal(r, _)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
|
||||
use MacArgs::*;
|
||||
match (l, r) {
|
||||
(Empty, Empty) => true,
|
||||
(Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
|
||||
(Eq(_, lt), Eq(_, rt)) => lt.kind == rt.kind,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
45
clippy_utils/src/ast_utils/ident_iter.rs
Normal file
45
clippy_utils/src/ast_utils/ident_iter.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use core::iter::FusedIterator;
|
||||
use rustc_ast::visit::{walk_attribute, walk_expr, Visitor};
|
||||
use rustc_ast::{Attribute, Expr};
|
||||
use rustc_span::symbol::Ident;
|
||||
|
||||
pub struct IdentIter(std::vec::IntoIter<Ident>);
|
||||
|
||||
impl Iterator for IdentIter {
|
||||
type Item = Ident;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for IdentIter {}
|
||||
|
||||
impl From<&Expr> for IdentIter {
|
||||
fn from(expr: &Expr) -> Self {
|
||||
let mut visitor = IdentCollector::default();
|
||||
|
||||
walk_expr(&mut visitor, expr);
|
||||
|
||||
IdentIter(visitor.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Attribute> for IdentIter {
|
||||
fn from(attr: &Attribute) -> Self {
|
||||
let mut visitor = IdentCollector::default();
|
||||
|
||||
walk_attribute(&mut visitor, attr);
|
||||
|
||||
IdentIter(visitor.0.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct IdentCollector(Vec<Ident>);
|
||||
|
||||
impl Visitor<'_> for IdentCollector {
|
||||
fn visit_ident(&mut self, ident: Ident) {
|
||||
self.0.push(ident);
|
||||
}
|
||||
}
|
||||
150
clippy_utils/src/attrs.rs
Normal file
150
clippy_utils/src/attrs.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use rustc_ast::ast;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::sym;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Deprecation status of attributes known by Clippy.
|
||||
#[allow(dead_code)]
|
||||
pub enum DeprecationStatus {
|
||||
/// Attribute is deprecated
|
||||
Deprecated,
|
||||
/// Attribute is deprecated and was replaced by the named attribute
|
||||
Replaced(&'static str),
|
||||
None,
|
||||
}
|
||||
|
||||
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),
|
||||
];
|
||||
|
||||
pub struct LimitStack {
|
||||
stack: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Drop for LimitStack {
|
||||
fn drop(&mut self) {
|
||||
assert_eq!(self.stack.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl LimitStack {
|
||||
#[must_use]
|
||||
pub fn new(limit: u64) -> Self {
|
||||
Self { stack: vec![limit] }
|
||||
}
|
||||
pub fn limit(&self) -> u64 {
|
||||
*self.stack.last().expect("there should always be a value in the stack")
|
||||
}
|
||||
pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
|
||||
let stack = &mut self.stack;
|
||||
parse_attrs(sess, attrs, name, |val| stack.push(val));
|
||||
}
|
||||
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
|
||||
let stack = &mut self.stack;
|
||||
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_attr<'a>(
|
||||
sess: &'a Session,
|
||||
attrs: &'a [ast::Attribute],
|
||||
name: &'static str,
|
||||
) -> impl Iterator<Item = &'a ast::Attribute> {
|
||||
attrs.iter().filter(move |attr| {
|
||||
let attr = if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
|
||||
attr
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let attr_segments = &attr.path.segments;
|
||||
if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
|
||||
BUILTIN_ATTRIBUTES
|
||||
.iter()
|
||||
.find_map(|&(builtin_name, ref deprecation_status)| {
|
||||
if attr_segments[1].ident.name.as_str() == builtin_name {
|
||||
Some(deprecation_status)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map_or_else(
|
||||
|| {
|
||||
sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
|
||||
false
|
||||
},
|
||||
|deprecation_status| {
|
||||
let mut diag =
|
||||
sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
|
||||
match *deprecation_status {
|
||||
DeprecationStatus::Deprecated => {
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::Replaced(new_name) => {
|
||||
diag.span_suggestion(
|
||||
attr_segments[1].ident.span,
|
||||
"consider using",
|
||||
new_name.to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.emit();
|
||||
false
|
||||
},
|
||||
DeprecationStatus::None => {
|
||||
diag.cancel();
|
||||
attr_segments[1].ident.name.as_str() == name
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
|
||||
for attr in get_attr(sess, attrs, name) {
|
||||
if let Some(ref value) = attr.value_str() {
|
||||
if let Ok(value) = FromStr::from_str(&value.as_str()) {
|
||||
f(value)
|
||||
} else {
|
||||
sess.span_err(attr.span, "not a number");
|
||||
}
|
||||
} else {
|
||||
sess.span_err(attr.span, "bad clippy attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
|
||||
let mut unique_attr = None;
|
||||
for attr in get_attr(sess, attrs, name) {
|
||||
match attr.style {
|
||||
ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
|
||||
ast::AttrStyle::Inner => {
|
||||
sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
|
||||
.span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
|
||||
.emit();
|
||||
},
|
||||
ast::AttrStyle::Outer => {
|
||||
sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
|
||||
},
|
||||
}
|
||||
}
|
||||
unique_attr
|
||||
}
|
||||
|
||||
/// Return true if the attributes contain any of `proc_macro`,
|
||||
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
|
||||
pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
|
||||
attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
|
||||
}
|
||||
121
clippy_utils/src/camel_case.rs
Normal file
121
clippy_utils/src/camel_case.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
/// Returns the index of the character after the first camel-case component of `s`.
|
||||
#[must_use]
|
||||
pub fn until(s: &str) -> usize {
|
||||
let mut iter = s.char_indices();
|
||||
if let Some((_, first)) = iter.next() {
|
||||
if !first.is_uppercase() {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
let mut up = true;
|
||||
let mut last_i = 0;
|
||||
for (i, c) in iter {
|
||||
if up {
|
||||
if c.is_lowercase() {
|
||||
up = false;
|
||||
} else {
|
||||
return last_i;
|
||||
}
|
||||
} else if c.is_uppercase() {
|
||||
up = true;
|
||||
last_i = i;
|
||||
} else if !c.is_lowercase() {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if up {
|
||||
last_i
|
||||
} else {
|
||||
s.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns index of the last camel-case component of `s`.
|
||||
#[must_use]
|
||||
pub fn from(s: &str) -> usize {
|
||||
let mut iter = s.char_indices().rev();
|
||||
if let Some((_, first)) = iter.next() {
|
||||
if !first.is_lowercase() {
|
||||
return s.len();
|
||||
}
|
||||
} else {
|
||||
return s.len();
|
||||
}
|
||||
let mut down = true;
|
||||
let mut last_i = s.len();
|
||||
for (i, c) in iter {
|
||||
if down {
|
||||
if c.is_uppercase() {
|
||||
down = false;
|
||||
last_i = i;
|
||||
} else if !c.is_lowercase() {
|
||||
return last_i;
|
||||
}
|
||||
} else if c.is_lowercase() {
|
||||
down = true;
|
||||
} else if c.is_uppercase() {
|
||||
last_i = i;
|
||||
} else {
|
||||
return last_i;
|
||||
}
|
||||
}
|
||||
last_i
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{from, until};
|
||||
|
||||
#[test]
|
||||
fn from_full() {
|
||||
assert_eq!(from("AbcDef"), 0);
|
||||
assert_eq!(from("Abc"), 0);
|
||||
assert_eq!(from("ABcd"), 0);
|
||||
assert_eq!(from("ABcdEf"), 0);
|
||||
assert_eq!(from("AabABcd"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_partial() {
|
||||
assert_eq!(from("abcDef"), 3);
|
||||
assert_eq!(from("aDbc"), 1);
|
||||
assert_eq!(from("aabABcd"), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_not() {
|
||||
assert_eq!(from("AbcDef_"), 7);
|
||||
assert_eq!(from("AbcDD"), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_caps() {
|
||||
assert_eq!(from("ABCD"), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn until_full() {
|
||||
assert_eq!(until("AbcDef"), 6);
|
||||
assert_eq!(until("Abc"), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn until_not() {
|
||||
assert_eq!(until("abcDef"), 0);
|
||||
assert_eq!(until("aDbc"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn until_partial() {
|
||||
assert_eq!(until("AbcDef_"), 6);
|
||||
assert_eq!(until("CallTypeC"), 8);
|
||||
assert_eq!(until("AbcDD"), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn until_caps() {
|
||||
assert_eq!(until("ABCD"), 0);
|
||||
}
|
||||
}
|
||||
36
clippy_utils/src/comparisons.rs
Normal file
36
clippy_utils/src/comparisons.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
//! Utility functions about comparison operators.
|
||||
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use rustc_hir::{BinOpKind, Expr};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
/// Represent a normalized comparison operator.
|
||||
pub enum Rel {
|
||||
/// `<`
|
||||
Lt,
|
||||
/// `<=`
|
||||
Le,
|
||||
/// `==`
|
||||
Eq,
|
||||
/// `!=`
|
||||
Ne,
|
||||
}
|
||||
|
||||
/// Put the expression in the form `lhs < rhs`, `lhs <= rhs`, `lhs == rhs` or
|
||||
/// `lhs != rhs`.
|
||||
pub fn normalize_comparison<'a>(
|
||||
op: BinOpKind,
|
||||
lhs: &'a Expr<'a>,
|
||||
rhs: &'a Expr<'a>,
|
||||
) -> Option<(Rel, &'a Expr<'a>, &'a Expr<'a>)> {
|
||||
match op {
|
||||
BinOpKind::Lt => Some((Rel::Lt, lhs, rhs)),
|
||||
BinOpKind::Le => Some((Rel::Le, lhs, rhs)),
|
||||
BinOpKind::Gt => Some((Rel::Lt, rhs, lhs)),
|
||||
BinOpKind::Ge => Some((Rel::Le, rhs, lhs)),
|
||||
BinOpKind::Eq => Some((Rel::Eq, rhs, lhs)),
|
||||
BinOpKind::Ne => Some((Rel::Ne, rhs, lhs)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
574
clippy_utils/src/consts.rs
Normal file
574
clippy_utils/src/consts.rs
Normal file
@@ -0,0 +1,574 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
use crate::{clip, sext, unsext};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, LitFloatType, LitKind};
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::interpret::Scalar;
|
||||
use rustc_middle::ty::subst::{Subst, SubstsRef};
|
||||
use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_span::symbol::Symbol;
|
||||
use std::cmp::Ordering::{self, Equal};
|
||||
use std::convert::TryInto;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// A `LitKind`-like enum to fold constant `Expr`s into.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Constant {
|
||||
/// A `String` (e.g., "abc").
|
||||
Str(String),
|
||||
/// A binary string (e.g., `b"abc"`).
|
||||
Binary(Lrc<[u8]>),
|
||||
/// A single `char` (e.g., `'a'`).
|
||||
Char(char),
|
||||
/// An integer's bit representation.
|
||||
Int(u128),
|
||||
/// An `f32`.
|
||||
F32(f32),
|
||||
/// An `f64`.
|
||||
F64(f64),
|
||||
/// `true` or `false`.
|
||||
Bool(bool),
|
||||
/// An array of constants.
|
||||
Vec(Vec<Constant>),
|
||||
/// Also an array, but with only one constant, repeated N times.
|
||||
Repeat(Box<Constant>, u64),
|
||||
/// A tuple of constants.
|
||||
Tuple(Vec<Constant>),
|
||||
/// A raw pointer.
|
||||
RawPtr(u128),
|
||||
/// A reference
|
||||
Ref(Box<Constant>),
|
||||
/// A literal with syntax error.
|
||||
Err(Symbol),
|
||||
}
|
||||
|
||||
impl PartialEq for Constant {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs,
|
||||
(&Self::Binary(ref l), &Self::Binary(ref r)) => l == r,
|
||||
(&Self::Char(l), &Self::Char(r)) => l == r,
|
||||
(&Self::Int(l), &Self::Int(r)) => l == r,
|
||||
(&Self::F64(l), &Self::F64(r)) => {
|
||||
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
|
||||
// `Fw32 == Fw64`, so don’t compare them.
|
||||
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
|
||||
l.to_bits() == r.to_bits()
|
||||
},
|
||||
(&Self::F32(l), &Self::F32(r)) => {
|
||||
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
|
||||
// `Fw32 == Fw64`, so don’t compare them.
|
||||
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
|
||||
f64::from(l).to_bits() == f64::from(r).to_bits()
|
||||
},
|
||||
(&Self::Bool(l), &Self::Bool(r)) => l == r,
|
||||
(&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r,
|
||||
(&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv,
|
||||
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb,
|
||||
// TODO: are there inter-type equalities?
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Constant {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
std::mem::discriminant(self).hash(state);
|
||||
match *self {
|
||||
Self::Str(ref s) => {
|
||||
s.hash(state);
|
||||
},
|
||||
Self::Binary(ref b) => {
|
||||
b.hash(state);
|
||||
},
|
||||
Self::Char(c) => {
|
||||
c.hash(state);
|
||||
},
|
||||
Self::Int(i) => {
|
||||
i.hash(state);
|
||||
},
|
||||
Self::F32(f) => {
|
||||
f64::from(f).to_bits().hash(state);
|
||||
},
|
||||
Self::F64(f) => {
|
||||
f.to_bits().hash(state);
|
||||
},
|
||||
Self::Bool(b) => {
|
||||
b.hash(state);
|
||||
},
|
||||
Self::Vec(ref v) | Self::Tuple(ref v) => {
|
||||
v.hash(state);
|
||||
},
|
||||
Self::Repeat(ref c, l) => {
|
||||
c.hash(state);
|
||||
l.hash(state);
|
||||
},
|
||||
Self::RawPtr(u) => {
|
||||
u.hash(state);
|
||||
},
|
||||
Self::Ref(ref r) => {
|
||||
r.hash(state);
|
||||
},
|
||||
Self::Err(ref s) => {
|
||||
s.hash(state);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Constant {
|
||||
pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option<Ordering> {
|
||||
match (left, right) {
|
||||
(&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)),
|
||||
(&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)),
|
||||
(&Self::Int(l), &Self::Int(r)) => {
|
||||
if let ty::Int(int_ty) = *cmp_type.kind() {
|
||||
Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty)))
|
||||
} else {
|
||||
Some(l.cmp(&r))
|
||||
}
|
||||
},
|
||||
(&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r),
|
||||
(&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r),
|
||||
(&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)),
|
||||
(&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l
|
||||
.iter()
|
||||
.zip(r.iter())
|
||||
.map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri))
|
||||
.find(|r| r.map_or(true, |o| o != Ordering::Equal))
|
||||
.unwrap_or_else(|| Some(l.len().cmp(&r.len()))),
|
||||
(&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => {
|
||||
match Self::partial_cmp(tcx, cmp_type, lv, rv) {
|
||||
Some(Equal) => Some(ls.cmp(rs)),
|
||||
x => x,
|
||||
}
|
||||
},
|
||||
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb),
|
||||
// TODO: are there any useful inter-type orderings?
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a `LitKind` to a `Constant`.
|
||||
pub fn lit_to_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
|
||||
match *lit {
|
||||
LitKind::Str(ref is, _) => Constant::Str(is.to_string()),
|
||||
LitKind::Byte(b) => Constant::Int(u128::from(b)),
|
||||
LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)),
|
||||
LitKind::Char(c) => Constant::Char(c),
|
||||
LitKind::Int(n, _) => Constant::Int(n),
|
||||
LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty {
|
||||
ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()),
|
||||
ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()),
|
||||
},
|
||||
LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() {
|
||||
ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()),
|
||||
ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()),
|
||||
_ => bug!(),
|
||||
},
|
||||
LitKind::Bool(b) => Constant::Bool(b),
|
||||
LitKind::Err(s) => Constant::Err(s),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constant<'tcx>(
|
||||
lcx: &LateContext<'tcx>,
|
||||
typeck_results: &ty::TypeckResults<'tcx>,
|
||||
e: &Expr<'_>,
|
||||
) -> Option<(Constant, bool)> {
|
||||
let mut cx = ConstEvalLateContext {
|
||||
lcx,
|
||||
typeck_results,
|
||||
param_env: lcx.param_env,
|
||||
needed_resolution: false,
|
||||
substs: lcx.tcx.intern_substs(&[]),
|
||||
};
|
||||
cx.expr(e).map(|cst| (cst, cx.needed_resolution))
|
||||
}
|
||||
|
||||
pub fn constant_simple<'tcx>(
|
||||
lcx: &LateContext<'tcx>,
|
||||
typeck_results: &ty::TypeckResults<'tcx>,
|
||||
e: &Expr<'_>,
|
||||
) -> Option<Constant> {
|
||||
constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) })
|
||||
}
|
||||
|
||||
/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`.
|
||||
pub fn constant_context<'a, 'tcx>(
|
||||
lcx: &'a LateContext<'tcx>,
|
||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
) -> ConstEvalLateContext<'a, 'tcx> {
|
||||
ConstEvalLateContext {
|
||||
lcx,
|
||||
typeck_results,
|
||||
param_env: lcx.param_env,
|
||||
needed_resolution: false,
|
||||
substs: lcx.tcx.intern_substs(&[]),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstEvalLateContext<'a, 'tcx> {
|
||||
lcx: &'a LateContext<'tcx>,
|
||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
needed_resolution: bool,
|
||||
substs: SubstsRef<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
|
||||
/// Simple constant folding: Insert an expression, get a constant or none.
|
||||
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant> {
|
||||
match e.kind {
|
||||
ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)),
|
||||
ExprKind::Block(ref block, _) => self.block(block),
|
||||
ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.typeck_results.expr_ty_opt(e))),
|
||||
ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec),
|
||||
ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple),
|
||||
ExprKind::Repeat(ref value, _) => {
|
||||
let n = match self.typeck_results.expr_ty(e).kind() {
|
||||
ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?,
|
||||
_ => span_bug!(e.span, "typeck error"),
|
||||
};
|
||||
self.expr(value).map(|v| Constant::Repeat(Box::new(v), n))
|
||||
},
|
||||
ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op {
|
||||
UnOp::Not => self.constant_not(&o, self.typeck_results.expr_ty(e)),
|
||||
UnOp::Neg => self.constant_negate(&o, self.typeck_results.expr_ty(e)),
|
||||
UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }),
|
||||
}),
|
||||
ExprKind::If(ref cond, ref then, ref otherwise) => self.ifthenelse(cond, then, *otherwise),
|
||||
ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right),
|
||||
ExprKind::Call(ref callee, ref args) => {
|
||||
// We only handle a few const functions for now.
|
||||
if_chain! {
|
||||
if args.is_empty();
|
||||
if let ExprKind::Path(qpath) = &callee.kind;
|
||||
let res = self.typeck_results.qpath_res(qpath, callee.hir_id);
|
||||
if let Some(def_id) = res.opt_def_id();
|
||||
let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect();
|
||||
let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect();
|
||||
if let ["core", "num", int_impl, "max_value"] = *def_path;
|
||||
then {
|
||||
let value = match int_impl {
|
||||
"<impl i8>" => i8::MAX as u128,
|
||||
"<impl i16>" => i16::MAX as u128,
|
||||
"<impl i32>" => i32::MAX as u128,
|
||||
"<impl i64>" => i64::MAX as u128,
|
||||
"<impl i128>" => i128::MAX as u128,
|
||||
_ => return None,
|
||||
};
|
||||
Some(Constant::Int(value))
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
ExprKind::Index(ref arr, ref index) => self.index(arr, index),
|
||||
ExprKind::AddrOf(_, _, ref inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
|
||||
// TODO: add other expressions.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
|
||||
use self::Constant::{Bool, Int};
|
||||
match *o {
|
||||
Bool(b) => Some(Bool(!b)),
|
||||
Int(value) => {
|
||||
let value = !value;
|
||||
match *ty.kind() {
|
||||
ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))),
|
||||
ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
|
||||
use self::Constant::{Int, F32, F64};
|
||||
match *o {
|
||||
Int(value) => {
|
||||
let ity = match *ty.kind() {
|
||||
ty::Int(ity) => ity,
|
||||
_ => return None,
|
||||
};
|
||||
// sign extend
|
||||
let value = sext(self.lcx.tcx, value, ity);
|
||||
let value = value.checked_neg()?;
|
||||
// clear unused bits
|
||||
Some(Int(unsext(self.lcx.tcx, value, ity)))
|
||||
},
|
||||
F32(f) => Some(F32(-f)),
|
||||
F64(f) => Some(F64(-f)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create `Some(Vec![..])` of all constants, unless there is any
|
||||
/// non-constant part.
|
||||
fn multi(&mut self, vec: &[Expr<'_>]) -> Option<Vec<Constant>> {
|
||||
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
|
||||
}
|
||||
|
||||
/// Lookup a possibly constant expression from a `ExprKind::Path`.
|
||||
fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant> {
|
||||
let res = self.typeck_results.qpath_res(qpath, id);
|
||||
match res {
|
||||
Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
|
||||
let substs = self.typeck_results.node_substs(id);
|
||||
let substs = if self.substs.is_empty() {
|
||||
substs
|
||||
} else {
|
||||
substs.subst(self.lcx.tcx, self.substs)
|
||||
};
|
||||
|
||||
let result = self
|
||||
.lcx
|
||||
.tcx
|
||||
.const_eval_resolve(
|
||||
self.param_env,
|
||||
ty::WithOptConstParam::unknown(def_id),
|
||||
substs,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.ok()
|
||||
.map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?;
|
||||
let result = miri_to_const(&result);
|
||||
if result.is_some() {
|
||||
self.needed_resolution = true;
|
||||
}
|
||||
result
|
||||
},
|
||||
// FIXME: cover all usable cases.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant> {
|
||||
let lhs = self.expr(lhs);
|
||||
let index = self.expr(index);
|
||||
|
||||
match (lhs, index) {
|
||||
(Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) {
|
||||
Some(Constant::F32(x)) => Some(Constant::F32(*x)),
|
||||
Some(Constant::F64(x)) => Some(Constant::F64(*x)),
|
||||
_ => None,
|
||||
},
|
||||
(Some(Constant::Vec(vec)), _) => {
|
||||
if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) {
|
||||
match vec.get(0) {
|
||||
Some(Constant::F32(x)) => Some(Constant::F32(*x)),
|
||||
Some(Constant::F64(x)) => Some(Constant::F64(*x)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A block can only yield a constant if it only has one constant expression.
|
||||
fn block(&mut self, block: &Block<'_>) -> Option<Constant> {
|
||||
if block.stmts.is_empty() {
|
||||
block.expr.as_ref().and_then(|b| self.expr(b))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option<Constant> {
|
||||
if let Some(Constant::Bool(b)) = self.expr(cond) {
|
||||
if b {
|
||||
self.expr(&*then)
|
||||
} else {
|
||||
otherwise.as_ref().and_then(|expr| self.expr(expr))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option<Constant> {
|
||||
let l = self.expr(left)?;
|
||||
let r = self.expr(right);
|
||||
match (l, r) {
|
||||
(Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() {
|
||||
ty::Int(ity) => {
|
||||
let l = sext(self.lcx.tcx, l, ity);
|
||||
let r = sext(self.lcx.tcx, r, ity);
|
||||
let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity));
|
||||
match op.node {
|
||||
BinOpKind::Add => l.checked_add(r).map(zext),
|
||||
BinOpKind::Sub => l.checked_sub(r).map(zext),
|
||||
BinOpKind::Mul => l.checked_mul(r).map(zext),
|
||||
BinOpKind::Div if r != 0 => l.checked_div(r).map(zext),
|
||||
BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext),
|
||||
BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext),
|
||||
BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext),
|
||||
BinOpKind::BitXor => Some(zext(l ^ r)),
|
||||
BinOpKind::BitOr => Some(zext(l | r)),
|
||||
BinOpKind::BitAnd => Some(zext(l & r)),
|
||||
BinOpKind::Eq => Some(Constant::Bool(l == r)),
|
||||
BinOpKind::Ne => Some(Constant::Bool(l != r)),
|
||||
BinOpKind::Lt => Some(Constant::Bool(l < r)),
|
||||
BinOpKind::Le => Some(Constant::Bool(l <= r)),
|
||||
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
|
||||
BinOpKind::Gt => Some(Constant::Bool(l > r)),
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
ty::Uint(_) => match op.node {
|
||||
BinOpKind::Add => l.checked_add(r).map(Constant::Int),
|
||||
BinOpKind::Sub => l.checked_sub(r).map(Constant::Int),
|
||||
BinOpKind::Mul => l.checked_mul(r).map(Constant::Int),
|
||||
BinOpKind::Div => l.checked_div(r).map(Constant::Int),
|
||||
BinOpKind::Rem => l.checked_rem(r).map(Constant::Int),
|
||||
BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int),
|
||||
BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int),
|
||||
BinOpKind::BitXor => Some(Constant::Int(l ^ r)),
|
||||
BinOpKind::BitOr => Some(Constant::Int(l | r)),
|
||||
BinOpKind::BitAnd => Some(Constant::Int(l & r)),
|
||||
BinOpKind::Eq => Some(Constant::Bool(l == r)),
|
||||
BinOpKind::Ne => Some(Constant::Bool(l != r)),
|
||||
BinOpKind::Lt => Some(Constant::Bool(l < r)),
|
||||
BinOpKind::Le => Some(Constant::Bool(l <= r)),
|
||||
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
|
||||
BinOpKind::Gt => Some(Constant::Bool(l > r)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
(Constant::F32(l), Some(Constant::F32(r))) => match op.node {
|
||||
BinOpKind::Add => Some(Constant::F32(l + r)),
|
||||
BinOpKind::Sub => Some(Constant::F32(l - r)),
|
||||
BinOpKind::Mul => Some(Constant::F32(l * r)),
|
||||
BinOpKind::Div => Some(Constant::F32(l / r)),
|
||||
BinOpKind::Rem => Some(Constant::F32(l % r)),
|
||||
BinOpKind::Eq => Some(Constant::Bool(l == r)),
|
||||
BinOpKind::Ne => Some(Constant::Bool(l != r)),
|
||||
BinOpKind::Lt => Some(Constant::Bool(l < r)),
|
||||
BinOpKind::Le => Some(Constant::Bool(l <= r)),
|
||||
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
|
||||
BinOpKind::Gt => Some(Constant::Bool(l > r)),
|
||||
_ => None,
|
||||
},
|
||||
(Constant::F64(l), Some(Constant::F64(r))) => match op.node {
|
||||
BinOpKind::Add => Some(Constant::F64(l + r)),
|
||||
BinOpKind::Sub => Some(Constant::F64(l - r)),
|
||||
BinOpKind::Mul => Some(Constant::F64(l * r)),
|
||||
BinOpKind::Div => Some(Constant::F64(l / r)),
|
||||
BinOpKind::Rem => Some(Constant::F64(l % r)),
|
||||
BinOpKind::Eq => Some(Constant::Bool(l == r)),
|
||||
BinOpKind::Ne => Some(Constant::Bool(l != r)),
|
||||
BinOpKind::Lt => Some(Constant::Bool(l < r)),
|
||||
BinOpKind::Le => Some(Constant::Bool(l <= r)),
|
||||
BinOpKind::Ge => Some(Constant::Bool(l >= r)),
|
||||
BinOpKind::Gt => Some(Constant::Bool(l > r)),
|
||||
_ => None,
|
||||
},
|
||||
(l, r) => match (op.node, l, r) {
|
||||
(BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)),
|
||||
(BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)),
|
||||
(BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => {
|
||||
Some(r)
|
||||
},
|
||||
(BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)),
|
||||
(BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)),
|
||||
(BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn miri_to_const(result: &ty::Const<'_>) -> Option<Constant> {
|
||||
use rustc_middle::mir::interpret::ConstValue;
|
||||
match result.val {
|
||||
ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => {
|
||||
match result.ty.kind() {
|
||||
ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)),
|
||||
ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))),
|
||||
ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits(
|
||||
int.try_into().expect("invalid f32 bit representation"),
|
||||
))),
|
||||
ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits(
|
||||
int.try_into().expect("invalid f64 bit representation"),
|
||||
))),
|
||||
ty::RawPtr(type_and_mut) => {
|
||||
if let ty::Uint(_) = type_and_mut.ty.kind() {
|
||||
return Some(Constant::RawPtr(int.assert_bits(int.size())));
|
||||
}
|
||||
None
|
||||
},
|
||||
// FIXME: implement other conversions.
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() {
|
||||
ty::Ref(_, tam, _) => match tam.kind() {
|
||||
ty::Str => String::from_utf8(
|
||||
data.inspect_with_uninit_and_ptr_outside_interpreter(start..end)
|
||||
.to_owned(),
|
||||
)
|
||||
.ok()
|
||||
.map(Constant::Str),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() {
|
||||
ty::Array(sub_type, len) => match sub_type.kind() {
|
||||
ty::Float(FloatTy::F32) => match miri_to_const(len) {
|
||||
Some(Constant::Int(len)) => alloc
|
||||
.inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
|
||||
.to_owned()
|
||||
.chunks(4)
|
||||
.map(|chunk| {
|
||||
Some(Constant::F32(f32::from_le_bytes(
|
||||
chunk.try_into().expect("this shouldn't happen"),
|
||||
)))
|
||||
})
|
||||
.collect::<Option<Vec<Constant>>>()
|
||||
.map(Constant::Vec),
|
||||
_ => None,
|
||||
},
|
||||
ty::Float(FloatTy::F64) => match miri_to_const(len) {
|
||||
Some(Constant::Int(len)) => alloc
|
||||
.inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
|
||||
.to_owned()
|
||||
.chunks(8)
|
||||
.map(|chunk| {
|
||||
Some(Constant::F64(f64::from_le_bytes(
|
||||
chunk.try_into().expect("this shouldn't happen"),
|
||||
)))
|
||||
})
|
||||
.collect::<Option<Vec<Constant>>>()
|
||||
.map(Constant::Vec),
|
||||
_ => None,
|
||||
},
|
||||
// FIXME: implement other array type conversions.
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
// FIXME: implement other conversions.
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
226
clippy_utils/src/diagnostics.rs
Normal file
226
clippy_utils/src/diagnostics.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
//! Clippy wrappers around rustc's diagnostic functions.
|
||||
|
||||
use rustc_errors::{Applicability, DiagnosticBuilder};
|
||||
use rustc_hir::HirId;
|
||||
use rustc_lint::{LateContext, Lint, LintContext};
|
||||
use rustc_span::source_map::{MultiSpan, Span};
|
||||
use std::env;
|
||||
|
||||
fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
|
||||
if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
|
||||
diag.help(&format!(
|
||||
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
|
||||
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
|
||||
// extract just major + minor version and ignore patch versions
|
||||
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
|
||||
}),
|
||||
lint.name_lower().replacen("clippy::", "", 1)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a basic lint message with a `msg` and a `span`.
|
||||
///
|
||||
/// This is the most primitive of our lint emission methods and can
|
||||
/// be a good way to get a new lint started.
|
||||
///
|
||||
/// Usually it's nicer to provide more context for lint messages.
|
||||
/// Be sure the output is understandable when you use this method.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// error: usage of mem::forget on Drop type
|
||||
/// --> $DIR/mem_forget.rs:17:5
|
||||
/// |
|
||||
/// 17 | std::mem::forget(seven);
|
||||
/// | ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
|
||||
cx.struct_span_lint(lint, sp, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
docs_link(&mut diag, lint);
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
|
||||
/// Same as `span_lint` but with an extra `help` message.
|
||||
///
|
||||
/// Use this if you want to provide some general help but
|
||||
/// can't provide a specific machine applicable suggestion.
|
||||
///
|
||||
/// The `help` message can be optionally attached to a `Span`.
|
||||
///
|
||||
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// error: constant division of 0.0 with 0.0 will always result in NaN
|
||||
/// --> $DIR/zero_div_zero.rs:6:25
|
||||
/// |
|
||||
/// 6 | let other_f64_nan = 0.0f64 / 0.0;
|
||||
/// | ^^^^^^^^^^^^
|
||||
/// |
|
||||
/// = help: Consider using `f64::NAN` if you would like a constant representing NaN
|
||||
/// ```
|
||||
pub fn span_lint_and_help<'a, T: LintContext>(
|
||||
cx: &'a T,
|
||||
lint: &'static Lint,
|
||||
span: Span,
|
||||
msg: &str,
|
||||
help_span: Option<Span>,
|
||||
help: &str,
|
||||
) {
|
||||
cx.struct_span_lint(lint, span, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
if let Some(help_span) = help_span {
|
||||
diag.span_help(help_span, help);
|
||||
} else {
|
||||
diag.help(help);
|
||||
}
|
||||
docs_link(&mut diag, lint);
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
|
||||
/// Like `span_lint` but with a `note` section instead of a `help` message.
|
||||
///
|
||||
/// The `note` message is presented separately from the main lint message
|
||||
/// and is attached to a specific span:
|
||||
///
|
||||
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
|
||||
/// --> $DIR/drop_forget_ref.rs:10:5
|
||||
/// |
|
||||
/// 10 | forget(&SomeStruct);
|
||||
/// | ^^^^^^^^^^^^^^^^^^^
|
||||
/// |
|
||||
/// = note: `-D clippy::forget-ref` implied by `-D warnings`
|
||||
/// note: argument has type &SomeStruct
|
||||
/// --> $DIR/drop_forget_ref.rs:10:12
|
||||
/// |
|
||||
/// 10 | forget(&SomeStruct);
|
||||
/// | ^^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn span_lint_and_note<'a, T: LintContext>(
|
||||
cx: &'a T,
|
||||
lint: &'static Lint,
|
||||
span: impl Into<MultiSpan>,
|
||||
msg: &str,
|
||||
note_span: Option<Span>,
|
||||
note: &str,
|
||||
) {
|
||||
cx.struct_span_lint(lint, span, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
if let Some(note_span) = note_span {
|
||||
diag.span_note(note_span, note);
|
||||
} else {
|
||||
diag.note(note);
|
||||
}
|
||||
docs_link(&mut diag, lint);
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
|
||||
/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
|
||||
///
|
||||
/// If you need to customize your lint output a lot, use this function.
|
||||
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
|
||||
pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
|
||||
where
|
||||
F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
|
||||
{
|
||||
cx.struct_span_lint(lint, sp, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
f(&mut diag);
|
||||
docs_link(&mut diag, lint);
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
|
||||
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
docs_link(&mut diag, lint);
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn span_lint_hir_and_then(
|
||||
cx: &LateContext<'_>,
|
||||
lint: &'static Lint,
|
||||
hir_id: HirId,
|
||||
sp: Span,
|
||||
msg: &str,
|
||||
f: impl FnOnce(&mut DiagnosticBuilder<'_>),
|
||||
) {
|
||||
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
|
||||
let mut diag = diag.build(msg);
|
||||
f(&mut diag);
|
||||
docs_link(&mut diag, lint);
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
|
||||
/// Add a span lint with a suggestion on how to fix it.
|
||||
///
|
||||
/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
|
||||
/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
|
||||
/// 2)"`.
|
||||
///
|
||||
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// error: This `.fold` can be more succinctly expressed as `.any`
|
||||
/// --> $DIR/methods.rs:390:13
|
||||
/// |
|
||||
/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
|
||||
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
|
||||
/// |
|
||||
/// = note: `-D fold-any` implied by `-D warnings`
|
||||
/// ```
|
||||
#[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))]
|
||||
pub fn span_lint_and_sugg<'a, T: LintContext>(
|
||||
cx: &'a T,
|
||||
lint: &'static Lint,
|
||||
sp: Span,
|
||||
msg: &str,
|
||||
help: &str,
|
||||
sugg: String,
|
||||
applicability: Applicability,
|
||||
) {
|
||||
span_lint_and_then(cx, lint, sp, msg, |diag| {
|
||||
diag.span_suggestion(sp, help, sugg, applicability);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a suggestion made from several `span → replacement`.
|
||||
///
|
||||
/// Note: in the JSON format (used by `compiletest_rs`), the help message will
|
||||
/// appear once per
|
||||
/// replacement. In human-readable format though, it only appears once before
|
||||
/// the whole suggestion.
|
||||
pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
|
||||
where
|
||||
I: IntoIterator<Item = (Span, String)>,
|
||||
{
|
||||
multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg)
|
||||
}
|
||||
|
||||
pub fn multispan_sugg_with_applicability<I>(
|
||||
diag: &mut DiagnosticBuilder<'_>,
|
||||
help_msg: &str,
|
||||
applicability: Applicability,
|
||||
sugg: I,
|
||||
) where
|
||||
I: IntoIterator<Item = (Span, String)>,
|
||||
{
|
||||
diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
|
||||
}
|
||||
134
clippy_utils/src/eager_or_lazy.rs
Normal file
134
clippy_utils/src/eager_or_lazy.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
|
||||
//!
|
||||
//! Things to consider:
|
||||
//! - has the expression side-effects?
|
||||
//! - is the expression computationally expensive?
|
||||
//!
|
||||
//! See lints:
|
||||
//! - unnecessary-lazy-evaluations
|
||||
//! - or-fun-call
|
||||
//! - option-if-let-else
|
||||
|
||||
use crate::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths};
|
||||
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_lint::LateContext;
|
||||
use rustc_middle::hir::map::Map;
|
||||
|
||||
/// 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(|expr| identify_some_pure_patterns(expr)),
|
||||
ExprKind::Struct(_, fields, expr) => {
|
||||
fields.iter().all(|f| identify_some_pure_patterns(f.expr))
|
||||
&& expr.map_or(true, |e| identify_some_pure_patterns(e))
|
||||
},
|
||||
ExprKind::Call(
|
||||
&Expr {
|
||||
kind:
|
||||
ExprKind::Path(QPath::Resolved(
|
||||
_,
|
||||
Path {
|
||||
res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
|
||||
..
|
||||
},
|
||||
)),
|
||||
..
|
||||
},
|
||||
args,
|
||||
) => args.iter().all(|expr| identify_some_pure_patterns(expr)),
|
||||
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::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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
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_type))
|
||||
|| match_type(self.cx, ty, &paths::BTREEMAP)
|
||||
},
|
||||
ExprKind::MethodCall(..) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if call_found {
|
||||
self.found |= true;
|
||||
}
|
||||
|
||||
if !self.found {
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
let mut finder = FunCallFinder { cx, found: false };
|
||||
finder.visit_expr(expr);
|
||||
finder.found
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
identify_some_potentially_expensive_patterns(cx, expr)
|
||||
}
|
||||
268
clippy_utils/src/higher.rs
Normal file
268
clippy_utils/src/higher.rs
Normal file
@@ -0,0 +1,268 @@
|
||||
//! This module contains functions for retrieve the original AST from lowered
|
||||
//! `hir`.
|
||||
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::{is_expn_of, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
/// Converts a hir binary operator to the corresponding `ast` type.
|
||||
#[must_use]
|
||||
pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
|
||||
match op {
|
||||
hir::BinOpKind::Eq => ast::BinOpKind::Eq,
|
||||
hir::BinOpKind::Ge => ast::BinOpKind::Ge,
|
||||
hir::BinOpKind::Gt => ast::BinOpKind::Gt,
|
||||
hir::BinOpKind::Le => ast::BinOpKind::Le,
|
||||
hir::BinOpKind::Lt => ast::BinOpKind::Lt,
|
||||
hir::BinOpKind::Ne => ast::BinOpKind::Ne,
|
||||
hir::BinOpKind::Or => ast::BinOpKind::Or,
|
||||
hir::BinOpKind::Add => ast::BinOpKind::Add,
|
||||
hir::BinOpKind::And => ast::BinOpKind::And,
|
||||
hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
|
||||
hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
|
||||
hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
|
||||
hir::BinOpKind::Div => ast::BinOpKind::Div,
|
||||
hir::BinOpKind::Mul => ast::BinOpKind::Mul,
|
||||
hir::BinOpKind::Rem => ast::BinOpKind::Rem,
|
||||
hir::BinOpKind::Shl => ast::BinOpKind::Shl,
|
||||
hir::BinOpKind::Shr => ast::BinOpKind::Shr,
|
||||
hir::BinOpKind::Sub => ast::BinOpKind::Sub,
|
||||
}
|
||||
}
|
||||
|
||||
/// Represent a range akin to `ast::ExprKind::Range`.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Range<'a> {
|
||||
/// The lower bound of the range, or `None` for ranges such as `..X`.
|
||||
pub start: Option<&'a hir::Expr<'a>>,
|
||||
/// The upper bound of the range, or `None` for ranges such as `X..`.
|
||||
pub end: Option<&'a hir::Expr<'a>>,
|
||||
/// Whether the interval is open or closed.
|
||||
pub limits: ast::RangeLimits,
|
||||
}
|
||||
|
||||
/// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
|
||||
pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
|
||||
/// Finds the field named `name` in the field. Always return `Some` for
|
||||
/// convenience.
|
||||
fn get_field<'c>(name: &str, fields: &'c [hir::Field<'_>]) -> Option<&'c hir::Expr<'c>> {
|
||||
let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
|
||||
|
||||
Some(expr)
|
||||
}
|
||||
|
||||
match expr.kind {
|
||||
hir::ExprKind::Call(ref path, ref args)
|
||||
if matches!(
|
||||
path.kind,
|
||||
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
|
||||
) =>
|
||||
{
|
||||
Some(Range {
|
||||
start: Some(&args[0]),
|
||||
end: Some(&args[1]),
|
||||
limits: ast::RangeLimits::Closed,
|
||||
})
|
||||
},
|
||||
hir::ExprKind::Struct(ref path, ref fields, None) => match path {
|
||||
hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
|
||||
start: None,
|
||||
end: None,
|
||||
limits: ast::RangeLimits::HalfOpen,
|
||||
}),
|
||||
hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
|
||||
start: Some(get_field("start", fields)?),
|
||||
end: None,
|
||||
limits: ast::RangeLimits::HalfOpen,
|
||||
}),
|
||||
hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
|
||||
start: Some(get_field("start", fields)?),
|
||||
end: Some(get_field("end", fields)?),
|
||||
limits: ast::RangeLimits::HalfOpen,
|
||||
}),
|
||||
hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
|
||||
start: None,
|
||||
end: Some(get_field("end", fields)?),
|
||||
limits: ast::RangeLimits::Closed,
|
||||
}),
|
||||
hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
|
||||
start: None,
|
||||
end: Some(get_field("end", fields)?),
|
||||
limits: ast::RangeLimits::HalfOpen,
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a `let` statement is from a `for` loop desugaring.
|
||||
pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
|
||||
// This will detect plain for-loops without an actual variable binding:
|
||||
//
|
||||
// ```
|
||||
// for x in some_vec {
|
||||
// // do stuff
|
||||
// }
|
||||
// ```
|
||||
if_chain! {
|
||||
if let Some(ref expr) = local.init;
|
||||
if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
|
||||
then {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// This detects a variable binding in for loop to avoid `let_unit_value`
|
||||
// lint (see issue #1964).
|
||||
//
|
||||
// ```
|
||||
// for _ in vec![()] {
|
||||
// // anything
|
||||
// }
|
||||
// ```
|
||||
if let hir::LocalSource::ForLoopDesugar = local.source {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Recover the essential nodes of a desugared for loop as well as the entire span:
|
||||
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
|
||||
pub fn for_loop<'tcx>(
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
|
||||
if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
|
||||
if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
|
||||
if let hir::ExprKind::Loop(ref block, ..) = arms[0].body.kind;
|
||||
if block.expr.is_none();
|
||||
if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
|
||||
if let hir::StmtKind::Local(ref local) = let_stmt.kind;
|
||||
if let hir::StmtKind::Expr(ref expr) = body.kind;
|
||||
then {
|
||||
return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Recover the essential nodes of a desugared while loop:
|
||||
/// `while cond { body }` becomes `(cond, body)`.
|
||||
pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind;
|
||||
if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
|
||||
if let hir::ExprKind::DropTemps(cond) = &cond.kind;
|
||||
if let [hir::Arm { body, .. }, ..] = &arms[..];
|
||||
then {
|
||||
return Some((cond, body));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Represent the pre-expansion arguments of a `vec!` invocation.
|
||||
pub enum VecArgs<'a> {
|
||||
/// `vec![elem; len]`
|
||||
Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
|
||||
/// `vec![a, b, c]`
|
||||
Vec(&'a [hir::Expr<'a>]),
|
||||
}
|
||||
|
||||
/// Returns the arguments of the `vec!` macro if this expression was expanded
|
||||
/// from `vec!`.
|
||||
pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
|
||||
if let hir::ExprKind::Path(ref qpath) = fun.kind;
|
||||
if is_expn_of(fun.span, "vec").is_some();
|
||||
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
|
||||
then {
|
||||
return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
|
||||
// `vec![elem; size]` case
|
||||
Some(VecArgs::Repeat(&args[0], &args[1]))
|
||||
}
|
||||
else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
|
||||
// `vec![a, b, c]` case
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Box(ref boxed) = args[0].kind;
|
||||
if let hir::ExprKind::Array(ref args) = boxed.kind;
|
||||
then {
|
||||
return Some(VecArgs::Vec(&*args));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
|
||||
Some(VecArgs::Vec(&[]))
|
||||
}
|
||||
else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract args from an assert-like macro.
|
||||
/// Currently working with:
|
||||
/// - `assert!`, `assert_eq!` and `assert_ne!`
|
||||
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
|
||||
/// For example:
|
||||
/// `assert!(expr)` will return Some([expr])
|
||||
/// `debug_assert_eq!(a, b)` will return Some([a, b])
|
||||
pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
|
||||
/// Try to match the AST for a pattern that contains a match, for example when two args are
|
||||
/// compared
|
||||
fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
|
||||
if_chain! {
|
||||
if let ExprKind::Match(ref headerexpr, _, _) = &matchblock_expr.kind;
|
||||
if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
|
||||
then {
|
||||
return Some(vec![lhs, rhs]);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
if let ExprKind::Block(ref block, _) = e.kind {
|
||||
if block.stmts.len() == 1 {
|
||||
if let StmtKind::Semi(ref matchexpr) = block.stmts.get(0)?.kind {
|
||||
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
|
||||
if_chain! {
|
||||
if let ExprKind::If(ref clause, _, _) = matchexpr.kind;
|
||||
if let ExprKind::Unary(UnOp::Not, condition) = clause.kind;
|
||||
then {
|
||||
return Some(vec![condition]);
|
||||
}
|
||||
}
|
||||
|
||||
// debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
|
||||
if_chain! {
|
||||
if let ExprKind::Block(ref matchblock,_) = matchexpr.kind;
|
||||
if let Some(ref matchblock_expr) = matchblock.expr;
|
||||
then {
|
||||
return ast_matchblock(matchblock_expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(matchblock_expr) = block.expr {
|
||||
// macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
|
||||
return ast_matchblock(&matchblock_expr);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
852
clippy_utils/src/hir_utils.rs
Normal file
852
clippy_utils/src/hir_utils.rs
Normal file
@@ -0,0 +1,852 @@
|
||||
use crate::consts::{constant_context, constant_simple};
|
||||
use crate::differing_macro_contexts;
|
||||
use rustc_ast::ast::InlineAsmTemplatePiece;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{
|
||||
BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FieldPat, FnRetTy,
|
||||
GenericArg, GenericArgs, Guard, HirId, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
|
||||
PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ich::StableHashingContextProvider;
|
||||
use rustc_middle::ty::TypeckResults;
|
||||
use rustc_span::Symbol;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Type used to check whether two ast are the same. This is different from the
|
||||
/// operator
|
||||
/// `==` on ast types as this operator would compare true equality with ID and
|
||||
/// span.
|
||||
///
|
||||
/// Note that some expressions kinds are not considered but could be added.
|
||||
pub struct SpanlessEq<'a, 'tcx> {
|
||||
/// Context used to evaluate constant expressions.
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
|
||||
allow_side_effects: bool,
|
||||
expr_fallback: Option<Box<dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
pub fn new(cx: &'a LateContext<'tcx>) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
maybe_typeck_results: cx.maybe_typeck_results(),
|
||||
allow_side_effects: true,
|
||||
expr_fallback: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consider expressions containing potential side effects as not equal.
|
||||
pub fn deny_side_effects(self) -> Self {
|
||||
Self {
|
||||
allow_side_effects: false,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expr_fallback(self, expr_fallback: impl FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
|
||||
Self {
|
||||
expr_fallback: Some(Box::new(expr_fallback)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this method to wrap comparisons that may involve inter-expression context.
|
||||
/// See `self.locals`.
|
||||
fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
|
||||
HirEqInterExpr {
|
||||
inner: self,
|
||||
locals: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
|
||||
self.inter_expr().eq_block(left, right)
|
||||
}
|
||||
|
||||
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
self.inter_expr().eq_expr(left, right)
|
||||
}
|
||||
|
||||
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
|
||||
self.inter_expr().eq_path_segment(left, right)
|
||||
}
|
||||
|
||||
pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
|
||||
self.inter_expr().eq_path_segments(left, right)
|
||||
}
|
||||
|
||||
pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
|
||||
self.inter_expr().eq_ty_kind(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
struct HirEqInterExpr<'a, 'b, 'tcx> {
|
||||
inner: &'a mut SpanlessEq<'b, 'tcx>,
|
||||
|
||||
// When binding are declared, the binding ID in the left expression is mapped to the one on the
|
||||
// right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
|
||||
// these blocks are considered equal since `x` is mapped to `y`.
|
||||
locals: FxHashMap<HirId, HirId>,
|
||||
}
|
||||
|
||||
impl HirEqInterExpr<'_, '_, '_> {
|
||||
fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
|
||||
match (&left.kind, &right.kind) {
|
||||
(&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
|
||||
self.eq_pat(&l.pat, &r.pat)
|
||||
&& both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
|
||||
&& both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
|
||||
},
|
||||
(&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => {
|
||||
self.eq_expr(l, r)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether two blocks are the same.
|
||||
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
|
||||
over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
|
||||
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
if !self.inner.allow_side_effects && differing_macro_contexts(left.span, right.span) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(typeck_results) = self.inner.maybe_typeck_results {
|
||||
if let (Some(l), Some(r)) = (
|
||||
constant_simple(self.inner.cx, typeck_results, left),
|
||||
constant_simple(self.inner.cx, typeck_results, right),
|
||||
) {
|
||||
if l == r {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
|
||||
(&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
|
||||
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
|
||||
},
|
||||
(&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
|
||||
both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
|
||||
},
|
||||
(&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
|
||||
self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
|
||||
},
|
||||
(&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
|
||||
self.inner.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
|
||||
},
|
||||
(&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
|
||||
(&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
|
||||
l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
|
||||
|| swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
|
||||
l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
|
||||
})
|
||||
},
|
||||
(&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
|
||||
both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
|
||||
&& both(le, re, |l, r| self.eq_expr(l, r))
|
||||
},
|
||||
(&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
|
||||
(&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
|
||||
self.inner.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
|
||||
},
|
||||
(&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
|
||||
| (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
|
||||
self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
|
||||
},
|
||||
(&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => {
|
||||
l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
|
||||
},
|
||||
(&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => {
|
||||
self.eq_expr(la, ra) && self.eq_expr(li, ri)
|
||||
},
|
||||
(&ExprKind::If(ref lc, ref lt, ref le), &ExprKind::If(ref rc, ref rt, ref re)) => {
|
||||
self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
|
||||
},
|
||||
(&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
|
||||
(&ExprKind::Loop(ref lb, ref ll, ref lls, _), &ExprKind::Loop(ref rb, ref rl, ref rls, _)) => {
|
||||
lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
|
||||
},
|
||||
(&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => {
|
||||
ls == rs
|
||||
&& self.eq_expr(le, re)
|
||||
&& over(la, ra, |l, r| {
|
||||
self.eq_pat(&l.pat, &r.pat)
|
||||
&& both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
|
||||
&& self.eq_expr(&l.body, &r.body)
|
||||
})
|
||||
},
|
||||
(&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
|
||||
self.inner.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
|
||||
},
|
||||
(&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
|
||||
let mut celcx = constant_context(self.inner.cx, self.inner.cx.tcx.typeck_body(ll_id.body));
|
||||
let ll = celcx.expr(&self.inner.cx.tcx.hir().body(ll_id.body).value);
|
||||
let mut celcx = constant_context(self.inner.cx, self.inner.cx.tcx.typeck_body(rl_id.body));
|
||||
let rl = celcx.expr(&self.inner.cx.tcx.hir().body(rl_id.body).value);
|
||||
|
||||
self.eq_expr(le, re) && ll == rl
|
||||
},
|
||||
(&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
|
||||
(&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
|
||||
(&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
|
||||
self.eq_qpath(l_path, r_path)
|
||||
&& both(lo, ro, |l, r| self.eq_expr(l, r))
|
||||
&& over(lf, rf, |l, r| self.eq_field(l, r))
|
||||
},
|
||||
(&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
|
||||
(&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
|
||||
(&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
|
||||
(&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
|
||||
_ => false,
|
||||
};
|
||||
is_eq || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right))
|
||||
}
|
||||
|
||||
fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
|
||||
over(left, right, |l, r| self.eq_expr(l, r))
|
||||
}
|
||||
|
||||
fn eq_field(&mut self, left: &Field<'_>, right: &Field<'_>) -> bool {
|
||||
left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
|
||||
}
|
||||
|
||||
fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
|
||||
match (left, right) {
|
||||
(Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
|
||||
(Guard::IfLet(lp, le), Guard::IfLet(rp, re)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
|
||||
match (left, right) {
|
||||
(GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
|
||||
(GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
|
||||
left.name == right.name
|
||||
}
|
||||
|
||||
fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool {
|
||||
let (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) = (&left, &right);
|
||||
li.name == ri.name && self.eq_pat(lp, rp)
|
||||
}
|
||||
|
||||
/// Checks whether two patterns are the same.
|
||||
fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
|
||||
match (&left.kind, &right.kind) {
|
||||
(&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
|
||||
(&PatKind::Struct(ref lp, ref la, ..), &PatKind::Struct(ref rp, ref ra, ..)) => {
|
||||
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_fieldpat(l, r))
|
||||
},
|
||||
(&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
|
||||
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
|
||||
},
|
||||
(&PatKind::Binding(lb, li, _, ref lp), &PatKind::Binding(rb, ri, _, ref rp)) => {
|
||||
let eq = lb == rb && both(lp, rp, |l, r| self.eq_pat(l, r));
|
||||
if eq {
|
||||
self.locals.insert(li, ri);
|
||||
}
|
||||
eq
|
||||
},
|
||||
(&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
|
||||
(&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r),
|
||||
(&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => {
|
||||
ls == rs && over(l, r, |l, r| self.eq_pat(l, r))
|
||||
},
|
||||
(&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
|
||||
both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
|
||||
},
|
||||
(&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re),
|
||||
(&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => {
|
||||
over(ls, rs, |l, r| self.eq_pat(l, r))
|
||||
&& over(le, re, |l, r| self.eq_pat(l, r))
|
||||
&& both(li, ri, |l, r| self.eq_pat(l, r))
|
||||
},
|
||||
(&PatKind::Wild, &PatKind::Wild) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
|
||||
match (left, right) {
|
||||
(&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => {
|
||||
both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
|
||||
},
|
||||
(&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
|
||||
self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
|
||||
},
|
||||
(&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
|
||||
match (left.res, right.res) {
|
||||
(Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
|
||||
(Res::Local(_), _) | (_, Res::Local(_)) => false,
|
||||
_ => over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r)),
|
||||
}
|
||||
}
|
||||
|
||||
fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
|
||||
if !(left.parenthesized || right.parenthesized) {
|
||||
over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
|
||||
&& over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r))
|
||||
} else if left.parenthesized && right.parenthesized {
|
||||
over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
|
||||
&& both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
|
||||
self.eq_ty(l, r)
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
|
||||
left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
|
||||
}
|
||||
|
||||
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
|
||||
// The == of idents doesn't work with different contexts,
|
||||
// we have to be explicit about hygiene
|
||||
left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
|
||||
}
|
||||
|
||||
fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
|
||||
self.eq_ty_kind(&left.kind, &right.kind)
|
||||
}
|
||||
|
||||
#[allow(clippy::similar_names)]
|
||||
fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
|
||||
match (left, right) {
|
||||
(&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
|
||||
(&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
|
||||
let cx = self.inner.cx;
|
||||
let eval_const =
|
||||
|body| constant_context(cx, cx.tcx.typeck_body(body)).expr(&cx.tcx.hir().body(body).value);
|
||||
self.eq_ty(lt, rt) && eval_const(ll_id.body) == eval_const(rl_id.body)
|
||||
},
|
||||
(&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
|
||||
l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
|
||||
},
|
||||
(&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
|
||||
l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
|
||||
},
|
||||
(&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
|
||||
(&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
|
||||
(&TyKind::Infer, &TyKind::Infer) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
|
||||
left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty())
|
||||
}
|
||||
}
|
||||
|
||||
/// Some simple reductions like `{ return }` => `return`
|
||||
fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
|
||||
if let ExprKind::Block(block, _) = kind {
|
||||
match (block.stmts, block.expr) {
|
||||
// `{}` => `()`
|
||||
([], None) => &ExprKind::Tup(&[]),
|
||||
([], Some(expr)) => match expr.kind {
|
||||
// `{ return .. }` => `return ..`
|
||||
ExprKind::Ret(..) => &expr.kind,
|
||||
_ => kind,
|
||||
},
|
||||
([stmt], None) => match stmt.kind {
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
|
||||
// `{ return ..; }` => `return ..`
|
||||
ExprKind::Ret(..) => &expr.kind,
|
||||
_ => kind,
|
||||
},
|
||||
_ => kind,
|
||||
},
|
||||
_ => kind,
|
||||
}
|
||||
} else {
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_binop<'a>(
|
||||
binop: BinOpKind,
|
||||
lhs: &'a Expr<'a>,
|
||||
rhs: &'a Expr<'a>,
|
||||
) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
|
||||
match binop {
|
||||
BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
|
||||
Some((binop, rhs, lhs))
|
||||
},
|
||||
BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
|
||||
BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
|
||||
BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
|
||||
BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
|
||||
BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
|
||||
| BinOpKind::Shl
|
||||
| BinOpKind::Shr
|
||||
| BinOpKind::Rem
|
||||
| BinOpKind::Sub
|
||||
| BinOpKind::Div
|
||||
| BinOpKind::And
|
||||
| BinOpKind::Or => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the two `Option`s are both `None` or some equal values as per
|
||||
/// `eq_fn`.
|
||||
pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
|
||||
l.as_ref()
|
||||
.map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
|
||||
}
|
||||
|
||||
/// Checks if two slices are equal as per `eq_fn`.
|
||||
pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
|
||||
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
|
||||
}
|
||||
|
||||
/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
|
||||
pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
|
||||
}
|
||||
|
||||
/// Type used to hash an ast element. This is different from the `Hash` trait
|
||||
/// on ast types as this
|
||||
/// trait would consider IDs and spans.
|
||||
///
|
||||
/// All expressions kind are hashed, but some might have a weaker hash.
|
||||
pub struct SpanlessHash<'a, 'tcx> {
|
||||
/// Context used to evaluate constant expressions.
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
|
||||
s: StableHasher,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
||||
pub fn new(cx: &'a LateContext<'tcx>) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
maybe_typeck_results: cx.maybe_typeck_results(),
|
||||
s: StableHasher::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self) -> u64 {
|
||||
self.s.finish()
|
||||
}
|
||||
|
||||
pub fn hash_block(&mut self, b: &Block<'_>) {
|
||||
for s in b.stmts {
|
||||
self.hash_stmt(s);
|
||||
}
|
||||
|
||||
if let Some(ref e) = b.expr {
|
||||
self.hash_expr(e);
|
||||
}
|
||||
|
||||
match b.rules {
|
||||
BlockCheckMode::DefaultBlock => 0,
|
||||
BlockCheckMode::UnsafeBlock(_) => 1,
|
||||
BlockCheckMode::PushUnsafeBlock(_) => 2,
|
||||
BlockCheckMode::PopUnsafeBlock(_) => 3,
|
||||
}
|
||||
.hash(&mut self.s);
|
||||
}
|
||||
|
||||
#[allow(clippy::many_single_char_names, clippy::too_many_lines)]
|
||||
pub fn hash_expr(&mut self, e: &Expr<'_>) {
|
||||
let simple_const = self
|
||||
.maybe_typeck_results
|
||||
.and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
|
||||
|
||||
// const hashing may result in the same hash as some unrelated node, so add a sort of
|
||||
// discriminant depending on which path we're choosing next
|
||||
simple_const.is_some().hash(&mut self.s);
|
||||
|
||||
if let Some(e) = simple_const {
|
||||
return e.hash(&mut self.s);
|
||||
}
|
||||
|
||||
std::mem::discriminant(&e.kind).hash(&mut self.s);
|
||||
|
||||
match e.kind {
|
||||
ExprKind::AddrOf(kind, m, ref e) => {
|
||||
match kind {
|
||||
BorrowKind::Ref => 0,
|
||||
BorrowKind::Raw => 1,
|
||||
}
|
||||
.hash(&mut self.s);
|
||||
m.hash(&mut self.s);
|
||||
self.hash_expr(e);
|
||||
},
|
||||
ExprKind::Continue(i) => {
|
||||
if let Some(i) = i.label {
|
||||
self.hash_name(i.ident.name);
|
||||
}
|
||||
},
|
||||
ExprKind::Assign(ref l, ref r, _) => {
|
||||
self.hash_expr(l);
|
||||
self.hash_expr(r);
|
||||
},
|
||||
ExprKind::AssignOp(ref o, ref l, ref r) => {
|
||||
o.node
|
||||
.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
|
||||
self.hash_expr(l);
|
||||
self.hash_expr(r);
|
||||
},
|
||||
ExprKind::Block(ref b, _) => {
|
||||
self.hash_block(b);
|
||||
},
|
||||
ExprKind::Binary(op, ref l, ref r) => {
|
||||
op.node
|
||||
.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
|
||||
self.hash_expr(l);
|
||||
self.hash_expr(r);
|
||||
},
|
||||
ExprKind::Break(i, ref j) => {
|
||||
if let Some(i) = i.label {
|
||||
self.hash_name(i.ident.name);
|
||||
}
|
||||
if let Some(ref j) = *j {
|
||||
self.hash_expr(&*j);
|
||||
}
|
||||
},
|
||||
ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => {
|
||||
self.hash_expr(e);
|
||||
},
|
||||
ExprKind::Call(ref fun, args) => {
|
||||
self.hash_expr(fun);
|
||||
self.hash_exprs(args);
|
||||
},
|
||||
ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
|
||||
self.hash_expr(e);
|
||||
self.hash_ty(ty);
|
||||
},
|
||||
ExprKind::Closure(cap, _, eid, _, _) => {
|
||||
match cap {
|
||||
CaptureBy::Value => 0,
|
||||
CaptureBy::Ref => 1,
|
||||
}
|
||||
.hash(&mut self.s);
|
||||
// closures inherit TypeckResults
|
||||
self.hash_expr(&self.cx.tcx.hir().body(eid).value);
|
||||
},
|
||||
ExprKind::Field(ref e, ref f) => {
|
||||
self.hash_expr(e);
|
||||
self.hash_name(f.name);
|
||||
},
|
||||
ExprKind::Index(ref a, ref i) => {
|
||||
self.hash_expr(a);
|
||||
self.hash_expr(i);
|
||||
},
|
||||
ExprKind::InlineAsm(ref asm) => {
|
||||
for piece in asm.template {
|
||||
match piece {
|
||||
InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
|
||||
InlineAsmTemplatePiece::Placeholder {
|
||||
operand_idx,
|
||||
modifier,
|
||||
span: _,
|
||||
} => {
|
||||
operand_idx.hash(&mut self.s);
|
||||
modifier.hash(&mut self.s);
|
||||
},
|
||||
}
|
||||
}
|
||||
asm.options.hash(&mut self.s);
|
||||
for (op, _op_sp) in asm.operands {
|
||||
match op {
|
||||
InlineAsmOperand::In { reg, expr } => {
|
||||
reg.hash(&mut self.s);
|
||||
self.hash_expr(expr);
|
||||
},
|
||||
InlineAsmOperand::Out { reg, late, expr } => {
|
||||
reg.hash(&mut self.s);
|
||||
late.hash(&mut self.s);
|
||||
if let Some(expr) = expr {
|
||||
self.hash_expr(expr);
|
||||
}
|
||||
},
|
||||
InlineAsmOperand::InOut { reg, late, expr } => {
|
||||
reg.hash(&mut self.s);
|
||||
late.hash(&mut self.s);
|
||||
self.hash_expr(expr);
|
||||
},
|
||||
InlineAsmOperand::SplitInOut {
|
||||
reg,
|
||||
late,
|
||||
in_expr,
|
||||
out_expr,
|
||||
} => {
|
||||
reg.hash(&mut self.s);
|
||||
late.hash(&mut self.s);
|
||||
self.hash_expr(in_expr);
|
||||
if let Some(out_expr) = out_expr {
|
||||
self.hash_expr(out_expr);
|
||||
}
|
||||
},
|
||||
InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr),
|
||||
}
|
||||
}
|
||||
},
|
||||
ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {},
|
||||
ExprKind::Lit(ref l) => {
|
||||
l.node.hash(&mut self.s);
|
||||
},
|
||||
ExprKind::Loop(ref b, ref i, ..) => {
|
||||
self.hash_block(b);
|
||||
if let Some(i) = *i {
|
||||
self.hash_name(i.ident.name);
|
||||
}
|
||||
},
|
||||
ExprKind::If(ref cond, ref then, ref else_opt) => {
|
||||
let c: fn(_, _, _) -> _ = ExprKind::If;
|
||||
c.hash(&mut self.s);
|
||||
self.hash_expr(cond);
|
||||
self.hash_expr(&**then);
|
||||
if let Some(ref e) = *else_opt {
|
||||
self.hash_expr(e);
|
||||
}
|
||||
},
|
||||
ExprKind::Match(ref e, arms, ref s) => {
|
||||
self.hash_expr(e);
|
||||
|
||||
for arm in arms {
|
||||
// TODO: arm.pat?
|
||||
if let Some(ref e) = arm.guard {
|
||||
self.hash_guard(e);
|
||||
}
|
||||
self.hash_expr(&arm.body);
|
||||
}
|
||||
|
||||
s.hash(&mut self.s);
|
||||
},
|
||||
ExprKind::MethodCall(ref path, ref _tys, args, ref _fn_span) => {
|
||||
self.hash_name(path.ident.name);
|
||||
self.hash_exprs(args);
|
||||
},
|
||||
ExprKind::ConstBlock(ref l_id) => {
|
||||
self.hash_body(l_id.body);
|
||||
},
|
||||
ExprKind::Repeat(ref e, ref l_id) => {
|
||||
self.hash_expr(e);
|
||||
self.hash_body(l_id.body);
|
||||
},
|
||||
ExprKind::Ret(ref e) => {
|
||||
if let Some(ref e) = *e {
|
||||
self.hash_expr(e);
|
||||
}
|
||||
},
|
||||
ExprKind::Path(ref qpath) => {
|
||||
self.hash_qpath(qpath);
|
||||
},
|
||||
ExprKind::Struct(ref path, fields, ref expr) => {
|
||||
self.hash_qpath(path);
|
||||
|
||||
for f in fields {
|
||||
self.hash_name(f.ident.name);
|
||||
self.hash_expr(&f.expr);
|
||||
}
|
||||
|
||||
if let Some(ref e) = *expr {
|
||||
self.hash_expr(e);
|
||||
}
|
||||
},
|
||||
ExprKind::Tup(tup) => {
|
||||
self.hash_exprs(tup);
|
||||
},
|
||||
ExprKind::Array(v) => {
|
||||
self.hash_exprs(v);
|
||||
},
|
||||
ExprKind::Unary(lop, ref le) => {
|
||||
lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
|
||||
self.hash_expr(le);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
|
||||
for e in e {
|
||||
self.hash_expr(e);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_name(&mut self, n: Symbol) {
|
||||
n.as_str().hash(&mut self.s);
|
||||
}
|
||||
|
||||
pub fn hash_qpath(&mut self, p: &QPath<'_>) {
|
||||
match *p {
|
||||
QPath::Resolved(_, ref path) => {
|
||||
self.hash_path(path);
|
||||
},
|
||||
QPath::TypeRelative(_, ref path) => {
|
||||
self.hash_name(path.ident.name);
|
||||
},
|
||||
QPath::LangItem(lang_item, ..) => {
|
||||
lang_item.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
|
||||
},
|
||||
}
|
||||
// self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
|
||||
}
|
||||
|
||||
pub fn hash_path(&mut self, path: &Path<'_>) {
|
||||
match path.res {
|
||||
// constant hash since equality is dependant on inter-expression context
|
||||
Res::Local(_) => 1_usize.hash(&mut self.s),
|
||||
_ => {
|
||||
for seg in path.segments {
|
||||
self.hash_name(seg.ident.name);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
|
||||
std::mem::discriminant(&b.kind).hash(&mut self.s);
|
||||
|
||||
match &b.kind {
|
||||
StmtKind::Local(local) => {
|
||||
if let Some(ref init) = local.init {
|
||||
self.hash_expr(init);
|
||||
}
|
||||
},
|
||||
StmtKind::Item(..) => {},
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
|
||||
self.hash_expr(expr);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_guard(&mut self, g: &Guard<'_>) {
|
||||
match g {
|
||||
Guard::If(ref expr) | Guard::IfLet(_, ref expr) => {
|
||||
self.hash_expr(expr);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
|
||||
std::mem::discriminant(&lifetime.name).hash(&mut self.s);
|
||||
if let LifetimeName::Param(ref name) = lifetime.name {
|
||||
std::mem::discriminant(name).hash(&mut self.s);
|
||||
match name {
|
||||
ParamName::Plain(ref ident) => {
|
||||
ident.name.hash(&mut self.s);
|
||||
},
|
||||
ParamName::Fresh(ref size) => {
|
||||
size.hash(&mut self.s);
|
||||
},
|
||||
ParamName::Error => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_ty(&mut self, ty: &Ty<'_>) {
|
||||
self.hash_tykind(&ty.kind);
|
||||
}
|
||||
|
||||
pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
|
||||
std::mem::discriminant(ty).hash(&mut self.s);
|
||||
match ty {
|
||||
TyKind::Slice(ty) => {
|
||||
self.hash_ty(ty);
|
||||
},
|
||||
TyKind::Array(ty, anon_const) => {
|
||||
self.hash_ty(ty);
|
||||
self.hash_body(anon_const.body);
|
||||
},
|
||||
TyKind::Ptr(mut_ty) => {
|
||||
self.hash_ty(&mut_ty.ty);
|
||||
mut_ty.mutbl.hash(&mut self.s);
|
||||
},
|
||||
TyKind::Rptr(lifetime, mut_ty) => {
|
||||
self.hash_lifetime(lifetime);
|
||||
self.hash_ty(&mut_ty.ty);
|
||||
mut_ty.mutbl.hash(&mut self.s);
|
||||
},
|
||||
TyKind::BareFn(bfn) => {
|
||||
bfn.unsafety.hash(&mut self.s);
|
||||
bfn.abi.hash(&mut self.s);
|
||||
for arg in bfn.decl.inputs {
|
||||
self.hash_ty(&arg);
|
||||
}
|
||||
match bfn.decl.output {
|
||||
FnRetTy::DefaultReturn(_) => {
|
||||
().hash(&mut self.s);
|
||||
},
|
||||
FnRetTy::Return(ref ty) => {
|
||||
self.hash_ty(ty);
|
||||
},
|
||||
}
|
||||
bfn.decl.c_variadic.hash(&mut self.s);
|
||||
},
|
||||
TyKind::Tup(ty_list) => {
|
||||
for ty in *ty_list {
|
||||
self.hash_ty(ty);
|
||||
}
|
||||
},
|
||||
TyKind::Path(qpath) => match qpath {
|
||||
QPath::Resolved(ref maybe_ty, ref path) => {
|
||||
if let Some(ref ty) = maybe_ty {
|
||||
self.hash_ty(ty);
|
||||
}
|
||||
for segment in path.segments {
|
||||
segment.ident.name.hash(&mut self.s);
|
||||
self.hash_generic_args(segment.args().args);
|
||||
}
|
||||
},
|
||||
QPath::TypeRelative(ref ty, ref segment) => {
|
||||
self.hash_ty(ty);
|
||||
segment.ident.name.hash(&mut self.s);
|
||||
},
|
||||
QPath::LangItem(lang_item, ..) => {
|
||||
lang_item.hash(&mut self.s);
|
||||
},
|
||||
},
|
||||
TyKind::OpaqueDef(_, arg_list) => {
|
||||
self.hash_generic_args(arg_list);
|
||||
},
|
||||
TyKind::TraitObject(_, lifetime) => {
|
||||
self.hash_lifetime(lifetime);
|
||||
},
|
||||
TyKind::Typeof(anon_const) => {
|
||||
self.hash_body(anon_const.body);
|
||||
},
|
||||
TyKind::Err | TyKind::Infer | TyKind::Never => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_body(&mut self, body_id: BodyId) {
|
||||
// swap out TypeckResults when hashing a body
|
||||
let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
|
||||
self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
|
||||
self.maybe_typeck_results = old_maybe_typeck_results;
|
||||
}
|
||||
|
||||
fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
|
||||
for arg in arg_list {
|
||||
match arg {
|
||||
GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
|
||||
GenericArg::Type(ref ty) => self.hash_ty(&ty),
|
||||
GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1883
clippy_utils/src/lib.rs
Normal file
1883
clippy_utils/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
228
clippy_utils/src/numeric_literal.rs
Normal file
228
clippy_utils/src/numeric_literal.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum Radix {
|
||||
Binary,
|
||||
Octal,
|
||||
Decimal,
|
||||
Hexadecimal,
|
||||
}
|
||||
|
||||
impl Radix {
|
||||
/// Returns a reasonable digit group size for this radix.
|
||||
#[must_use]
|
||||
fn suggest_grouping(self) -> usize {
|
||||
match self {
|
||||
Self::Binary | Self::Hexadecimal => 4,
|
||||
Self::Octal | Self::Decimal => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper method to format numeric literals with digit grouping.
|
||||
/// `lit` must be a valid numeric literal without suffix.
|
||||
pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
|
||||
NumericLiteral::new(lit, type_suffix, float).format()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NumericLiteral<'a> {
|
||||
/// Which radix the literal was represented in.
|
||||
pub radix: Radix,
|
||||
/// The radix prefix, if present.
|
||||
pub prefix: Option<&'a str>,
|
||||
|
||||
/// The integer part of the number.
|
||||
pub integer: &'a str,
|
||||
/// The fraction part of the number.
|
||||
pub fraction: Option<&'a str>,
|
||||
/// The exponent separator (b'e' or b'E') including preceding underscore if present
|
||||
/// and the exponent part.
|
||||
pub exponent: Option<(&'a str, &'a str)>,
|
||||
|
||||
/// The type suffix, including preceding underscore if present.
|
||||
pub suffix: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> NumericLiteral<'a> {
|
||||
pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
|
||||
NumericLiteral::from_lit_kind(src, &lit.kind)
|
||||
}
|
||||
|
||||
pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
|
||||
if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) {
|
||||
let (unsuffixed, suffix) = split_suffix(&src, lit_kind);
|
||||
let float = matches!(lit_kind, LitKind::Float(..));
|
||||
Some(NumericLiteral::new(unsuffixed, suffix, float))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
|
||||
// Determine delimiter for radix prefix, if present, and radix.
|
||||
let radix = if lit.starts_with("0x") {
|
||||
Radix::Hexadecimal
|
||||
} else if lit.starts_with("0b") {
|
||||
Radix::Binary
|
||||
} else if lit.starts_with("0o") {
|
||||
Radix::Octal
|
||||
} else {
|
||||
Radix::Decimal
|
||||
};
|
||||
|
||||
// Grab part of the literal after prefix, if present.
|
||||
let (prefix, mut sans_prefix) = if let Radix::Decimal = radix {
|
||||
(None, lit)
|
||||
} else {
|
||||
let (p, s) = lit.split_at(2);
|
||||
(Some(p), s)
|
||||
};
|
||||
|
||||
if suffix.is_some() && sans_prefix.ends_with('_') {
|
||||
// The '_' before the suffix isn't part of the digits
|
||||
sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
|
||||
}
|
||||
|
||||
let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
|
||||
|
||||
Self {
|
||||
radix,
|
||||
prefix,
|
||||
integer,
|
||||
fraction,
|
||||
exponent,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_decimal(&self) -> bool {
|
||||
self.radix == Radix::Decimal
|
||||
}
|
||||
|
||||
pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) {
|
||||
let mut integer = digits;
|
||||
let mut fraction = None;
|
||||
let mut exponent = None;
|
||||
|
||||
if float {
|
||||
for (i, c) in digits.char_indices() {
|
||||
match c {
|
||||
'.' => {
|
||||
integer = &digits[..i];
|
||||
fraction = Some(&digits[i + 1..]);
|
||||
},
|
||||
'e' | 'E' => {
|
||||
let exp_start = if digits[..i].ends_with('_') { i - 1 } else { i };
|
||||
|
||||
if integer.len() > exp_start {
|
||||
integer = &digits[..exp_start];
|
||||
} else {
|
||||
fraction = Some(&digits[integer.len() + 1..exp_start]);
|
||||
};
|
||||
exponent = Some((&digits[exp_start..=i], &digits[i + 1..]));
|
||||
break;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(integer, fraction, exponent)
|
||||
}
|
||||
|
||||
/// Returns literal formatted in a sensible way.
|
||||
pub fn format(&self) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
if let Some(prefix) = self.prefix {
|
||||
output.push_str(prefix);
|
||||
}
|
||||
|
||||
let group_size = self.radix.suggest_grouping();
|
||||
|
||||
Self::group_digits(
|
||||
&mut output,
|
||||
self.integer,
|
||||
group_size,
|
||||
true,
|
||||
self.radix == Radix::Hexadecimal,
|
||||
);
|
||||
|
||||
if let Some(fraction) = self.fraction {
|
||||
output.push('.');
|
||||
Self::group_digits(&mut output, fraction, group_size, false, false);
|
||||
}
|
||||
|
||||
if let Some((separator, exponent)) = self.exponent {
|
||||
output.push_str(separator);
|
||||
Self::group_digits(&mut output, exponent, group_size, true, false);
|
||||
}
|
||||
|
||||
if let Some(suffix) = self.suffix {
|
||||
output.push('_');
|
||||
output.push_str(suffix);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
|
||||
debug_assert!(group_size > 0);
|
||||
|
||||
let mut digits = input.chars().filter(|&c| c != '_');
|
||||
|
||||
let first_group_size;
|
||||
|
||||
if partial_group_first {
|
||||
first_group_size = (digits.clone().count() - 1) % group_size + 1;
|
||||
if pad {
|
||||
for _ in 0..group_size - first_group_size {
|
||||
output.push('0');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
first_group_size = group_size;
|
||||
}
|
||||
|
||||
for _ in 0..first_group_size {
|
||||
if let Some(digit) = digits.next() {
|
||||
output.push(digit);
|
||||
}
|
||||
}
|
||||
|
||||
for (c, i) in digits.zip((0..group_size).cycle()) {
|
||||
if i == 0 {
|
||||
output.push('_');
|
||||
}
|
||||
output.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
|
||||
debug_assert!(lit_kind.is_numeric());
|
||||
lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
|
||||
let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
|
||||
(unsuffixed, Some(suffix))
|
||||
})
|
||||
}
|
||||
|
||||
fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
|
||||
debug_assert!(lit_kind.is_numeric());
|
||||
let suffix = match lit_kind {
|
||||
LitKind::Int(_, int_lit_kind) => match int_lit_kind {
|
||||
LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
|
||||
LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
|
||||
LitIntType::Unsuffixed => None,
|
||||
},
|
||||
LitKind::Float(_, float_lit_kind) => match float_lit_kind {
|
||||
LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
|
||||
LitFloatType::Unsuffixed => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
suffix.map(str::len)
|
||||
}
|
||||
181
clippy_utils/src/paths.rs
Normal file
181
clippy_utils/src/paths.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
//! This module contains paths to types and functions Clippy needs to know
|
||||
//! about.
|
||||
//!
|
||||
//! Whenever possible, please consider diagnostic items over hardcoded paths.
|
||||
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
|
||||
|
||||
pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
|
||||
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
|
||||
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
|
||||
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
|
||||
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
|
||||
pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
|
||||
pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
|
||||
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
|
||||
pub const BOX: [&str; 3] = ["alloc", "boxed", "Box"];
|
||||
pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
|
||||
pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
|
||||
pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
|
||||
pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
|
||||
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
|
||||
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
|
||||
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
|
||||
pub const COPY: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
|
||||
pub const COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy"];
|
||||
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
|
||||
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
|
||||
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
|
||||
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
|
||||
pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
|
||||
pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
|
||||
pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
|
||||
pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
|
||||
pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
|
||||
pub const DROP: [&str; 3] = ["core", "mem", "drop"];
|
||||
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
|
||||
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
|
||||
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
|
||||
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
|
||||
pub const FILE: [&str; 3] = ["std", "fs", "File"];
|
||||
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
|
||||
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
|
||||
pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
|
||||
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
|
||||
pub const FN: [&str; 3] = ["core", "ops", "Fn"];
|
||||
pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
|
||||
pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
|
||||
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
|
||||
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
|
||||
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
|
||||
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
|
||||
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
|
||||
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
|
||||
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
|
||||
pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
|
||||
pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
|
||||
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
|
||||
pub const INTO: [&str; 3] = ["core", "convert", "Into"];
|
||||
pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
|
||||
pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
|
||||
pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
|
||||
pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
|
||||
pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
|
||||
pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
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 LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
|
||||
pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
|
||||
pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
|
||||
pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"];
|
||||
pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
|
||||
pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
|
||||
pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
|
||||
pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
|
||||
pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
|
||||
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
|
||||
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
|
||||
pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
|
||||
pub const OPTION: [&str; 3] = ["core", "option", "Option"];
|
||||
pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
|
||||
pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
|
||||
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
|
||||
pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
|
||||
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
|
||||
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
|
||||
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_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"];
|
||||
pub const PATH: [&str; 3] = ["std", "path", "Path"];
|
||||
pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
|
||||
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
|
||||
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
|
||||
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
|
||||
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
|
||||
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
|
||||
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
|
||||
pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
|
||||
pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
|
||||
pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
|
||||
pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
|
||||
pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
|
||||
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
|
||||
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
|
||||
pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
|
||||
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
|
||||
pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"];
|
||||
pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
|
||||
pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
|
||||
pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
|
||||
pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
|
||||
pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
|
||||
pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
|
||||
pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
|
||||
pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
|
||||
pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"];
|
||||
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
|
||||
pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
|
||||
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
|
||||
pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
|
||||
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
|
||||
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
|
||||
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
|
||||
pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
|
||||
pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
|
||||
pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
|
||||
pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
|
||||
pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
|
||||
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
|
||||
pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
|
||||
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
|
||||
pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
|
||||
pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
|
||||
pub const STRING: [&str; 3] = ["alloc", "string", "String"];
|
||||
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
|
||||
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
|
||||
pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
|
||||
pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
|
||||
pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
|
||||
pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
|
||||
pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
|
||||
pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
|
||||
pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
|
||||
pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
|
||||
pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
|
||||
pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
|
||||
pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"];
|
||||
pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
|
||||
pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
|
||||
pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
|
||||
pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
|
||||
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
|
||||
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
|
||||
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
|
||||
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
|
||||
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
|
||||
pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
|
||||
86
clippy_utils/src/ptr.rs
Normal file
86
clippy_utils/src/ptr.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use crate::{get_pat_name, match_var, snippet};
|
||||
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Body, BodyId, Expr, ExprKind, Param};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub fn get_spans(
|
||||
cx: &LateContext<'_>,
|
||||
opt_body_id: Option<BodyId>,
|
||||
idx: usize,
|
||||
replacements: &[(&'static str, &'static str)],
|
||||
) -> Option<Vec<(Span, Cow<'static, str>)>> {
|
||||
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
|
||||
get_binding_name(&body.params[idx]).map_or_else(
|
||||
|| Some(vec![]),
|
||||
|name| extract_clone_suggestions(cx, name, replacements, body),
|
||||
)
|
||||
} else {
|
||||
Some(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_clone_suggestions<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
name: Symbol,
|
||||
replace: &[(&'static str, &'static str)],
|
||||
body: &'tcx Body<'_>,
|
||||
) -> Option<Vec<(Span, Cow<'static, str>)>> {
|
||||
let mut visitor = PtrCloneVisitor {
|
||||
cx,
|
||||
name,
|
||||
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>,
|
||||
name: Symbol,
|
||||
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;
|
||||
}
|
||||
if let ExprKind::MethodCall(ref seg, _, ref args, _) = expr.kind {
|
||||
if args.len() == 1 && match_var(&args[0], self.name) {
|
||||
if seg.ident.name.as_str() == "capacity" {
|
||||
self.abort = true;
|
||||
return;
|
||||
}
|
||||
for &(fn_name, suffix) in self.replace {
|
||||
if seg.ident.name.as_str() == fn_name {
|
||||
self.spans
|
||||
.push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_binding_name(arg: &Param<'_>) -> Option<Symbol> {
|
||||
get_pat_name(&arg.pat)
|
||||
}
|
||||
347
clippy_utils/src/qualify_min_const_fn.rs
Normal file
347
clippy_utils/src/qualify_min_const_fn.rs
Normal file
@@ -0,0 +1,347 @@
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::mir::{
|
||||
Body, CastKind, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
|
||||
TerminatorKind,
|
||||
};
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
use rustc_target::spec::abi::Abi::RustIntrinsic;
|
||||
use std::borrow::Cow;
|
||||
|
||||
type McfResult = Result<(), (Span, Cow<'static, str>)>;
|
||||
|
||||
pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
|
||||
let def_id = body.source.def_id();
|
||||
let mut current = def_id;
|
||||
loop {
|
||||
let predicates = tcx.predicates_of(current);
|
||||
for (predicate, _) in predicates.predicates {
|
||||
match predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::RegionOutlives(_)
|
||||
| ty::PredicateKind::TypeOutlives(_)
|
||||
| ty::PredicateKind::WellFormed(_)
|
||||
| ty::PredicateKind::Projection(_)
|
||||
| ty::PredicateKind::ConstEvaluatable(..)
|
||||
| ty::PredicateKind::ConstEquate(..)
|
||||
| ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
|
||||
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
|
||||
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
|
||||
ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
|
||||
ty::PredicateKind::Trait(pred, _) => {
|
||||
if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
|
||||
continue;
|
||||
}
|
||||
match pred.self_ty().kind() {
|
||||
ty::Param(ref p) => {
|
||||
let generics = tcx.generics_of(current);
|
||||
let def = generics.type_param(p, tcx);
|
||||
let span = tcx.def_span(def.def_id);
|
||||
return Err((
|
||||
span,
|
||||
"trait bounds other than `Sized` \
|
||||
on const fn parameters are unstable"
|
||||
.into(),
|
||||
));
|
||||
},
|
||||
// other kinds of bounds are either tautologies
|
||||
// or cause errors in other passes
|
||||
_ => continue,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
match predicates.parent {
|
||||
Some(parent) => current = parent,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
for local in &body.local_decls {
|
||||
check_ty(tcx, local.ty, local.source_info.span)?;
|
||||
}
|
||||
// impl trait is gone in MIR, so check the return type manually
|
||||
check_ty(
|
||||
tcx,
|
||||
tcx.fn_sig(def_id).output().skip_binder(),
|
||||
body.local_decls.iter().next().unwrap().source_info.span,
|
||||
)?;
|
||||
|
||||
for bb in body.basic_blocks() {
|
||||
check_terminator(tcx, body, bb.terminator())?;
|
||||
for stmt in &bb.statements {
|
||||
check_statement(tcx, body, def_id, stmt)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
|
||||
for arg in ty.walk() {
|
||||
let ty = match arg.unpack() {
|
||||
GenericArgKind::Type(ty) => ty,
|
||||
|
||||
// No constraints on lifetimes or constants, except potentially
|
||||
// constants' types, but `walk` will get to them as well.
|
||||
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
|
||||
};
|
||||
|
||||
match ty.kind() {
|
||||
ty::Ref(_, _, hir::Mutability::Mut) => {
|
||||
return Err((span, "mutable references in const fn are unstable".into()));
|
||||
},
|
||||
ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
|
||||
ty::FnPtr(..) => {
|
||||
return Err((span, "function pointers in const fn are unstable".into()));
|
||||
},
|
||||
ty::Dynamic(preds, _) => {
|
||||
for pred in preds.iter() {
|
||||
match pred.skip_binder() {
|
||||
ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
|
||||
return Err((
|
||||
span,
|
||||
"trait bounds other than `Sized` \
|
||||
on const fn parameters are unstable"
|
||||
.into(),
|
||||
));
|
||||
},
|
||||
ty::ExistentialPredicate::Trait(trait_ref) => {
|
||||
if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
|
||||
return Err((
|
||||
span,
|
||||
"trait bounds other than `Sized` \
|
||||
on const fn parameters are unstable"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_rvalue(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, rvalue: &Rvalue<'tcx>, span: Span) -> McfResult {
|
||||
match rvalue {
|
||||
Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
|
||||
Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => check_operand(tcx, operand, span, body),
|
||||
Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
|
||||
check_place(tcx, *place, span, body)
|
||||
},
|
||||
Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
|
||||
use rustc_middle::ty::cast::CastTy;
|
||||
let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
|
||||
let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
|
||||
match (cast_in, cast_out) {
|
||||
(CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
|
||||
Err((span, "casting pointers to ints is unstable in const fn".into()))
|
||||
},
|
||||
_ => check_operand(tcx, operand, span, body),
|
||||
}
|
||||
},
|
||||
Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), operand, _) => {
|
||||
check_operand(tcx, operand, span, body)
|
||||
},
|
||||
Rvalue::Cast(
|
||||
CastKind::Pointer(
|
||||
PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer,
|
||||
),
|
||||
_,
|
||||
_,
|
||||
) => Err((span, "function pointer casts are not allowed in const fn".into())),
|
||||
Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
|
||||
let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
|
||||
deref_ty.ty
|
||||
} else {
|
||||
// We cannot allow this for now.
|
||||
return Err((span, "unsizing casts are only allowed for references right now".into()));
|
||||
};
|
||||
let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
|
||||
if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
|
||||
check_operand(tcx, op, span, body)?;
|
||||
// Casting/coercing things to slices is fine.
|
||||
Ok(())
|
||||
} else {
|
||||
// We just can't allow trait objects until we have figured out trait method calls.
|
||||
Err((span, "unsizing casts are not allowed in const fn".into()))
|
||||
}
|
||||
},
|
||||
// binops are fine on integers
|
||||
Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
|
||||
check_operand(tcx, lhs, span, body)?;
|
||||
check_operand(tcx, rhs, span, body)?;
|
||||
let ty = lhs.ty(body, tcx);
|
||||
if ty.is_integral() || ty.is_bool() || ty.is_char() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err((
|
||||
span,
|
||||
"only int, `bool` and `char` operations are stable in const fn".into(),
|
||||
))
|
||||
}
|
||||
},
|
||||
Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
|
||||
Rvalue::NullaryOp(NullOp::Box, _) => Err((span, "heap allocations are not allowed in const fn".into())),
|
||||
Rvalue::UnaryOp(_, operand) => {
|
||||
let ty = operand.ty(body, tcx);
|
||||
if ty.is_integral() || ty.is_bool() {
|
||||
check_operand(tcx, operand, span, body)
|
||||
} else {
|
||||
Err((span, "only int and `bool` operations are stable in const fn".into()))
|
||||
}
|
||||
},
|
||||
Rvalue::Aggregate(_, operands) => {
|
||||
for operand in operands {
|
||||
check_operand(tcx, operand, span, body)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_statement(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, statement: &Statement<'tcx>) -> McfResult {
|
||||
let span = statement.source_info.span;
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(box (place, rval)) => {
|
||||
check_place(tcx, *place, span, body)?;
|
||||
check_rvalue(tcx, body, def_id, rval, span)
|
||||
},
|
||||
|
||||
StatementKind::FakeRead(_, place) |
|
||||
// just an assignment
|
||||
StatementKind::SetDiscriminant { place, .. } => check_place(tcx, **place, span, body),
|
||||
|
||||
StatementKind::LlvmInlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
|
||||
|
||||
// These are all NOPs
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Retag { .. }
|
||||
| StatementKind::AscribeUserType(..)
|
||||
| StatementKind::Coverage(..)
|
||||
| StatementKind::Nop => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_operand(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
|
||||
match operand {
|
||||
Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
|
||||
Operand::Constant(c) => match c.check_static_ptr(tcx) {
|
||||
Some(_) => Err((span, "cannot access `static` items in const fn".into())),
|
||||
None => Ok(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_place(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
|
||||
let mut cursor = place.projection.as_ref();
|
||||
while let [ref proj_base @ .., elem] = *cursor {
|
||||
cursor = proj_base;
|
||||
match elem {
|
||||
ProjectionElem::Field(..) => {
|
||||
let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty;
|
||||
if let Some(def) = base_ty.ty_adt_def() {
|
||||
// No union field accesses in `const fn`
|
||||
if def.is_union() {
|
||||
return Err((span, "accessing union fields is unstable".into()));
|
||||
}
|
||||
}
|
||||
},
|
||||
ProjectionElem::ConstantIndex { .. }
|
||||
| ProjectionElem::Downcast(..)
|
||||
| ProjectionElem::Subslice { .. }
|
||||
| ProjectionElem::Deref
|
||||
| ProjectionElem::Index(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Terminator<'tcx>) -> McfResult {
|
||||
let span = terminator.source_info.span;
|
||||
match &terminator.kind {
|
||||
TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Unreachable => Ok(()),
|
||||
|
||||
TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
|
||||
TerminatorKind::DropAndReplace { place, value, .. } => {
|
||||
check_place(tcx, *place, span, body)?;
|
||||
check_operand(tcx, value, span, body)
|
||||
},
|
||||
|
||||
TerminatorKind::SwitchInt {
|
||||
discr,
|
||||
switch_ty: _,
|
||||
targets: _,
|
||||
} => check_operand(tcx, discr, span, body),
|
||||
|
||||
TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
|
||||
TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
|
||||
Err((span, "const fn generators are unstable".into()))
|
||||
},
|
||||
|
||||
TerminatorKind::Call {
|
||||
func,
|
||||
args,
|
||||
from_hir_call: _,
|
||||
destination: _,
|
||||
cleanup: _,
|
||||
fn_span: _,
|
||||
} => {
|
||||
let fn_ty = func.ty(body, tcx);
|
||||
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
|
||||
if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) {
|
||||
return Err((
|
||||
span,
|
||||
format!(
|
||||
"can only call other `const fn` within a `const fn`, \
|
||||
but `{:?}` is not stable as `const fn`",
|
||||
func,
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
|
||||
// HACK: This is to "unstabilize" the `transmute` intrinsic
|
||||
// within const fns. `transmute` is allowed in all other const contexts.
|
||||
// This won't really scale to more intrinsics or functions. Let's allow const
|
||||
// transmutes in const fn before we add more hacks to this.
|
||||
if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic && tcx.item_name(fn_def_id) == sym::transmute {
|
||||
return Err((
|
||||
span,
|
||||
"can only call `transmute` from const items, not `const fn`".into(),
|
||||
));
|
||||
}
|
||||
|
||||
check_operand(tcx, func, span, body)?;
|
||||
|
||||
for arg in args {
|
||||
check_operand(tcx, arg, span, body)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err((span, "can only call other const fns within const fn".into()))
|
||||
}
|
||||
},
|
||||
|
||||
TerminatorKind::Assert {
|
||||
cond,
|
||||
expected: _,
|
||||
msg: _,
|
||||
target: _,
|
||||
cleanup: _,
|
||||
} => check_operand(tcx, cond, span, body),
|
||||
|
||||
TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
|
||||
}
|
||||
}
|
||||
683
clippy_utils/src/sugg.rs
Normal file
683
clippy_utils/src/sugg.rs
Normal file
@@ -0,0 +1,683 @@
|
||||
//! Contains utility functions to generate suggestions.
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::{higher, snippet, snippet_opt, snippet_with_macro_callsite};
|
||||
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_lint::{EarlyContext, LateContext, LintContext};
|
||||
use rustc_span::source_map::{CharPos, Span};
|
||||
use rustc_span::{BytePos, Pos};
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Add, Neg, Not, Sub};
|
||||
|
||||
/// A helper type to build suggestion correctly handling parenthesis.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Sugg<'a> {
|
||||
/// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
|
||||
NonParen(Cow<'a, str>),
|
||||
/// An expression that does not fit in other variants.
|
||||
MaybeParen(Cow<'a, str>),
|
||||
/// A binary operator expression, including `as`-casts and explicit type
|
||||
/// coercion.
|
||||
BinOp(AssocOp, Cow<'a, str>),
|
||||
}
|
||||
|
||||
/// Literal constant `0`, for convenience.
|
||||
pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
|
||||
/// Literal constant `1`, for convenience.
|
||||
pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
|
||||
/// a constant represents an empty string, for convenience.
|
||||
pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
|
||||
|
||||
impl Display for Sugg<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match *self {
|
||||
Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
|
||||
impl<'a> Sugg<'a> {
|
||||
/// Prepare a suggestion from an expression.
|
||||
pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
|
||||
snippet_opt(cx, expr.span).map(|snippet| {
|
||||
let snippet = Cow::Owned(snippet);
|
||||
Self::hir_from_snippet(expr, snippet)
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience function around `hir_opt` for suggestions with a default
|
||||
/// text.
|
||||
pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
|
||||
Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
|
||||
}
|
||||
|
||||
/// Same as `hir`, but it adapts the applicability level by following rules:
|
||||
///
|
||||
/// - Applicability level `Unspecified` will never be changed.
|
||||
/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
|
||||
/// - If the default value is used and the applicability level is `MachineApplicable`, change it
|
||||
/// to
|
||||
/// `HasPlaceholders`
|
||||
pub fn hir_with_applicability(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
default: &'a str,
|
||||
applicability: &mut Applicability,
|
||||
) -> Self {
|
||||
if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
|
||||
*applicability = Applicability::MaybeIncorrect;
|
||||
}
|
||||
Self::hir_opt(cx, expr).unwrap_or_else(|| {
|
||||
if *applicability == Applicability::MachineApplicable {
|
||||
*applicability = Applicability::HasPlaceholders;
|
||||
}
|
||||
Sugg::NonParen(Cow::Borrowed(default))
|
||||
})
|
||||
}
|
||||
|
||||
/// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
|
||||
pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
|
||||
let snippet = snippet_with_macro_callsite(cx, expr.span, default);
|
||||
|
||||
Self::hir_from_snippet(expr, snippet)
|
||||
}
|
||||
|
||||
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
|
||||
/// function variants of `Sugg`, since these use different snippet functions.
|
||||
fn hir_from_snippet(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
|
||||
if let Some(range) = higher::range(expr) {
|
||||
let op = match range.limits {
|
||||
ast::RangeLimits::HalfOpen => AssocOp::DotDot,
|
||||
ast::RangeLimits::Closed => AssocOp::DotDotEq,
|
||||
};
|
||||
return Sugg::BinOp(op, snippet);
|
||||
}
|
||||
|
||||
match expr.kind {
|
||||
hir::ExprKind::AddrOf(..)
|
||||
| hir::ExprKind::Box(..)
|
||||
| hir::ExprKind::If(..)
|
||||
| hir::ExprKind::Closure(..)
|
||||
| hir::ExprKind::Unary(..)
|
||||
| hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
|
||||
hir::ExprKind::Continue(..)
|
||||
| hir::ExprKind::Yield(..)
|
||||
| hir::ExprKind::Array(..)
|
||||
| hir::ExprKind::Block(..)
|
||||
| hir::ExprKind::Break(..)
|
||||
| hir::ExprKind::Call(..)
|
||||
| hir::ExprKind::Field(..)
|
||||
| hir::ExprKind::Index(..)
|
||||
| hir::ExprKind::InlineAsm(..)
|
||||
| hir::ExprKind::LlvmInlineAsm(..)
|
||||
| hir::ExprKind::ConstBlock(..)
|
||||
| hir::ExprKind::Lit(..)
|
||||
| hir::ExprKind::Loop(..)
|
||||
| hir::ExprKind::MethodCall(..)
|
||||
| hir::ExprKind::Path(..)
|
||||
| hir::ExprKind::Repeat(..)
|
||||
| hir::ExprKind::Ret(..)
|
||||
| hir::ExprKind::Struct(..)
|
||||
| hir::ExprKind::Tup(..)
|
||||
| hir::ExprKind::DropTemps(_)
|
||||
| hir::ExprKind::Err => Sugg::NonParen(snippet),
|
||||
hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
|
||||
hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
|
||||
hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
|
||||
hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
|
||||
hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare a suggestion from an expression.
|
||||
pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
|
||||
let snippet = if expr.span.from_expansion() {
|
||||
snippet_with_macro_callsite(cx, expr.span, default)
|
||||
} else {
|
||||
snippet(cx, expr.span, default)
|
||||
};
|
||||
|
||||
match expr.kind {
|
||||
ast::ExprKind::AddrOf(..)
|
||||
| ast::ExprKind::Box(..)
|
||||
| ast::ExprKind::Closure(..)
|
||||
| ast::ExprKind::If(..)
|
||||
| ast::ExprKind::Let(..)
|
||||
| ast::ExprKind::Unary(..)
|
||||
| ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
|
||||
ast::ExprKind::Async(..)
|
||||
| ast::ExprKind::Block(..)
|
||||
| ast::ExprKind::Break(..)
|
||||
| ast::ExprKind::Call(..)
|
||||
| ast::ExprKind::Continue(..)
|
||||
| ast::ExprKind::Yield(..)
|
||||
| ast::ExprKind::Field(..)
|
||||
| ast::ExprKind::ForLoop(..)
|
||||
| ast::ExprKind::Index(..)
|
||||
| ast::ExprKind::InlineAsm(..)
|
||||
| ast::ExprKind::LlvmInlineAsm(..)
|
||||
| ast::ExprKind::ConstBlock(..)
|
||||
| ast::ExprKind::Lit(..)
|
||||
| ast::ExprKind::Loop(..)
|
||||
| ast::ExprKind::MacCall(..)
|
||||
| ast::ExprKind::MethodCall(..)
|
||||
| ast::ExprKind::Paren(..)
|
||||
| ast::ExprKind::Underscore
|
||||
| ast::ExprKind::Path(..)
|
||||
| ast::ExprKind::Repeat(..)
|
||||
| ast::ExprKind::Ret(..)
|
||||
| ast::ExprKind::Struct(..)
|
||||
| ast::ExprKind::Try(..)
|
||||
| ast::ExprKind::TryBlock(..)
|
||||
| ast::ExprKind::Tup(..)
|
||||
| ast::ExprKind::Array(..)
|
||||
| ast::ExprKind::While(..)
|
||||
| ast::ExprKind::Await(..)
|
||||
| ast::ExprKind::Err => Sugg::NonParen(snippet),
|
||||
ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
|
||||
ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
|
||||
ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
|
||||
ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
|
||||
ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
|
||||
ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
|
||||
ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience method to create the `<lhs> && <rhs>` suggestion.
|
||||
pub fn and(self, rhs: &Self) -> Sugg<'static> {
|
||||
make_binop(ast::BinOpKind::And, &self, rhs)
|
||||
}
|
||||
|
||||
/// Convenience method to create the `<lhs> & <rhs>` suggestion.
|
||||
pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
|
||||
make_binop(ast::BinOpKind::BitAnd, &self, rhs)
|
||||
}
|
||||
|
||||
/// Convenience method to create the `<lhs> as <rhs>` suggestion.
|
||||
pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
|
||||
make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
|
||||
}
|
||||
|
||||
/// Convenience method to create the `&<expr>` suggestion.
|
||||
pub fn addr(self) -> Sugg<'static> {
|
||||
make_unop("&", self)
|
||||
}
|
||||
|
||||
/// Convenience method to create the `&mut <expr>` suggestion.
|
||||
pub fn mut_addr(self) -> Sugg<'static> {
|
||||
make_unop("&mut ", self)
|
||||
}
|
||||
|
||||
/// Convenience method to create the `*<expr>` suggestion.
|
||||
pub fn deref(self) -> Sugg<'static> {
|
||||
make_unop("*", self)
|
||||
}
|
||||
|
||||
/// Convenience method to create the `&*<expr>` suggestion. Currently this
|
||||
/// is needed because `sugg.deref().addr()` produces an unnecessary set of
|
||||
/// parentheses around the deref.
|
||||
pub fn addr_deref(self) -> Sugg<'static> {
|
||||
make_unop("&*", self)
|
||||
}
|
||||
|
||||
/// Convenience method to create the `&mut *<expr>` suggestion. Currently
|
||||
/// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
|
||||
/// set of parentheses around the deref.
|
||||
pub fn mut_addr_deref(self) -> Sugg<'static> {
|
||||
make_unop("&mut *", self)
|
||||
}
|
||||
|
||||
/// Convenience method to transform suggestion into a return call
|
||||
pub fn make_return(self) -> Sugg<'static> {
|
||||
Sugg::NonParen(Cow::Owned(format!("return {}", self)))
|
||||
}
|
||||
|
||||
/// Convenience method to transform suggestion into a block
|
||||
/// where the suggestion is a trailing expression
|
||||
pub fn blockify(self) -> Sugg<'static> {
|
||||
Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
|
||||
}
|
||||
|
||||
/// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
|
||||
/// suggestion.
|
||||
#[allow(dead_code)]
|
||||
pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
|
||||
match limit {
|
||||
ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
|
||||
ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds parenthesis to any expression that might need them. Suitable to the
|
||||
/// `self` argument of a method call
|
||||
/// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
|
||||
pub fn maybe_par(self) -> Self {
|
||||
match self {
|
||||
Sugg::NonParen(..) => self,
|
||||
// `(x)` and `(x).y()` both don't need additional parens.
|
||||
Sugg::MaybeParen(sugg) => {
|
||||
if sugg.starts_with('(') && sugg.ends_with(')') {
|
||||
Sugg::MaybeParen(sugg)
|
||||
} else {
|
||||
Sugg::NonParen(format!("({})", sugg).into())
|
||||
}
|
||||
},
|
||||
Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from the rust standart library, and then edited
|
||||
macro_rules! forward_binop_impls_to_ref {
|
||||
(impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
|
||||
impl $imp<$t> for &$t {
|
||||
type Output = $o;
|
||||
|
||||
fn $method(self, other: $t) -> $o {
|
||||
$imp::$method(self, &other)
|
||||
}
|
||||
}
|
||||
|
||||
impl $imp<&$t> for $t {
|
||||
type Output = $o;
|
||||
|
||||
fn $method(self, other: &$t) -> $o {
|
||||
$imp::$method(&self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl $imp for $t {
|
||||
type Output = $o;
|
||||
|
||||
fn $method(self, other: $t) -> $o {
|
||||
$imp::$method(&self, &other)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Add for &Sugg<'_> {
|
||||
type Output = Sugg<'static>;
|
||||
fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
|
||||
make_binop(ast::BinOpKind::Add, self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for &Sugg<'_> {
|
||||
type Output = Sugg<'static>;
|
||||
fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
|
||||
make_binop(ast::BinOpKind::Sub, self, rhs)
|
||||
}
|
||||
}
|
||||
|
||||
forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
|
||||
forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
|
||||
|
||||
impl Neg for Sugg<'_> {
|
||||
type Output = Sugg<'static>;
|
||||
fn neg(self) -> Sugg<'static> {
|
||||
make_unop("-", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Sugg<'_> {
|
||||
type Output = Sugg<'static>;
|
||||
fn not(self) -> Sugg<'static> {
|
||||
make_unop("!", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type to display either `foo` or `(foo)`.
|
||||
struct ParenHelper<T> {
|
||||
/// `true` if parentheses are needed.
|
||||
paren: bool,
|
||||
/// The main thing to display.
|
||||
wrapped: T,
|
||||
}
|
||||
|
||||
impl<T> ParenHelper<T> {
|
||||
/// Builds a `ParenHelper`.
|
||||
fn new(paren: bool, wrapped: T) -> Self {
|
||||
Self { paren, wrapped }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Display for ParenHelper<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
if self.paren {
|
||||
write!(f, "({})", self.wrapped)
|
||||
} else {
|
||||
self.wrapped.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the string for `<op><expr>` adding parenthesis when necessary.
|
||||
///
|
||||
/// For convenience, the operator is taken as a string because all unary
|
||||
/// operators have the same
|
||||
/// precedence.
|
||||
pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
|
||||
Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
|
||||
}
|
||||
|
||||
/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
|
||||
///
|
||||
/// Precedence of shift operator relative to other arithmetic operation is
|
||||
/// often confusing so
|
||||
/// parenthesis will always be added for a mix of these.
|
||||
pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
|
||||
/// Returns `true` if the operator is a shift operator `<<` or `>>`.
|
||||
fn is_shift(op: AssocOp) -> bool {
|
||||
matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
|
||||
}
|
||||
|
||||
/// Returns `true` if the operator is a arithmetic operator
|
||||
/// (i.e., `+`, `-`, `*`, `/`, `%`).
|
||||
fn is_arith(op: AssocOp) -> bool {
|
||||
matches!(
|
||||
op,
|
||||
AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the operator `op` needs parenthesis with the operator
|
||||
/// `other` in the direction `dir`.
|
||||
fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
|
||||
other.precedence() < op.precedence()
|
||||
|| (other.precedence() == op.precedence()
|
||||
&& ((op != other && associativity(op) != dir)
|
||||
|| (op == other && associativity(op) != Associativity::Both)))
|
||||
|| is_shift(op) && is_arith(other)
|
||||
|| is_shift(other) && is_arith(op)
|
||||
}
|
||||
|
||||
let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
|
||||
needs_paren(op, lop, Associativity::Left)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
|
||||
needs_paren(op, rop, Associativity::Right)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let lhs = ParenHelper::new(lhs_paren, lhs);
|
||||
let rhs = ParenHelper::new(rhs_paren, rhs);
|
||||
let sugg = match op {
|
||||
AssocOp::Add
|
||||
| AssocOp::BitAnd
|
||||
| AssocOp::BitOr
|
||||
| AssocOp::BitXor
|
||||
| AssocOp::Divide
|
||||
| AssocOp::Equal
|
||||
| AssocOp::Greater
|
||||
| AssocOp::GreaterEqual
|
||||
| AssocOp::LAnd
|
||||
| AssocOp::LOr
|
||||
| AssocOp::Less
|
||||
| AssocOp::LessEqual
|
||||
| AssocOp::Modulus
|
||||
| AssocOp::Multiply
|
||||
| AssocOp::NotEqual
|
||||
| AssocOp::ShiftLeft
|
||||
| AssocOp::ShiftRight
|
||||
| AssocOp::Subtract => format!(
|
||||
"{} {} {}",
|
||||
lhs,
|
||||
op.to_ast_binop().expect("Those are AST ops").to_string(),
|
||||
rhs
|
||||
),
|
||||
AssocOp::Assign => format!("{} = {}", lhs, rhs),
|
||||
AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
|
||||
AssocOp::As => format!("{} as {}", lhs, rhs),
|
||||
AssocOp::DotDot => format!("{}..{}", lhs, rhs),
|
||||
AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
|
||||
AssocOp::Colon => format!("{}: {}", lhs, rhs),
|
||||
};
|
||||
|
||||
Sugg::BinOp(op, sugg.into())
|
||||
}
|
||||
|
||||
/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
|
||||
pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
|
||||
make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
/// Operator associativity.
|
||||
enum Associativity {
|
||||
/// The operator is both left-associative and right-associative.
|
||||
Both,
|
||||
/// The operator is left-associative.
|
||||
Left,
|
||||
/// The operator is not associative.
|
||||
None,
|
||||
/// The operator is right-associative.
|
||||
Right,
|
||||
}
|
||||
|
||||
/// Returns the associativity/fixity of an operator. The difference with
|
||||
/// `AssocOp::fixity` is that an operator can be both left and right associative
|
||||
/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
|
||||
///
|
||||
/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
|
||||
/// they are considered
|
||||
/// associative.
|
||||
#[must_use]
|
||||
fn associativity(op: AssocOp) -> Associativity {
|
||||
use rustc_ast::util::parser::AssocOp::{
|
||||
Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
|
||||
GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
|
||||
};
|
||||
|
||||
match op {
|
||||
Assign | AssignOp(_) => Associativity::Right,
|
||||
Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
|
||||
Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
|
||||
| Subtract => Associativity::Left,
|
||||
DotDot | DotDotEq => Associativity::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
|
||||
fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
|
||||
use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
|
||||
|
||||
AssocOp::AssignOp(match op.node {
|
||||
hir::BinOpKind::Add => Plus,
|
||||
hir::BinOpKind::BitAnd => And,
|
||||
hir::BinOpKind::BitOr => Or,
|
||||
hir::BinOpKind::BitXor => Caret,
|
||||
hir::BinOpKind::Div => Slash,
|
||||
hir::BinOpKind::Mul => Star,
|
||||
hir::BinOpKind::Rem => Percent,
|
||||
hir::BinOpKind::Shl => Shl,
|
||||
hir::BinOpKind::Shr => Shr,
|
||||
hir::BinOpKind::Sub => Minus,
|
||||
|
||||
hir::BinOpKind::And
|
||||
| hir::BinOpKind::Eq
|
||||
| hir::BinOpKind::Ge
|
||||
| hir::BinOpKind::Gt
|
||||
| hir::BinOpKind::Le
|
||||
| hir::BinOpKind::Lt
|
||||
| hir::BinOpKind::Ne
|
||||
| hir::BinOpKind::Or => panic!("This operator does not exist"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
|
||||
fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
|
||||
use rustc_ast::ast::BinOpKind::{
|
||||
Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
|
||||
};
|
||||
use rustc_ast::token::BinOpToken;
|
||||
|
||||
AssocOp::AssignOp(match op.node {
|
||||
Add => BinOpToken::Plus,
|
||||
BitAnd => BinOpToken::And,
|
||||
BitOr => BinOpToken::Or,
|
||||
BitXor => BinOpToken::Caret,
|
||||
Div => BinOpToken::Slash,
|
||||
Mul => BinOpToken::Star,
|
||||
Rem => BinOpToken::Percent,
|
||||
Shl => BinOpToken::Shl,
|
||||
Shr => BinOpToken::Shr,
|
||||
Sub => BinOpToken::Minus,
|
||||
And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the indentation before `span` if there are nothing but `[ \t]`
|
||||
/// before it on its line.
|
||||
fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
|
||||
let lo = cx.sess().source_map().lookup_char_pos(span.lo());
|
||||
lo.file
|
||||
.get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
|
||||
.and_then(|line| {
|
||||
if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
|
||||
// We can mix char and byte positions here because we only consider `[ \t]`.
|
||||
if lo.col == CharPos(pos) {
|
||||
Some(line[..pos].into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience extension trait for `DiagnosticBuilder`.
|
||||
pub trait DiagnosticBuilderExt<T: LintContext> {
|
||||
/// Suggests to add an attribute to an item.
|
||||
///
|
||||
/// Correctly handles indentation of the attribute and item.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
|
||||
/// ```
|
||||
fn suggest_item_with_attr<D: Display + ?Sized>(
|
||||
&mut self,
|
||||
cx: &T,
|
||||
item: Span,
|
||||
msg: &str,
|
||||
attr: &D,
|
||||
applicability: Applicability,
|
||||
);
|
||||
|
||||
/// Suggest to add an item before another.
|
||||
///
|
||||
/// The item should not be indented (except for inner indentation).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// diag.suggest_prepend_item(cx, item,
|
||||
/// "fn foo() {
|
||||
/// bar();
|
||||
/// }");
|
||||
/// ```
|
||||
fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
|
||||
|
||||
/// Suggest to completely remove an item.
|
||||
///
|
||||
/// This will remove an item and all following whitespace until the next non-whitespace
|
||||
/// character. This should work correctly if item is on the same indentation level as the
|
||||
/// following item.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// diag.suggest_remove_item(cx, item, "remove this")
|
||||
/// ```
|
||||
fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
|
||||
}
|
||||
|
||||
impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
|
||||
fn suggest_item_with_attr<D: Display + ?Sized>(
|
||||
&mut self,
|
||||
cx: &T,
|
||||
item: Span,
|
||||
msg: &str,
|
||||
attr: &D,
|
||||
applicability: Applicability,
|
||||
) {
|
||||
if let Some(indent) = indentation(cx, item) {
|
||||
let span = item.with_hi(item.lo());
|
||||
|
||||
self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
|
||||
if let Some(indent) = indentation(cx, item) {
|
||||
let span = item.with_hi(item.lo());
|
||||
|
||||
let mut first = true;
|
||||
let new_item = new_item
|
||||
.lines()
|
||||
.map(|l| {
|
||||
if first {
|
||||
first = false;
|
||||
format!("{}\n", l)
|
||||
} else {
|
||||
format!("{}{}\n", indent, l)
|
||||
}
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
|
||||
let mut remove_span = item;
|
||||
let hi = cx.sess().source_map().next_point(remove_span).hi();
|
||||
let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
|
||||
|
||||
if let Some(ref src) = fmpos.sf.src {
|
||||
let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
|
||||
|
||||
if let Some(non_whitespace_offset) = non_whitespace_offset {
|
||||
remove_span = remove_span
|
||||
.with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")))
|
||||
}
|
||||
}
|
||||
|
||||
self.span_suggestion(remove_span, msg, String::new(), applicability);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Sugg;
|
||||
use std::borrow::Cow;
|
||||
|
||||
const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
|
||||
|
||||
#[test]
|
||||
fn make_return_transform_sugg_into_a_return_call() {
|
||||
assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blockify_transforms_sugg_into_a_block() {
|
||||
assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
|
||||
}
|
||||
}
|
||||
7
clippy_utils/src/sym_helper.rs
Normal file
7
clippy_utils/src/sym_helper.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[macro_export]
|
||||
/// Convenience wrapper around rustc's `Symbol::intern`
|
||||
macro_rules! sym {
|
||||
($tt:tt) => {
|
||||
rustc_span::symbol::Symbol::intern(stringify!($tt))
|
||||
};
|
||||
}
|
||||
198
clippy_utils/src/usage.rs
Normal file
198
clippy_utils/src/usage.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use crate as utils;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, Path};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::ty;
|
||||
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
|
||||
/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
|
||||
pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<FxHashSet<HirId>> {
|
||||
let mut delegate = MutVarsDelegate {
|
||||
used_mutably: FxHashSet::default(),
|
||||
skip: false,
|
||||
};
|
||||
cx.tcx.infer_ctxt().enter(|infcx| {
|
||||
ExprUseVisitor::new(
|
||||
&mut delegate,
|
||||
&infcx,
|
||||
expr.hir_id.owner,
|
||||
cx.param_env,
|
||||
cx.typeck_results(),
|
||||
)
|
||||
.walk_expr(expr);
|
||||
});
|
||||
|
||||
if delegate.skip {
|
||||
return None;
|
||||
}
|
||||
Some(delegate.used_mutably)
|
||||
}
|
||||
|
||||
pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
|
||||
if let Res::Local(id) = variable.res {
|
||||
mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct MutVarsDelegate {
|
||||
used_mutably: FxHashSet<HirId>,
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
impl<'tcx> MutVarsDelegate {
|
||||
#[allow(clippy::similar_names)]
|
||||
fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
|
||||
match cat.place.base {
|
||||
PlaceBase::Local(id) => {
|
||||
self.used_mutably.insert(id);
|
||||
},
|
||||
PlaceBase::Upvar(_) => {
|
||||
//FIXME: This causes false negatives. We can't get the `NodeId` from
|
||||
//`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
|
||||
//`while`-body, not just the ones in the condition.
|
||||
self.skip = true
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
|
||||
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {}
|
||||
|
||||
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
|
||||
if let ty::BorrowKind::MutBorrow = bk {
|
||||
self.update(&cmt)
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
|
||||
self.update(&cmt)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParamBindingIdCollector {
|
||||
binding_hir_ids: Vec<hir::HirId>,
|
||||
}
|
||||
impl<'tcx> ParamBindingIdCollector {
|
||||
fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
|
||||
let mut hir_ids: Vec<hir::HirId> = Vec::new();
|
||||
for param in body.params.iter() {
|
||||
let mut finder = ParamBindingIdCollector {
|
||||
binding_hir_ids: Vec::new(),
|
||||
};
|
||||
finder.visit_param(param);
|
||||
for hir_id in &finder.binding_hir_ids {
|
||||
hir_ids.push(*hir_id);
|
||||
}
|
||||
}
|
||||
hir_ids
|
||||
}
|
||||
}
|
||||
impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
|
||||
if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
|
||||
self.binding_hir_ids.push(hir_id);
|
||||
}
|
||||
intravisit::walk_pat(self, pat);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BindingUsageFinder<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
binding_ids: Vec<hir::HirId>,
|
||||
usage_found: bool,
|
||||
}
|
||||
impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
|
||||
pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
|
||||
let mut finder = BindingUsageFinder {
|
||||
cx,
|
||||
binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
|
||||
usage_found: false,
|
||||
};
|
||||
finder.visit_body(body);
|
||||
finder.usage_found
|
||||
}
|
||||
}
|
||||
impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
|
||||
if !self.usage_found {
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
|
||||
if let hir::def::Res::Local(id) = path.res {
|
||||
if self.binding_ids.contains(&id) {
|
||||
self.usage_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
match &ex.kind {
|
||||
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
|
||||
self.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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
190
clippy_utils/src/visitors.rs
Normal file
190
clippy_utils/src/visitors.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
use crate::path_to_local_id;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Arm, Body, Expr, HirId, Stmt};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::map::Map;
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
|
||||
where
|
||||
F: FnMut(&'hir hir::Expr<'hir>) -> bool,
|
||||
{
|
||||
struct RetFinder<F> {
|
||||
in_stmt: bool,
|
||||
failed: bool,
|
||||
cb: F,
|
||||
}
|
||||
|
||||
struct WithStmtGuarg<'a, F> {
|
||||
val: &'a mut RetFinder<F>,
|
||||
prev_in_stmt: bool,
|
||||
}
|
||||
|
||||
impl<F> RetFinder<F> {
|
||||
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
|
||||
let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
|
||||
WithStmtGuarg {
|
||||
val: self,
|
||||
prev_in_stmt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
|
||||
type Target = RetFinder<F>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Drop for WithStmtGuarg<'_, F> {
|
||||
fn drop(&mut self) {
|
||||
self.val.in_stmt = self.prev_in_stmt;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
|
||||
type Map = Map<'hir>;
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
|
||||
intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt)
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
|
||||
if self.failed {
|
||||
return;
|
||||
}
|
||||
if self.in_stmt {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
|
||||
_ => intravisit::walk_expr(self, expr),
|
||||
}
|
||||
} else {
|
||||
match expr.kind {
|
||||
hir::ExprKind::If(cond, then, else_opt) => {
|
||||
self.inside_stmt(true).visit_expr(cond);
|
||||
self.visit_expr(then);
|
||||
if let Some(el) = else_opt {
|
||||
self.visit_expr(el);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Match(cond, arms, _) => {
|
||||
self.inside_stmt(true).visit_expr(cond);
|
||||
for arm in arms {
|
||||
self.visit_expr(arm.body);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
|
||||
hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
|
||||
_ => self.failed |= !(self.cb)(expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
!contains_try(expr) && {
|
||||
let mut ret_finder = RetFinder {
|
||||
in_stmt: false,
|
||||
failed: false,
|
||||
cb: callback,
|
||||
};
|
||||
ret_finder.visit_expr(expr);
|
||||
!ret_finder.failed
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LocalUsedVisitor<'hir> {
|
||||
hir: Map<'hir>,
|
||||
pub local_hir_id: HirId,
|
||||
pub used: bool,
|
||||
}
|
||||
|
||||
impl<'hir> LocalUsedVisitor<'hir> {
|
||||
pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
|
||||
Self {
|
||||
hir: cx.tcx.hir(),
|
||||
local_hir_id,
|
||||
used: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
|
||||
visit(self, t);
|
||||
std::mem::replace(&mut self.used, false)
|
||||
}
|
||||
|
||||
pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
|
||||
self.check(arm, Self::visit_arm)
|
||||
}
|
||||
|
||||
pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
|
||||
self.check(body, Self::visit_body)
|
||||
}
|
||||
|
||||
pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
|
||||
self.check(expr, Self::visit_expr)
|
||||
}
|
||||
|
||||
pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
|
||||
self.check(stmt, Self::visit_stmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
|
||||
type Map = Map<'v>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
|
||||
if self.used {
|
||||
return;
|
||||
}
|
||||
if path_to_local_id(expr, self.local_hir_id) {
|
||||
self.used = true;
|
||||
} else {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::OnlyBodies(self.hir)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user