Factor out clippy_utils crate

This commit is contained in:
Samuel E. Moelius III 2021-02-17 12:42:21 -05:00
parent a2c25fa9f0
commit 09bded4437
30 changed files with 778 additions and 681 deletions

View file

@ -1,562 +0,0 @@
//! 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::utils::{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(l), Mod(r)) => l.inline == r.inline && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_item_kind)),
(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,
}
}

View file

@ -1,45 +0,0 @@
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);
}
}

View file

@ -1,150 +0,0 @@
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))
}

View file

@ -1,779 +0,0 @@
//! A group of attributes that can be attached to Rust code in order
//! to generate a clippy lint detecting said code automatically.
use crate::utils::get_attr;
use rustc_ast::ast::{Attribute, LitFloatType, LitKind};
use rustc_ast::walk_list;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind, QPath, Stmt, StmtKind, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
use rustc_session::Session;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// **What it does:** Generates clippy code that detects the offending pattern
///
/// **Example:**
/// ```rust,ignore
/// // ./tests/ui/my_lint.rs
/// fn foo() {
/// // detect the following pattern
/// #[clippy::author]
/// if x == 42 {
/// // but ignore everything from here on
/// #![clippy::author = "ignore"]
/// }
/// ()
/// }
/// ```
///
/// Running `TESTNAME=ui/my_lint cargo uitest` will produce
/// a `./tests/ui/new_lint.stdout` file with the generated code:
///
/// ```rust,ignore
/// // ./tests/ui/new_lint.stdout
/// if_chain! {
/// if let ExprKind::If(ref cond, ref then, None) = item.kind,
/// if let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind,
/// if let ExprKind::Path(ref path) = left.kind,
/// if let ExprKind::Lit(ref lit) = right.kind,
/// if let LitKind::Int(42, _) = lit.node,
/// then {
/// // report your lint here
/// }
/// }
/// ```
pub LINT_AUTHOR,
internal_warn,
"helper for writing lints"
}
declare_lint_pass!(Author => [LINT_AUTHOR]);
fn prelude() {
println!("if_chain! {{");
}
fn done() {
println!(" then {{");
println!(" // report your lint here");
println!(" }}");
println!("}}");
}
impl<'tcx> LateLintPass<'tcx> for Author {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if !has_attr(cx.sess(), &item.attrs) {
return;
}
prelude();
PrintVisitor::new("item").visit_item(item);
done();
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if !has_attr(cx.sess(), &item.attrs) {
return;
}
prelude();
PrintVisitor::new("item").visit_impl_item(item);
done();
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
if !has_attr(cx.sess(), &item.attrs) {
return;
}
prelude();
PrintVisitor::new("item").visit_trait_item(item);
done();
}
fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx hir::Variant<'_>) {
if !has_attr(cx.sess(), &var.attrs) {
return;
}
prelude();
let parent_hir_id = cx.tcx.hir().get_parent_node(var.id);
PrintVisitor::new("var").visit_variant(var, &hir::Generics::empty(), parent_hir_id);
done();
}
fn check_struct_field(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::StructField<'_>) {
if !has_attr(cx.sess(), &field.attrs) {
return;
}
prelude();
PrintVisitor::new("field").visit_struct_field(field);
done();
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if !has_attr(cx.sess(), &expr.attrs) {
return;
}
prelude();
PrintVisitor::new("expr").visit_expr(expr);
done();
}
fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
if !has_attr(cx.sess(), &arm.attrs) {
return;
}
prelude();
PrintVisitor::new("arm").visit_arm(arm);
done();
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
if !has_attr(cx.sess(), stmt.kind.attrs(|id| cx.tcx.hir().item(id.id))) {
return;
}
prelude();
PrintVisitor::new("stmt").visit_stmt(stmt);
done();
}
fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'_>) {
if !has_attr(cx.sess(), &item.attrs) {
return;
}
prelude();
PrintVisitor::new("item").visit_foreign_item(item);
done();
}
}
impl PrintVisitor {
#[must_use]
fn new(s: &'static str) -> Self {
Self {
ids: FxHashMap::default(),
current: s.to_owned(),
}
}
fn next(&mut self, s: &'static str) -> String {
use std::collections::hash_map::Entry::{Occupied, Vacant};
match self.ids.entry(s) {
// already there: start numbering from `1`
Occupied(mut occ) => {
let val = occ.get_mut();
*val += 1;
format!("{}{}", s, *val)
},
// not there: insert and return name as given
Vacant(vac) => {
vac.insert(0);
s.to_owned()
},
}
}
fn print_qpath(&mut self, path: &QPath<'_>) {
if let QPath::LangItem(lang_item, _) = *path {
println!(
" if matches!({}, QPath::LangItem(LangItem::{:?}, _));",
self.current, lang_item,
);
} else {
print!(" if match_qpath({}, &[", self.current);
print_path(path, &mut true);
println!("]);");
}
}
}
struct PrintVisitor {
/// Fields are the current index that needs to be appended to pattern
/// binding names
ids: FxHashMap<&'static str, usize>,
/// the name that needs to be destructured
current: String,
}
impl<'tcx> Visitor<'tcx> for PrintVisitor {
type Map = Map<'tcx>;
#[allow(clippy::too_many_lines)]
fn visit_expr(&mut self, expr: &Expr<'_>) {
print!(" if let ExprKind::");
let current = format!("{}.kind", self.current);
match expr.kind {
ExprKind::Box(ref inner) => {
let inner_pat = self.next("inner");
println!("Box(ref {}) = {};", inner_pat, current);
self.current = inner_pat;
self.visit_expr(inner);
},
ExprKind::Array(ref elements) => {
let elements_pat = self.next("elements");
println!("Array(ref {}) = {};", elements_pat, current);
println!(" if {}.len() == {};", elements_pat, elements.len());
for (i, element) in elements.iter().enumerate() {
self.current = format!("{}[{}]", elements_pat, i);
self.visit_expr(element);
}
},
ExprKind::Call(ref func, ref args) => {
let func_pat = self.next("func");
let args_pat = self.next("args");
println!("Call(ref {}, ref {}) = {};", func_pat, args_pat, current);
self.current = func_pat;
self.visit_expr(func);
println!(" if {}.len() == {};", args_pat, args.len());
for (i, arg) in args.iter().enumerate() {
self.current = format!("{}[{}]", args_pat, i);
self.visit_expr(arg);
}
},
ExprKind::MethodCall(ref _method_name, ref _generics, ref _args, ref _fn_span) => {
println!(
"MethodCall(ref method_name, ref generics, ref args, ref fn_span) = {};",
current
);
println!(" // unimplemented: `ExprKind::MethodCall` is not further destructured at the moment");
},
ExprKind::Tup(ref elements) => {
let elements_pat = self.next("elements");
println!("Tup(ref {}) = {};", elements_pat, current);
println!(" if {}.len() == {};", elements_pat, elements.len());
for (i, element) in elements.iter().enumerate() {
self.current = format!("{}[{}]", elements_pat, i);
self.visit_expr(element);
}
},
ExprKind::Binary(ref op, ref left, ref right) => {
let op_pat = self.next("op");
let left_pat = self.next("left");
let right_pat = self.next("right");
println!(
"Binary(ref {}, ref {}, ref {}) = {};",
op_pat, left_pat, right_pat, current
);
println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat);
self.current = left_pat;
self.visit_expr(left);
self.current = right_pat;
self.visit_expr(right);
},
ExprKind::Unary(ref op, ref inner) => {
let inner_pat = self.next("inner");
println!("Unary(UnOp::{:?}, ref {}) = {};", op, inner_pat, current);
self.current = inner_pat;
self.visit_expr(inner);
},
ExprKind::Lit(ref lit) => {
let lit_pat = self.next("lit");
println!("Lit(ref {}) = {};", lit_pat, current);
match lit.node {
LitKind::Bool(val) => println!(" if let LitKind::Bool({:?}) = {}.node;", val, lit_pat),
LitKind::Char(c) => println!(" if let LitKind::Char({:?}) = {}.node;", c, lit_pat),
LitKind::Err(val) => println!(" if let LitKind::Err({}) = {}.node;", val, lit_pat),
LitKind::Byte(b) => println!(" if let LitKind::Byte({}) = {}.node;", b, lit_pat),
// FIXME: also check int type
LitKind::Int(i, _) => println!(" if let LitKind::Int({}, _) = {}.node;", i, lit_pat),
LitKind::Float(_, LitFloatType::Suffixed(_)) => println!(
" if let LitKind::Float(_, LitFloatType::Suffixed(_)) = {}.node;",
lit_pat
),
LitKind::Float(_, LitFloatType::Unsuffixed) => println!(
" if let LitKind::Float(_, LitFloatType::Unsuffixed) = {}.node;",
lit_pat
),
LitKind::ByteStr(ref vec) => {
let vec_pat = self.next("vec");
println!(" if let LitKind::ByteStr(ref {}) = {}.node;", vec_pat, lit_pat);
println!(" if let [{:?}] = **{};", vec, vec_pat);
},
LitKind::Str(ref text, _) => {
let str_pat = self.next("s");
println!(" if let LitKind::Str(ref {}, _) = {}.node;", str_pat, lit_pat);
println!(" if {}.as_str() == {:?}", str_pat, &*text.as_str())
},
}
},
ExprKind::Cast(ref expr, ref ty) => {
let cast_pat = self.next("expr");
let cast_ty = self.next("cast_ty");
let qp_label = self.next("qp");
println!("Cast(ref {}, ref {}) = {};", cast_pat, cast_ty, current);
if let TyKind::Path(ref qp) = ty.kind {
println!(" if let TyKind::Path(ref {}) = {}.kind;", qp_label, cast_ty);
self.current = qp_label;
self.print_qpath(qp);
}
self.current = cast_pat;
self.visit_expr(expr);
},
ExprKind::Type(ref expr, ref _ty) => {
let cast_pat = self.next("expr");
println!("Type(ref {}, _) = {};", cast_pat, current);
self.current = cast_pat;
self.visit_expr(expr);
},
ExprKind::Loop(ref body, _, desugaring, _) => {
let body_pat = self.next("body");
let des = loop_desugaring_name(desugaring);
let label_pat = self.next("label");
println!("Loop(ref {}, ref {}, {}) = {};", body_pat, label_pat, des, current);
self.current = body_pat;
self.visit_block(body);
},
ExprKind::If(ref cond, ref then, ref opt_else) => {
let cond_pat = self.next("cond");
let then_pat = self.next("then");
if let Some(ref else_) = *opt_else {
let else_pat = self.next("else_");
println!(
"If(ref {}, ref {}, Some(ref {})) = {};",
cond_pat, then_pat, else_pat, current
);
self.current = else_pat;
self.visit_expr(else_);
} else {
println!("If(ref {}, ref {}, None) = {};", cond_pat, then_pat, current);
}
self.current = cond_pat;
self.visit_expr(cond);
self.current = then_pat;
self.visit_expr(then);
},
ExprKind::Match(ref expr, ref arms, desugaring) => {
let des = desugaring_name(desugaring);
let expr_pat = self.next("expr");
let arms_pat = self.next("arms");
println!("Match(ref {}, ref {}, {}) = {};", expr_pat, arms_pat, des, current);
self.current = expr_pat;
self.visit_expr(expr);
println!(" if {}.len() == {};", arms_pat, arms.len());
for (i, arm) in arms.iter().enumerate() {
self.current = format!("{}[{}].body", arms_pat, i);
self.visit_expr(&arm.body);
if let Some(ref guard) = arm.guard {
let guard_pat = self.next("guard");
println!(" if let Some(ref {}) = {}[{}].guard;", guard_pat, arms_pat, i);
match guard {
hir::Guard::If(ref if_expr) => {
let if_expr_pat = self.next("expr");
println!(" if let Guard::If(ref {}) = {};", if_expr_pat, guard_pat);
self.current = if_expr_pat;
self.visit_expr(if_expr);
},
hir::Guard::IfLet(ref if_let_pat, ref if_let_expr) => {
let if_let_pat_pat = self.next("pat");
let if_let_expr_pat = self.next("expr");
println!(
" if let Guard::IfLet(ref {}, ref {}) = {};",
if_let_pat_pat, if_let_expr_pat, guard_pat
);
self.current = if_let_expr_pat;
self.visit_expr(if_let_expr);
self.current = if_let_pat_pat;
self.visit_pat(if_let_pat);
},
}
}
self.current = format!("{}[{}].pat", arms_pat, i);
self.visit_pat(&arm.pat);
}
},
ExprKind::Closure(ref _capture_clause, ref _func, _, _, _) => {
println!("Closure(ref capture_clause, ref func, _, _, _) = {};", current);
println!(" // unimplemented: `ExprKind::Closure` is not further destructured at the moment");
},
ExprKind::Yield(ref sub, _) => {
let sub_pat = self.next("sub");
println!("Yield(ref sub) = {};", current);
self.current = sub_pat;
self.visit_expr(sub);
},
ExprKind::Block(ref block, _) => {
let block_pat = self.next("block");
println!("Block(ref {}) = {};", block_pat, current);
self.current = block_pat;
self.visit_block(block);
},
ExprKind::Assign(ref target, ref value, _) => {
let target_pat = self.next("target");
let value_pat = self.next("value");
println!(
"Assign(ref {}, ref {}, ref _span) = {};",
target_pat, value_pat, current
);
self.current = target_pat;
self.visit_expr(target);
self.current = value_pat;
self.visit_expr(value);
},
ExprKind::AssignOp(ref op, ref target, ref value) => {
let op_pat = self.next("op");
let target_pat = self.next("target");
let value_pat = self.next("value");
println!(
"AssignOp(ref {}, ref {}, ref {}) = {};",
op_pat, target_pat, value_pat, current
);
println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat);
self.current = target_pat;
self.visit_expr(target);
self.current = value_pat;
self.visit_expr(value);
},
ExprKind::Field(ref object, ref field_ident) => {
let obj_pat = self.next("object");
let field_name_pat = self.next("field_name");
println!("Field(ref {}, ref {}) = {};", obj_pat, field_name_pat, current);
println!(" if {}.as_str() == {:?}", field_name_pat, field_ident.as_str());
self.current = obj_pat;
self.visit_expr(object);
},
ExprKind::Index(ref object, ref index) => {
let object_pat = self.next("object");
let index_pat = self.next("index");
println!("Index(ref {}, ref {}) = {};", object_pat, index_pat, current);
self.current = object_pat;
self.visit_expr(object);
self.current = index_pat;
self.visit_expr(index);
},
ExprKind::Path(ref path) => {
let path_pat = self.next("path");
println!("Path(ref {}) = {};", path_pat, current);
self.current = path_pat;
self.print_qpath(path);
},
ExprKind::AddrOf(kind, mutability, ref inner) => {
let inner_pat = self.next("inner");
println!(
"AddrOf(BorrowKind::{:?}, Mutability::{:?}, ref {}) = {};",
kind, mutability, inner_pat, current
);
self.current = inner_pat;
self.visit_expr(inner);
},
ExprKind::Break(ref _destination, ref opt_value) => {
let destination_pat = self.next("destination");
if let Some(ref value) = *opt_value {
let value_pat = self.next("value");
println!("Break(ref {}, Some(ref {})) = {};", destination_pat, value_pat, current);
self.current = value_pat;
self.visit_expr(value);
} else {
println!("Break(ref {}, None) = {};", destination_pat, current);
}
// FIXME: implement label printing
},
ExprKind::Continue(ref _destination) => {
let destination_pat = self.next("destination");
println!("Again(ref {}) = {};", destination_pat, current);
// FIXME: implement label printing
},
ExprKind::Ret(ref opt_value) => {
if let Some(ref value) = *opt_value {
let value_pat = self.next("value");
println!("Ret(Some(ref {})) = {};", value_pat, current);
self.current = value_pat;
self.visit_expr(value);
} else {
println!("Ret(None) = {};", current);
}
},
ExprKind::InlineAsm(_) => {
println!("InlineAsm(_) = {};", current);
println!(" // unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
},
ExprKind::LlvmInlineAsm(_) => {
println!("LlvmInlineAsm(_) = {};", current);
println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment");
},
ExprKind::Struct(ref path, ref fields, ref opt_base) => {
let path_pat = self.next("path");
let fields_pat = self.next("fields");
if let Some(ref base) = *opt_base {
let base_pat = self.next("base");
println!(
"Struct(ref {}, ref {}, Some(ref {})) = {};",
path_pat, fields_pat, base_pat, current
);
self.current = base_pat;
self.visit_expr(base);
} else {
println!("Struct(ref {}, ref {}, None) = {};", path_pat, fields_pat, current);
}
self.current = path_pat;
self.print_qpath(path);
println!(" if {}.len() == {};", fields_pat, fields.len());
println!(" // unimplemented: field checks");
},
ExprKind::ConstBlock(_) => {
let value_pat = self.next("value");
println!("Const({})", value_pat);
self.current = value_pat;
},
// FIXME: compute length (needs type info)
ExprKind::Repeat(ref value, _) => {
let value_pat = self.next("value");
println!("Repeat(ref {}, _) = {};", value_pat, current);
println!("// unimplemented: repeat count check");
self.current = value_pat;
self.visit_expr(value);
},
ExprKind::Err => {
println!("Err = {}", current);
},
ExprKind::DropTemps(ref expr) => {
let expr_pat = self.next("expr");
println!("DropTemps(ref {}) = {};", expr_pat, current);
self.current = expr_pat;
self.visit_expr(expr);
},
}
}
fn visit_block(&mut self, block: &Block<'_>) {
let trailing_pat = self.next("trailing_expr");
println!(" if let Some({}) = &{}.expr;", trailing_pat, self.current);
println!(" if {}.stmts.len() == {};", self.current, block.stmts.len());
let current = self.current.clone();
for (i, stmt) in block.stmts.iter().enumerate() {
self.current = format!("{}.stmts[{}]", current, i);
self.visit_stmt(stmt);
}
}
#[allow(clippy::too_many_lines)]
fn visit_pat(&mut self, pat: &Pat<'_>) {
print!(" if let PatKind::");
let current = format!("{}.kind", self.current);
match pat.kind {
PatKind::Wild => println!("Wild = {};", current),
PatKind::Binding(anno, .., ident, ref sub) => {
let anno_pat = match anno {
BindingAnnotation::Unannotated => "BindingAnnotation::Unannotated",
BindingAnnotation::Mutable => "BindingAnnotation::Mutable",
BindingAnnotation::Ref => "BindingAnnotation::Ref",
BindingAnnotation::RefMut => "BindingAnnotation::RefMut",
};
let name_pat = self.next("name");
if let Some(ref sub) = *sub {
let sub_pat = self.next("sub");
println!(
"Binding({}, _, {}, Some(ref {})) = {};",
anno_pat, name_pat, sub_pat, current
);
self.current = sub_pat;
self.visit_pat(sub);
} else {
println!("Binding({}, _, {}, None) = {};", anno_pat, name_pat, current);
}
println!(" if {}.as_str() == \"{}\";", name_pat, ident.as_str());
},
PatKind::Struct(ref path, ref fields, ignore) => {
let path_pat = self.next("path");
let fields_pat = self.next("fields");
println!(
"Struct(ref {}, ref {}, {}) = {};",
path_pat, fields_pat, ignore, current
);
self.current = path_pat;
self.print_qpath(path);
println!(" if {}.len() == {};", fields_pat, fields.len());
println!(" // unimplemented: field checks");
},
PatKind::Or(ref fields) => {
let fields_pat = self.next("fields");
println!("Or(ref {}) = {};", fields_pat, current);
println!(" if {}.len() == {};", fields_pat, fields.len());
println!(" // unimplemented: field checks");
},
PatKind::TupleStruct(ref path, ref fields, skip_pos) => {
let path_pat = self.next("path");
let fields_pat = self.next("fields");
println!(
"TupleStruct(ref {}, ref {}, {:?}) = {};",
path_pat, fields_pat, skip_pos, current
);
self.current = path_pat;
self.print_qpath(path);
println!(" if {}.len() == {};", fields_pat, fields.len());
println!(" // unimplemented: field checks");
},
PatKind::Path(ref path) => {
let path_pat = self.next("path");
println!("Path(ref {}) = {};", path_pat, current);
self.current = path_pat;
self.print_qpath(path);
},
PatKind::Tuple(ref fields, skip_pos) => {
let fields_pat = self.next("fields");
println!("Tuple(ref {}, {:?}) = {};", fields_pat, skip_pos, current);
println!(" if {}.len() == {};", fields_pat, fields.len());
println!(" // unimplemented: field checks");
},
PatKind::Box(ref pat) => {
let pat_pat = self.next("pat");
println!("Box(ref {}) = {};", pat_pat, current);
self.current = pat_pat;
self.visit_pat(pat);
},
PatKind::Ref(ref pat, muta) => {
let pat_pat = self.next("pat");
println!("Ref(ref {}, Mutability::{:?}) = {};", pat_pat, muta, current);
self.current = pat_pat;
self.visit_pat(pat);
},
PatKind::Lit(ref lit_expr) => {
let lit_expr_pat = self.next("lit_expr");
println!("Lit(ref {}) = {}", lit_expr_pat, current);
self.current = lit_expr_pat;
self.visit_expr(lit_expr);
},
PatKind::Range(ref start, ref end, end_kind) => {
let start_pat = self.next("start");
let end_pat = self.next("end");
println!(
"Range(ref {}, ref {}, RangeEnd::{:?}) = {};",
start_pat, end_pat, end_kind, current
);
self.current = start_pat;
walk_list!(self, visit_expr, start);
self.current = end_pat;
walk_list!(self, visit_expr, end);
},
PatKind::Slice(ref start, ref middle, ref end) => {
let start_pat = self.next("start");
let end_pat = self.next("end");
if let Some(ref middle) = middle {
let middle_pat = self.next("middle");
println!(
"Slice(ref {}, Some(ref {}), ref {}) = {};",
start_pat, middle_pat, end_pat, current
);
self.current = middle_pat;
self.visit_pat(middle);
} else {
println!("Slice(ref {}, None, ref {}) = {};", start_pat, end_pat, current);
}
println!(" if {}.len() == {};", start_pat, start.len());
for (i, pat) in start.iter().enumerate() {
self.current = format!("{}[{}]", start_pat, i);
self.visit_pat(pat);
}
println!(" if {}.len() == {};", end_pat, end.len());
for (i, pat) in end.iter().enumerate() {
self.current = format!("{}[{}]", end_pat, i);
self.visit_pat(pat);
}
},
}
}
fn visit_stmt(&mut self, s: &Stmt<'_>) {
print!(" if let StmtKind::");
let current = format!("{}.kind", self.current);
match s.kind {
// A local (let) binding:
StmtKind::Local(ref local) => {
let local_pat = self.next("local");
println!("Local(ref {}) = {};", local_pat, current);
if let Some(ref init) = local.init {
let init_pat = self.next("init");
println!(" if let Some(ref {}) = {}.init;", init_pat, local_pat);
self.current = init_pat;
self.visit_expr(init);
}
self.current = format!("{}.pat", local_pat);
self.visit_pat(&local.pat);
},
// An item binding:
StmtKind::Item(_) => {
println!("Item(item_id) = {};", current);
},
// Expr without trailing semi-colon (must have unit type):
StmtKind::Expr(ref e) => {
let e_pat = self.next("e");
println!("Expr(ref {}, _) = {}", e_pat, current);
self.current = e_pat;
self.visit_expr(e);
},
// Expr with trailing semi-colon (may have any type):
StmtKind::Semi(ref e) => {
let e_pat = self.next("e");
println!("Semi(ref {}, _) = {}", e_pat, current);
self.current = e_pat;
self.visit_expr(e);
},
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
fn has_attr(sess: &Session, attrs: &[Attribute]) -> bool {
get_attr(sess, attrs, "author").count() > 0
}
#[must_use]
fn desugaring_name(des: hir::MatchSource) -> String {
match des {
hir::MatchSource::ForLoopDesugar => "MatchSource::ForLoopDesugar".to_string(),
hir::MatchSource::TryDesugar => "MatchSource::TryDesugar".to_string(),
hir::MatchSource::WhileDesugar => "MatchSource::WhileDesugar".to_string(),
hir::MatchSource::WhileLetDesugar => "MatchSource::WhileLetDesugar".to_string(),
hir::MatchSource::Normal => "MatchSource::Normal".to_string(),
hir::MatchSource::IfLetDesugar { contains_else_clause } => format!(
"MatchSource::IfLetDesugar {{ contains_else_clause: {} }}",
contains_else_clause
),
hir::MatchSource::IfLetGuardDesugar => "MatchSource::IfLetGuardDesugar".to_string(),
hir::MatchSource::AwaitDesugar => "MatchSource::AwaitDesugar".to_string(),
}
}
#[must_use]
fn loop_desugaring_name(des: hir::LoopSource) -> &'static str {
match des {
hir::LoopSource::ForLoop => "LoopSource::ForLoop",
hir::LoopSource::Loop => "LoopSource::Loop",
hir::LoopSource::While => "LoopSource::While",
hir::LoopSource::WhileLet => "LoopSource::WhileLet",
}
}
fn print_path(path: &QPath<'_>, first: &mut bool) {
match *path {
QPath::Resolved(_, ref path) => {
for segment in path.segments {
if *first {
*first = false;
} else {
print!(", ");
}
print!("{:?}", segment.ident.as_str());
}
},
QPath::TypeRelative(ref ty, ref segment) => match ty.kind {
hir::TyKind::Path(ref inner_path) => {
print_path(inner_path, first);
if *first {
*first = false;
} else {
print!(", ");
}
print!("{:?}", segment.ident.as_str());
},
ref other => print!("/* unimplemented: {:?}*/", other),
},
QPath::LangItem(..) => panic!("print_path: called for lang item qpath"),
}
}

View file

@ -1,121 +0,0 @@
/// 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);
}
}

View file

@ -1,36 +0,0 @@
//! 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,
}
}

View file

@ -1,257 +0,0 @@
//! Read configurations files.
#![deny(clippy::missing_docs_in_private_items)]
use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem};
use rustc_span::source_map;
use source_map::Span;
use std::lazy::SyncLazy;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs, io};
/// Gets the configuration file from arguments.
pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> {
for arg in args.iter().filter_map(NestedMetaItem::meta_item) {
if arg.has_name(sym!(conf_file)) {
return match arg.kind {
MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
MetaItemKind::NameValue(ref value) => {
if let LitKind::Str(ref file, _) = value.kind {
Ok(Some(file.to_string().into()))
} else {
Err(("`conf_file` value must be a string", value.span))
}
},
};
}
}
Ok(None)
}
/// Error from reading a configuration file.
#[derive(Debug)]
pub enum Error {
/// An I/O error.
Io(io::Error),
/// Not valid toml or doesn't fit the expected config format
Toml(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => err.fmt(f),
Self::Toml(err) => err.fmt(f),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
/// Vec of errors that might be collected during config toml parsing
static ERRORS: SyncLazy<Mutex<Vec<Error>>> = SyncLazy::new(|| Mutex::new(Vec::new()));
macro_rules! define_Conf {
($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => {
mod helpers {
use serde::Deserialize;
/// Type used to store lint configuration.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Conf {
$(
#[$doc]
#[serde(default = $config_str)]
#[serde(with = $config_str)]
pub $config: $Ty,
)+
#[allow(dead_code)]
#[serde(default)]
third_party: Option<::toml::Value>,
}
$(
mod $config {
use serde::Deserialize;
pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> {
use super::super::{ERRORS, Error};
Ok(
<$Ty>::deserialize(deserializer).unwrap_or_else(|e| {
ERRORS
.lock()
.expect("no threading here")
.push(Error::Toml(e.to_string()));
super::$config()
})
)
}
}
#[must_use]
fn $config() -> $Ty {
let x = $default;
x
}
)+
}
};
}
pub use self::helpers::Conf;
define_Conf! {
/// Lint: REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN. The minimum rust version that the project supports
(msrv, "msrv": Option<String>, None),
/// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
(blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
/// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
(cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25),
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
(cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None),
/// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
(doc_valid_idents, "doc_valid_idents": Vec<String>, [
"KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
"DirectX",
"ECMAScript",
"GPLv2", "GPLv3",
"GitHub", "GitLab",
"IPv4", "IPv6",
"ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
"NaN", "NaNs",
"OAuth", "GraphQL",
"OCaml",
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
"WebGL",
"TensorFlow",
"TrueType",
"iOS", "macOS",
"TeX", "LaTeX", "BibTeX", "BibLaTeX",
"MinGW",
"CamelCase",
].iter().map(ToString::to_string).collect()),
/// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
(too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7),
/// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
(type_complexity_threshold, "type_complexity_threshold": u64, 250),
/// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
(single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
/// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
(too_large_for_stack, "too_large_for_stack": u64, 200),
/// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
(enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
/// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
(enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200),
/// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
(verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1),
/// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
(literal_representation_threshold, "literal_representation_threshold": u64, 16384),
/// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
(trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None),
/// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
(pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256),
/// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
(too_many_lines_threshold, "too_many_lines_threshold": u64, 100),
/// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
(array_size_threshold, "array_size_threshold": u64, 512_000),
/// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
(vec_box_size_threshold, "vec_box_size_threshold": u64, 4096),
/// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
(max_trait_bounds, "max_trait_bounds": u64, 3),
/// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
(max_struct_bools, "max_struct_bools": u64, 3),
/// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
(max_fn_params_bools, "max_fn_params_bools": u64, 3),
/// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
(warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
/// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
(disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
/// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
(unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true),
/// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
(cargo_ignore_publish, "cargo_ignore_publish": bool, false),
}
impl Default for Conf {
#[must_use]
fn default() -> Self {
toml::from_str("").expect("we never error on empty config files")
}
}
/// Search for the configuration file.
pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
/// Possible filename to search for.
const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
// Start looking for a config file in CLIPPY_CONF_DIR, or failing that, CARGO_MANIFEST_DIR.
// If neither of those exist, use ".".
let mut current = env::var_os("CLIPPY_CONF_DIR")
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
.map_or_else(|| PathBuf::from("."), PathBuf::from);
loop {
for config_file_name in &CONFIG_FILE_NAMES {
let config_file = current.join(config_file_name);
match fs::metadata(&config_file) {
// Only return if it's a file to handle the unlikely situation of a directory named
// `clippy.toml`.
Ok(ref md) if !md.is_dir() => return Ok(Some(config_file)),
// Return the error if it's something other than `NotFound`; otherwise we didn't
// find the project file yet, and continue searching.
Err(e) if e.kind() != io::ErrorKind::NotFound => return Err(e),
_ => {},
}
}
// If the current directory has no parent, we're done searching.
if !current.pop() {
return Ok(None);
}
}
}
/// Produces a `Conf` filled with the default values and forwards the errors
///
/// Used internally for convenience
fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
(Conf::default(), errors)
}
/// Read the `toml` configuration file.
///
/// In case of error, the function tries to continue as much as possible.
pub fn read(path: &Path) -> (Conf, Vec<Error>) {
let content = match fs::read_to_string(path) {
Ok(content) => content,
Err(err) => return default(vec![err.into()]),
};
assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
match toml::from_str(&content) {
Ok(toml) => {
let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
let toml_ref: &Conf = &toml;
let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
if cyc_field.is_some() {
let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
errors.push(Error::Toml(cyc_err));
}
(toml, errors)
},
Err(e) => {
let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
errors.push(Error::Toml(e.to_string()));
default(errors)
},
}
}

View file

@ -1,226 +0,0 @@
//! 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);
}

View file

@ -1,134 +0,0 @@
//! 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::utils::{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)
}

View file

@ -1,268 +0,0 @@
//! This module contains functions for retrieve the original AST from lowered
//! `hir`.
#![deny(clippy::missing_docs_in_private_items)]
use crate::utils::{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
}

View file

@ -1,852 +0,0 @@
use crate::consts::{constant_context, constant_simple};
use crate::utils::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),
}
}
}
}

View file

@ -1,578 +0,0 @@
//! checks for attributes
use crate::utils::get_attr;
use rustc_ast::ast::{Attribute, InlineAsmTemplatePiece};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::Session;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// **What it does:** Dumps every ast/hir node which has the `#[clippy::dump]`
/// attribute
///
/// **Example:**
/// ```rust,ignore
/// #[clippy::dump]
/// extern crate foo;
/// ```
///
/// prints
///
/// ```text
/// item `foo`
/// visibility inherited from outer item
/// extern crate dylib source: "/path/to/foo.so"
/// ```
pub DEEP_CODE_INSPECTION,
internal_warn,
"helper to dump info about code"
}
declare_lint_pass!(DeepCodeInspector => [DEEP_CODE_INSPECTION]);
impl<'tcx> LateLintPass<'tcx> for DeepCodeInspector {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if !has_attr(cx.sess(), &item.attrs) {
return;
}
print_item(cx, item);
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if !has_attr(cx.sess(), &item.attrs) {
return;
}
println!("impl item `{}`", item.ident.name);
match item.vis.node {
hir::VisibilityKind::Public => println!("public"),
hir::VisibilityKind::Crate(_) => println!("visible crate wide"),
hir::VisibilityKind::Restricted { ref path, .. } => println!(
"visible in module `{}`",
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(path, false))
),
hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"),
}
if item.defaultness.is_default() {
println!("default");
}
match item.kind {
hir::ImplItemKind::Const(_, body_id) => {
println!("associated constant");
print_expr(cx, &cx.tcx.hir().body(body_id).value, 1);
},
hir::ImplItemKind::Fn(..) => println!("method"),
hir::ImplItemKind::TyAlias(_) => println!("associated type"),
}
}
// fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx
// hir::TraitItem) {
// if !has_attr(&item.attrs) {
// return;
// }
// }
//
// fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx
// hir::Variant, _:
// &hir::Generics) {
// if !has_attr(&var.node.attrs) {
// return;
// }
// }
//
// fn check_struct_field(&mut self, cx: &LateContext<'tcx>, field: &'tcx
// hir::StructField) {
// if !has_attr(&field.attrs) {
// return;
// }
// }
//
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if !has_attr(cx.sess(), &expr.attrs) {
return;
}
print_expr(cx, expr, 0);
}
fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
if !has_attr(cx.sess(), &arm.attrs) {
return;
}
print_pat(cx, &arm.pat, 1);
if let Some(ref guard) = arm.guard {
println!("guard:");
print_guard(cx, guard, 1);
}
println!("body:");
print_expr(cx, &arm.body, 1);
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
if !has_attr(cx.sess(), stmt.kind.attrs(|id| cx.tcx.hir().item(id.id))) {
return;
}
match stmt.kind {
hir::StmtKind::Local(ref local) => {
println!("local variable of type {}", cx.typeck_results().node_type(local.hir_id));
println!("pattern:");
print_pat(cx, &local.pat, 0);
if let Some(ref e) = local.init {
println!("init expression:");
print_expr(cx, e, 0);
}
},
hir::StmtKind::Item(_) => println!("item decl"),
hir::StmtKind::Expr(ref e) | hir::StmtKind::Semi(ref e) => print_expr(cx, e, 0),
}
}
// fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx
// hir::ForeignItem) {
// if !has_attr(&item.attrs) {
// return;
// }
// }
//
}
fn has_attr(sess: &Session, attrs: &[Attribute]) -> bool {
get_attr(sess, attrs, "dump").count() > 0
}
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
fn print_expr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, indent: usize) {
let ind = " ".repeat(indent);
println!("{}+", ind);
println!("{}ty: {}", ind, cx.typeck_results().expr_ty(expr));
println!(
"{}adjustments: {:?}",
ind,
cx.typeck_results().adjustments().get(expr.hir_id)
);
match expr.kind {
hir::ExprKind::Box(ref e) => {
println!("{}Box", ind);
print_expr(cx, e, indent + 1);
},
hir::ExprKind::Array(v) => {
println!("{}Array", ind);
for e in v {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Call(ref func, args) => {
println!("{}Call", ind);
println!("{}function:", ind);
print_expr(cx, func, indent + 1);
println!("{}arguments:", ind);
for arg in args {
print_expr(cx, arg, indent + 1);
}
},
hir::ExprKind::MethodCall(ref path, _, args, _) => {
println!("{}MethodCall", ind);
println!("{}method name: {}", ind, path.ident.name);
for arg in args {
print_expr(cx, arg, indent + 1);
}
},
hir::ExprKind::Tup(v) => {
println!("{}Tup", ind);
for e in v {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Binary(op, ref lhs, ref rhs) => {
println!("{}Binary", ind);
println!("{}op: {:?}", ind, op.node);
println!("{}lhs:", ind);
print_expr(cx, lhs, indent + 1);
println!("{}rhs:", ind);
print_expr(cx, rhs, indent + 1);
},
hir::ExprKind::Unary(op, ref inner) => {
println!("{}Unary", ind);
println!("{}op: {:?}", ind, op);
print_expr(cx, inner, indent + 1);
},
hir::ExprKind::Lit(ref lit) => {
println!("{}Lit", ind);
println!("{}{:?}", ind, lit);
},
hir::ExprKind::Cast(ref e, ref target) => {
println!("{}Cast", ind);
print_expr(cx, e, indent + 1);
println!("{}target type: {:?}", ind, target);
},
hir::ExprKind::Type(ref e, ref target) => {
println!("{}Type", ind);
print_expr(cx, e, indent + 1);
println!("{}target type: {:?}", ind, target);
},
hir::ExprKind::Loop(..) => {
println!("{}Loop", ind);
},
hir::ExprKind::If(ref cond, _, ref else_opt) => {
println!("{}If", ind);
println!("{}condition:", ind);
print_expr(cx, cond, indent + 1);
if let Some(ref els) = *else_opt {
println!("{}else:", ind);
print_expr(cx, els, indent + 1);
}
},
hir::ExprKind::Match(ref cond, _, ref source) => {
println!("{}Match", ind);
println!("{}condition:", ind);
print_expr(cx, cond, indent + 1);
println!("{}source: {:?}", ind, source);
},
hir::ExprKind::Closure(ref clause, _, _, _, _) => {
println!("{}Closure", ind);
println!("{}clause: {:?}", ind, clause);
},
hir::ExprKind::Yield(ref sub, _) => {
println!("{}Yield", ind);
print_expr(cx, sub, indent + 1);
},
hir::ExprKind::Block(_, _) => {
println!("{}Block", ind);
},
hir::ExprKind::Assign(ref lhs, ref rhs, _) => {
println!("{}Assign", ind);
println!("{}lhs:", ind);
print_expr(cx, lhs, indent + 1);
println!("{}rhs:", ind);
print_expr(cx, rhs, indent + 1);
},
hir::ExprKind::AssignOp(ref binop, ref lhs, ref rhs) => {
println!("{}AssignOp", ind);
println!("{}op: {:?}", ind, binop.node);
println!("{}lhs:", ind);
print_expr(cx, lhs, indent + 1);
println!("{}rhs:", ind);
print_expr(cx, rhs, indent + 1);
},
hir::ExprKind::Field(ref e, ident) => {
println!("{}Field", ind);
println!("{}field name: {}", ind, ident.name);
println!("{}struct expr:", ind);
print_expr(cx, e, indent + 1);
},
hir::ExprKind::Index(ref arr, ref idx) => {
println!("{}Index", ind);
println!("{}array expr:", ind);
print_expr(cx, arr, indent + 1);
println!("{}index expr:", ind);
print_expr(cx, idx, indent + 1);
},
hir::ExprKind::Path(hir::QPath::Resolved(ref ty, ref path)) => {
println!("{}Resolved Path, {:?}", ind, ty);
println!("{}path: {:?}", ind, path);
},
hir::ExprKind::Path(hir::QPath::TypeRelative(ref ty, ref seg)) => {
println!("{}Relative Path, {:?}", ind, ty);
println!("{}seg: {:?}", ind, seg);
},
hir::ExprKind::Path(hir::QPath::LangItem(lang_item, ..)) => {
println!("{}Lang Item Path, {:?}", ind, lang_item.name());
},
hir::ExprKind::AddrOf(kind, ref muta, ref e) => {
println!("{}AddrOf", ind);
println!("kind: {:?}", kind);
println!("mutability: {:?}", muta);
print_expr(cx, e, indent + 1);
},
hir::ExprKind::Break(_, ref e) => {
println!("{}Break", ind);
if let Some(ref e) = *e {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Continue(_) => println!("{}Again", ind),
hir::ExprKind::Ret(ref e) => {
println!("{}Ret", ind);
if let Some(ref e) = *e {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::InlineAsm(ref asm) => {
println!("{}InlineAsm", ind);
println!("{}template: {}", ind, InlineAsmTemplatePiece::to_string(asm.template));
println!("{}options: {:?}", ind, asm.options);
println!("{}operands:", ind);
for (op, _op_sp) in asm.operands {
match op {
hir::InlineAsmOperand::In { expr, .. }
| hir::InlineAsmOperand::InOut { expr, .. }
| hir::InlineAsmOperand::Const { expr }
| hir::InlineAsmOperand::Sym { expr } => print_expr(cx, expr, indent + 1),
hir::InlineAsmOperand::Out { expr, .. } => {
if let Some(expr) = expr {
print_expr(cx, expr, indent + 1);
}
},
hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => {
print_expr(cx, in_expr, indent + 1);
if let Some(out_expr) = out_expr {
print_expr(cx, out_expr, indent + 1);
}
},
}
}
},
hir::ExprKind::LlvmInlineAsm(ref asm) => {
let inputs = &asm.inputs_exprs;
let outputs = &asm.outputs_exprs;
println!("{}LlvmInlineAsm", ind);
println!("{}inputs:", ind);
for e in inputs.iter() {
print_expr(cx, e, indent + 1);
}
println!("{}outputs:", ind);
for e in outputs.iter() {
print_expr(cx, e, indent + 1);
}
},
hir::ExprKind::Struct(ref path, fields, ref base) => {
println!("{}Struct", ind);
println!("{}path: {:?}", ind, path);
for field in fields {
println!("{}field \"{}\":", ind, field.ident.name);
print_expr(cx, &field.expr, indent + 1);
}
if let Some(ref base) = *base {
println!("{}base:", ind);
print_expr(cx, base, indent + 1);
}
},
hir::ExprKind::ConstBlock(ref anon_const) => {
println!("{}ConstBlock", ind);
println!("{}anon_const:", ind);
print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1);
},
hir::ExprKind::Repeat(ref val, ref anon_const) => {
println!("{}Repeat", ind);
println!("{}value:", ind);
print_expr(cx, val, indent + 1);
println!("{}repeat count:", ind);
print_expr(cx, &cx.tcx.hir().body(anon_const.body).value, indent + 1);
},
hir::ExprKind::Err => {
println!("{}Err", ind);
},
hir::ExprKind::DropTemps(ref e) => {
println!("{}DropTemps", ind);
print_expr(cx, e, indent + 1);
},
}
}
fn print_item(cx: &LateContext<'_>, item: &hir::Item<'_>) {
let did = cx.tcx.hir().local_def_id(item.hir_id);
println!("item `{}`", item.ident.name);
match item.vis.node {
hir::VisibilityKind::Public => println!("public"),
hir::VisibilityKind::Crate(_) => println!("visible crate wide"),
hir::VisibilityKind::Restricted { ref path, .. } => println!(
"visible in module `{}`",
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(path, false))
),
hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"),
}
match item.kind {
hir::ItemKind::ExternCrate(ref _renamed_from) => {
let def_id = cx.tcx.hir().local_def_id(item.hir_id);
if let Some(crate_id) = cx.tcx.extern_mod_stmt_cnum(def_id) {
let source = cx.tcx.used_crate_source(crate_id);
if let Some(ref src) = source.dylib {
println!("extern crate dylib source: {:?}", src.0);
}
if let Some(ref src) = source.rlib {
println!("extern crate rlib source: {:?}", src.0);
}
} else {
println!("weird extern crate without a crate id");
}
},
hir::ItemKind::Use(ref path, ref kind) => println!("{:?}, {:?}", path, kind),
hir::ItemKind::Static(..) => println!("static item of type {:#?}", cx.tcx.type_of(did)),
hir::ItemKind::Const(..) => println!("const item of type {:#?}", cx.tcx.type_of(did)),
hir::ItemKind::Fn(..) => {
let item_ty = cx.tcx.type_of(did);
println!("function of type {:#?}", item_ty);
},
hir::ItemKind::Mod(..) => println!("module"),
hir::ItemKind::ForeignMod { abi, .. } => println!("foreign module with abi: {}", abi),
hir::ItemKind::GlobalAsm(ref asm) => println!("global asm: {:?}", asm),
hir::ItemKind::TyAlias(..) => {
println!("type alias for {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::OpaqueTy(..) => {
println!("existential type with real type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Enum(..) => {
println!("enum definition of type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Struct(..) => {
println!("struct definition of type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Union(..) => {
println!("union definition of type {:?}", cx.tcx.type_of(did));
},
hir::ItemKind::Trait(..) => {
println!("trait decl");
if cx.tcx.trait_is_auto(did.to_def_id()) {
println!("trait is auto");
} else {
println!("trait is not auto");
}
},
hir::ItemKind::TraitAlias(..) => {
println!("trait alias");
},
hir::ItemKind::Impl(hir::Impl {
of_trait: Some(ref _trait_ref),
..
}) => {
println!("trait impl");
},
hir::ItemKind::Impl(hir::Impl { of_trait: None, .. }) => {
println!("impl");
},
}
}
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
fn print_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, indent: usize) {
let ind = " ".repeat(indent);
println!("{}+", ind);
match pat.kind {
hir::PatKind::Wild => println!("{}Wild", ind),
hir::PatKind::Binding(ref mode, .., ident, ref inner) => {
println!("{}Binding", ind);
println!("{}mode: {:?}", ind, mode);
println!("{}name: {}", ind, ident.name);
if let Some(ref inner) = *inner {
println!("{}inner:", ind);
print_pat(cx, inner, indent + 1);
}
},
hir::PatKind::Or(fields) => {
println!("{}Or", ind);
for field in fields {
print_pat(cx, field, indent + 1);
}
},
hir::PatKind::Struct(ref path, fields, ignore) => {
println!("{}Struct", ind);
println!(
"{}name: {}",
ind,
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
);
println!("{}ignore leftover fields: {}", ind, ignore);
println!("{}fields:", ind);
for field in fields {
println!("{} field name: {}", ind, field.ident.name);
if field.is_shorthand {
println!("{} in shorthand notation", ind);
}
print_pat(cx, &field.pat, indent + 1);
}
},
hir::PatKind::TupleStruct(ref path, fields, opt_dots_position) => {
println!("{}TupleStruct", ind);
println!(
"{}path: {}",
ind,
rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
);
if let Some(dot_position) = opt_dots_position {
println!("{}dot position: {}", ind, dot_position);
}
for field in fields {
print_pat(cx, field, indent + 1);
}
},
hir::PatKind::Path(hir::QPath::Resolved(ref ty, ref path)) => {
println!("{}Resolved Path, {:?}", ind, ty);
println!("{}path: {:?}", ind, path);
},
hir::PatKind::Path(hir::QPath::TypeRelative(ref ty, ref seg)) => {
println!("{}Relative Path, {:?}", ind, ty);
println!("{}seg: {:?}", ind, seg);
},
hir::PatKind::Path(hir::QPath::LangItem(lang_item, ..)) => {
println!("{}Lang Item Path, {:?}", ind, lang_item.name());
},
hir::PatKind::Tuple(pats, opt_dots_position) => {
println!("{}Tuple", ind);
if let Some(dot_position) = opt_dots_position {
println!("{}dot position: {}", ind, dot_position);
}
for field in pats {
print_pat(cx, field, indent + 1);
}
},
hir::PatKind::Box(ref inner) => {
println!("{}Box", ind);
print_pat(cx, inner, indent + 1);
},
hir::PatKind::Ref(ref inner, ref muta) => {
println!("{}Ref", ind);
println!("{}mutability: {:?}", ind, muta);
print_pat(cx, inner, indent + 1);
},
hir::PatKind::Lit(ref e) => {
println!("{}Lit", ind);
print_expr(cx, e, indent + 1);
},
hir::PatKind::Range(ref l, ref r, ref range_end) => {
println!("{}Range", ind);
if let Some(expr) = l {
print_expr(cx, expr, indent + 1);
}
if let Some(expr) = r {
print_expr(cx, expr, indent + 1);
}
match *range_end {
hir::RangeEnd::Included => println!("{} end included", ind),
hir::RangeEnd::Excluded => println!("{} end excluded", ind),
}
},
hir::PatKind::Slice(first_pats, ref range, last_pats) => {
println!("{}Slice [a, b, ..i, y, z]", ind);
println!("[a, b]:");
for pat in first_pats {
print_pat(cx, pat, indent + 1);
}
println!("i:");
if let Some(ref pat) = *range {
print_pat(cx, pat, indent + 1);
}
println!("[y, z]:");
for pat in last_pats {
print_pat(cx, pat, indent + 1);
}
},
}
}
fn print_guard(cx: &LateContext<'_>, guard: &hir::Guard<'_>, indent: usize) {
let ind = " ".repeat(indent);
println!("{}+", ind);
match guard {
hir::Guard::If(expr) => {
println!("{}If", ind);
print_expr(cx, expr, indent + 1);
},
hir::Guard::IfLet(pat, expr) => {
println!("{}IfLet", ind);
print_pat(cx, pat, indent + 1);
print_expr(cx, expr, indent + 1);
},
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,228 +0,0 @@
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)
}

View file

@ -1,181 +0,0 @@
//! 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; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
pub const COPY_NONOVERLAPPING: [&str; 3] = ["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"];

View file

@ -1,86 +0,0 @@
use crate::utils::{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)
}

View file

@ -1,347 +0,0 @@
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())),
}
}

View file

@ -1,683 +0,0 @@
//! Contains utility functions to generate suggestions.
#![deny(clippy::missing_docs_in_private_items)]
use crate::utils::{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());
}
}

View file

@ -1,7 +0,0 @@
#[macro_export]
/// Convenience wrapper around rustc's `Symbol::intern`
macro_rules! sym {
($tt:tt) => {
rustc_span::symbol::Symbol::intern(stringify!($tt))
};
}

View file

@ -1,198 +0,0 @@
use crate::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
}

View file

@ -1,190 +0,0 @@
use crate::utils::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)
}
}