diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /src/tools/clippy/clippy_utils | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_utils')
24 files changed, 11118 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml new file mode 100644 index 000000000..bb443bdc1 --- /dev/null +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "clippy_utils" +version = "0.1.64" +edition = "2021" +publish = false + +[dependencies] +arrayvec = { version = "0.7", default-features = false } +if_chain = "1.0" +rustc-semver = "1.1" + +[features] +deny-warnings = [] +internal = [] + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs new file mode 100644 index 000000000..b22602632 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -0,0 +1,710 @@ +//! Utilities for manipulating and extracting information from `rustc_ast::ast`. +//! +//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s. + +#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] + +use crate::{both, over}; +use rustc_ast::ptr::P; +use rustc_ast::{self as ast, *}; +use rustc_span::symbol::Ident; +use std::mem; + +pub mod ident_iter; +pub use ident_iter::IdentIter; + +pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool { + use BinOpKind::*; + matches!( + kind, + Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr + ) +} + +/// Checks if each element in the first slice is contained within the latter as per `eq_fn`. +pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool { + left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r))) +} + +pub fn eq_id(l: Ident, r: Ident) -> bool { + l.name == r.name +} + +pub fn eq_pat(l: &Pat, r: &Pat) -> bool { + use PatKind::*; + match (&l.kind, &r.kind) { + (Paren(l), _) => eq_pat(l, r), + (_, Paren(r)) => eq_pat(l, r), + (Wild, Wild) | (Rest, Rest) => true, + (Lit(l), Lit(r)) => eq_expr(l, r), + (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)), + (Range(lf, lt, le), Range(rf, rt, re)) => { + eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node) + }, + (Box(l), Box(r)) + | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) + | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), + (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), + (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp), + (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => { + eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)) + }, + (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => { + lr == rr && eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat) + }, + (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: &PatField, r: &PatField) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_pat(&l.pat, &r.pat) + && over(&l.attrs, &r.attrs, eq_attr) +} + +pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool { + l.position == r.position && eq_ty(&l.ty, &r.ty) +} + +pub fn eq_maybe_qself(l: &Option<QSelf>, r: &Option<QSelf>) -> bool { + match (l, r) { + (Some(l), Some(r)) => eq_qself(l, r), + (None, None) => true, + _ => false, + } +} + +pub fn eq_path(l: &Path, r: &Path) -> bool { + over(&l.segments, &r.segments, eq_path_seg) +} + +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, eq_angle_arg), + (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, eq_attr) { + 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, eq_arm), + (Closure(lb, lc, la, lm, lf, le, _), Closure(rb, rc, ra, rm, rf, re, _)) => { + eq_closure_binder(lb, rb) + && lc == rc + && la.is_async() == ra.is_async() + && lm == rm + && eq_fn_decl(lf, rf) + && eq_expr(le, re) + }, + (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, eq_qself) && eq_path(lp, rp), + (MacCall(l), MacCall(r)) => eq_mac_call(l, r), + (Struct(lse), Struct(rse)) => { + eq_maybe_qself(&lse.qself, &rse.qself) + && eq_path(&lse.path, &rse.path) + && eq_struct_rest(&lse.rest, &rse.rest) + && unordered_over(&lse.fields, &rse.fields, eq_field) + }, + _ => false, + } +} + +pub fn eq_field(l: &ExprField, r: &ExprField) -> bool { + l.is_placeholder == r.is_placeholder + && eq_id(l.ident, r.ident) + && eq_expr(&l.expr, &r.expr) + && over(&l.attrs, &r.attrs, eq_attr) +} + +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, eq_attr) +} + +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, eq_stmt) +} + +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_local_kind(&l.kind, &r.kind) + && over(&l.attrs, &r.attrs, eq_attr) + }, + (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, eq_attr) + }, + _ => false, + } +} + +pub fn eq_local_kind(l: &LocalKind, r: &LocalKind) -> bool { + use LocalKind::*; + match (l, r) { + (Decl, Decl) => true, + (Init(l), Init(r)) => eq_expr(l, r), + (InitElse(li, le), InitElse(ri, re)) => eq_expr(li, ri) && eq_block(le, re), + _ => 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, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind) +} + +#[expect(clippy::too_many_lines)] // Just a big match statement +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 ast::Fn { + defaultness: ld, + sig: lf, + generics: lg, + body: lb, + }), + Fn(box ast::Fn { + defaultness: rd, + sig: rf, + generics: rg, + body: rb, + }), + ) => { + eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) + }, + (Mod(lu, lmk), Mod(ru, rmk)) => { + lu == ru + && match (lmk, rmk) { + (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => { + linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind)) + }, + (ModKind::Unloaded, ModKind::Unloaded) => true, + _ => false, + } + }, + (ForeignMod(l), ForeignMod(r)) => { + both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind)) + }, + ( + TyAlias(box ast::TyAlias { + defaultness: ld, + generics: lg, + bounds: lb, + ty: lt, + .. + }), + TyAlias(box ast::TyAlias { + defaultness: rd, + generics: rg, + bounds: rb, + ty: rt, + .. + }), + ) => { + eq_defaultness(*ld, *rd) + && eq_generics(lg, rg) + && over(lb, rb, eq_generic_bound) + && both(lt, rt, |l, r| eq_ty(l, r)) + }, + (Enum(le, lg), Enum(re, rg)) => over(&le.variants, &re.variants, eq_variant) && 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 ast::Trait { + is_auto: la, + unsafety: lu, + generics: lg, + bounds: lb, + items: li, + }), + Trait(box ast::Trait { + is_auto: ra, + unsafety: ru, + generics: rg, + bounds: rb, + items: ri, + }), + ) => { + la == ra + && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No) + && eq_generics(lg, rg) + && over(lb, rb, eq_generic_bound) + && 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, eq_generic_bound), + ( + Impl(box ast::Impl { + unsafety: lu, + polarity: lp, + defaultness: ld, + constness: lc, + generics: lg, + of_trait: lot, + self_ty: lst, + items: li, + }), + Impl(box ast::Impl { + 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 ast::Fn { + defaultness: ld, + sig: lf, + generics: lg, + body: lb, + }), + Fn(box ast::Fn { + defaultness: rd, + sig: rf, + generics: rg, + body: rb, + }), + ) => { + eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) + }, + ( + TyAlias(box ast::TyAlias { + defaultness: ld, + generics: lg, + bounds: lb, + ty: lt, + .. + }), + TyAlias(box ast::TyAlias { + defaultness: rd, + generics: rg, + bounds: rb, + ty: rt, + .. + }), + ) => { + eq_defaultness(*ld, *rd) + && eq_generics(lg, rg) + && over(lb, rb, eq_generic_bound) + && 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 ast::Fn { + defaultness: ld, + sig: lf, + generics: lg, + body: lb, + }), + Fn(box ast::Fn { + defaultness: rd, + sig: rf, + generics: rg, + body: rb, + }), + ) => { + eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r)) + }, + ( + TyAlias(box ast::TyAlias { + defaultness: ld, + generics: lg, + bounds: lb, + ty: lt, + .. + }), + TyAlias(box ast::TyAlias { + defaultness: rd, + generics: rg, + bounds: rb, + ty: rt, + .. + }), + ) => { + eq_defaultness(*ld, *rd) + && eq_generics(lg, rg) + && over(lb, rb, eq_generic_bound) + && 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, eq_attr) + && 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, eq_struct_field), + _ => false, + } +} + +pub fn eq_struct_field(l: &FieldDef, r: &FieldDef) -> bool { + l.is_placeholder == r.is_placeholder + && over(&l.attrs, &r.attrs, eq_attr) + && 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, eq_generic_param) + && 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, eq_generic_bound) + }, + (RegionPredicate(l), RegionPredicate(r)) => { + eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, eq_generic_bound) + }, + (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_anon_const(l: &AnonConst, r: &AnonConst) -> bool { + eq_expr(&l.value, &r.value) +} + +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_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) => 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, eq_attr) + }) +} + +pub fn eq_closure_binder(l: &ClosureBinder, r: &ClosureBinder) -> bool { + match (l, r) { + (ClosureBinder::NotPresent, ClosureBinder::NotPresent) => true, + (ClosureBinder::For { generic_params: lp, .. }, ClosureBinder::For { generic_params: rp, .. }) => { + lp.len() == rp.len() && std::iter::zip(lp.iter(), rp.iter()).all(|(l, r)| eq_generic_param(l, r)) + }, + _ => false, + } +} + +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, eq_generic_param) + && 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, eq_qself) && eq_path(lp, rp), + (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound), + (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound), + (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, eq_generic_bound) + && 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, eq_anon_const), + _ => false, + } + && over(&l.attrs, &r.attrs, eq_attr) +} + +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, + } +} + +fn eq_term(l: &Term, r: &Term) -> bool { + match (l, r) { + (Term::Ty(l), Term::Ty(r)) => eq_ty(l, r), + (Term::Const(l), Term::Const(r)) => eq_anon_const(l, r), + _ => false, + } +} + +pub fn eq_assoc_constraint(l: &AssocConstraint, r: &AssocConstraint) -> bool { + use AssocConstraintKind::*; + eq_id(l.ident, r.ident) + && match (&l.kind, &r.kind) { + (Equality { term: l }, Equality { term: r }) => eq_term(l, r), + (Bound { bounds: l }, Bound { bounds: r }) => over(l, r, eq_generic_bound), + _ => 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(_, MacArgsEq::Ast(le)), Eq(_, MacArgsEq::Ast(re))) => eq_expr(le, re), + (Eq(_, MacArgsEq::Hir(ll)), Eq(_, MacArgsEq::Hir(rl))) => ll.kind == rl.kind, + _ => false, + } +} diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/ident_iter.rs b/src/tools/clippy/clippy_utils/src/ast_utils/ident_iter.rs new file mode 100644 index 000000000..eefcbabd8 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ast_utils/ident_iter.rs @@ -0,0 +1,45 @@ +use core::iter::FusedIterator; +use rustc_ast::visit::{walk_attribute, walk_expr, Visitor}; +use rustc_ast::{Attribute, Expr}; +use rustc_span::symbol::Ident; + +pub struct IdentIter(std::vec::IntoIter<Ident>); + +impl Iterator for IdentIter { + type Item = Ident; + + fn next(&mut self) -> Option<Self::Item> { + self.0.next() + } +} + +impl FusedIterator for IdentIter {} + +impl From<&Expr> for IdentIter { + fn from(expr: &Expr) -> Self { + let mut visitor = IdentCollector::default(); + + walk_expr(&mut visitor, expr); + + IdentIter(visitor.0.into_iter()) + } +} + +impl From<&Attribute> for IdentIter { + fn from(attr: &Attribute) -> Self { + let mut visitor = IdentCollector::default(); + + walk_attribute(&mut visitor, attr); + + IdentIter(visitor.0.into_iter()) + } +} + +#[derive(Default)] +struct IdentCollector(Vec<Ident>); + +impl Visitor<'_> for IdentCollector { + fn visit_ident(&mut self, ident: Ident) { + self.0.push(ident); + } +} diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs new file mode 100644 index 000000000..186bba09d --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/attrs.rs @@ -0,0 +1,159 @@ +use rustc_ast::ast; +use rustc_ast::attr; +use rustc_errors::Applicability; +use rustc_session::Session; +use rustc_span::sym; +use std::str::FromStr; + +/// Deprecation status of attributes known by Clippy. +pub enum DeprecationStatus { + /// Attribute is deprecated + Deprecated, + /// Attribute is deprecated and was replaced by the named attribute + Replaced(&'static str), + None, +} + +#[rustfmt::skip] +pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[ + ("author", DeprecationStatus::None), + ("version", DeprecationStatus::None), + ("cognitive_complexity", DeprecationStatus::None), + ("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")), + ("dump", DeprecationStatus::None), + ("msrv", DeprecationStatus::None), + ("has_significant_drop", 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, + 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)) +} + +/// Return true if the attributes contain `#[doc(hidden)]` +pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool { + attrs + .iter() + .filter(|attr| attr.has_name(sym::doc)) + .filter_map(ast::Attribute::meta_item_list) + .any(|l| attr::list_contains_name(&l, sym::hidden)) +} diff --git a/src/tools/clippy/clippy_utils/src/comparisons.rs b/src/tools/clippy/clippy_utils/src/comparisons.rs new file mode 100644 index 000000000..7a18d5e81 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/comparisons.rs @@ -0,0 +1,36 @@ +//! Utility functions about comparison operators. + +#![deny(clippy::missing_docs_in_private_items)] + +use rustc_hir::{BinOpKind, Expr}; + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +/// Represent a normalized comparison operator. +pub enum Rel { + /// `<` + Lt, + /// `<=` + Le, + /// `==` + Eq, + /// `!=` + Ne, +} + +/// Put the expression in the form `lhs < rhs`, `lhs <= rhs`, `lhs == rhs` or +/// `lhs != rhs`. +pub fn normalize_comparison<'a>( + op: BinOpKind, + lhs: &'a Expr<'a>, + rhs: &'a Expr<'a>, +) -> Option<(Rel, &'a Expr<'a>, &'a Expr<'a>)> { + match op { + BinOpKind::Lt => Some((Rel::Lt, lhs, rhs)), + BinOpKind::Le => Some((Rel::Le, lhs, rhs)), + BinOpKind::Gt => Some((Rel::Lt, rhs, lhs)), + BinOpKind::Ge => Some((Rel::Le, rhs, lhs)), + BinOpKind::Eq => Some((Rel::Eq, rhs, lhs)), + BinOpKind::Ne => Some((Rel::Ne, rhs, lhs)), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs new file mode 100644 index 000000000..351a3f4ae --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -0,0 +1,652 @@ +#![allow(clippy::float_cmp)] + +use crate::{clip, is_direct_expn_of, sext, unsext}; +use if_chain::if_chain; +use rustc_ast::ast::{self, LitFloatType, LitKind}; +use rustc_data_structures::sync::Lrc; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::mir; +use rustc_middle::mir::interpret::Scalar; +use rustc_middle::ty::subst::{Subst, SubstsRef}; +use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt}; +use rustc_middle::{bug, span_bug}; +use rustc_span::symbol::Symbol; +use std::cmp::Ordering::{self, Equal}; +use std::hash::{Hash, Hasher}; +use std::iter; + +/// A `LitKind`-like enum to fold constant `Expr`s into. +#[derive(Debug, Clone)] +pub enum Constant { + /// A `String` (e.g., "abc"). + Str(String), + /// A binary string (e.g., `b"abc"`). + Binary(Lrc<[u8]>), + /// A single `char` (e.g., `'a'`). + Char(char), + /// An integer's bit representation. + Int(u128), + /// An `f32`. + F32(f32), + /// An `f64`. + F64(f64), + /// `true` or `false`. + Bool(bool), + /// An array of constants. + Vec(Vec<Constant>), + /// Also an array, but with only one constant, repeated N times. + Repeat(Box<Constant>, u64), + /// A tuple of constants. + Tuple(Vec<Constant>), + /// A raw pointer. + RawPtr(u128), + /// A reference + Ref(Box<Constant>), + /// A literal with syntax error. + Err(Symbol), +} + +impl PartialEq for Constant { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, + (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, + (&Self::Char(l), &Self::Char(r)) => l == r, + (&Self::Int(l), &Self::Int(r)) => l == r, + (&Self::F64(l), &Self::F64(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + l.to_bits() == r.to_bits() + }, + (&Self::F32(l), &Self::F32(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + f64::from(l).to_bits() == f64::from(r).to_bits() + }, + (&Self::Bool(l), &Self::Bool(r)) => l == r, + (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv, + (&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb, + // TODO: are there inter-type equalities? + _ => false, + } + } +} + +impl Hash for Constant { + fn hash<H>(&self, state: &mut H) + where + H: Hasher, + { + std::mem::discriminant(self).hash(state); + match *self { + Self::Str(ref s) => { + s.hash(state); + }, + Self::Binary(ref b) => { + b.hash(state); + }, + Self::Char(c) => { + c.hash(state); + }, + Self::Int(i) => { + i.hash(state); + }, + Self::F32(f) => { + f64::from(f).to_bits().hash(state); + }, + Self::F64(f) => { + f.to_bits().hash(state); + }, + Self::Bool(b) => { + b.hash(state); + }, + Self::Vec(ref v) | Self::Tuple(ref v) => { + v.hash(state); + }, + Self::Repeat(ref c, l) => { + c.hash(state); + l.hash(state); + }, + Self::RawPtr(u) => { + u.hash(state); + }, + Self::Ref(ref r) => { + r.hash(state); + }, + Self::Err(ref s) => { + s.hash(state); + }, + } + } +} + +impl Constant { + pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option<Ordering> { + match (left, right) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), + (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), + (&Self::Int(l), &Self::Int(r)) => match *cmp_type.kind() { + ty::Int(int_ty) => Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))), + ty::Uint(_) => Some(l.cmp(&r)), + _ => bug!("Not an int type"), + }, + (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), + (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), + (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)), + (&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => iter::zip(l, r) + .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri)) + .find(|r| r.map_or(true, |o| o != Ordering::Equal)) + .unwrap_or_else(|| Some(l.len().cmp(&r.len()))), + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => { + match Self::partial_cmp(tcx, cmp_type, lv, rv) { + Some(Equal) => Some(ls.cmp(rs)), + x => x, + } + }, + (&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb), + // TODO: are there any useful inter-type orderings? + _ => None, + } + } + + /// Returns the integer value or `None` if `self` or `val_type` is not integer type. + pub fn int_value(&self, cx: &LateContext<'_>, val_type: Ty<'_>) -> Option<FullInt> { + if let Constant::Int(const_int) = *self { + match *val_type.kind() { + ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))), + ty::Uint(_) => Some(FullInt::U(const_int)), + _ => None, + } + } else { + None + } + } + + #[must_use] + pub fn peel_refs(mut self) -> Self { + while let Constant::Ref(r) = self { + self = *r; + } + self + } +} + +/// Parses a `LitKind` to a `Constant`. +pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant { + match *lit { + LitKind::Str(ref is, _) => Constant::Str(is.to_string()), + LitKind::Byte(b) => Constant::Int(u128::from(b)), + LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)), + LitKind::Char(c) => Constant::Char(c), + LitKind::Int(n, _) => Constant::Int(n), + LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { + ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), + ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), + }, + LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() { + ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()), + ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()), + _ => bug!(), + }, + LitKind::Bool(b) => Constant::Bool(b), + LitKind::Err(s) => Constant::Err(s), + } +} + +pub fn constant<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option<(Constant, bool)> { + let mut cx = ConstEvalLateContext { + lcx, + typeck_results, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + }; + cx.expr(e).map(|cst| (cst, cx.needed_resolution)) +} + +pub fn constant_simple<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option<Constant> { + constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) }) +} + +pub fn constant_full_int<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option<FullInt> { + constant_simple(lcx, typeck_results, e)?.int_value(lcx, typeck_results.expr_ty(e)) +} + +#[derive(Copy, Clone, Debug, Eq)] +pub enum FullInt { + S(i128), + U(u128), +} + +impl PartialEq for FullInt { + #[must_use] + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +impl PartialOrd for FullInt { + #[must_use] + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for FullInt { + #[must_use] + fn cmp(&self, other: &Self) -> Ordering { + use FullInt::{S, U}; + + fn cmp_s_u(s: i128, u: u128) -> Ordering { + u128::try_from(s).map_or(Ordering::Less, |x| x.cmp(&u)) + } + + match (*self, *other) { + (S(s), S(o)) => s.cmp(&o), + (U(s), U(o)) => s.cmp(&o), + (S(s), U(o)) => cmp_s_u(s, o), + (U(s), S(o)) => cmp_s_u(o, s).reverse(), + } + } +} + +/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`. +pub fn constant_context<'a, 'tcx>( + lcx: &'a LateContext<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, +) -> ConstEvalLateContext<'a, 'tcx> { + ConstEvalLateContext { + lcx, + typeck_results, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + } +} + +pub struct ConstEvalLateContext<'a, 'tcx> { + lcx: &'a LateContext<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, + param_env: ty::ParamEnv<'tcx>, + needed_resolution: bool, + substs: SubstsRef<'tcx>, +} + +impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { + /// Simple constant folding: Insert an expression, get a constant or none. + pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant> { + match e.kind { + ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), + ExprKind::Block(block, _) => self.block(block), + ExprKind::Lit(ref lit) => { + if is_direct_expn_of(e.span, "cfg").is_some() { + None + } else { + Some(lit_to_mir_constant(&lit.node, self.typeck_results.expr_ty_opt(e))) + } + }, + ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec), + ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple), + ExprKind::Repeat(value, _) => { + let n = match self.typeck_results.expr_ty(e).kind() { + ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, + _ => span_bug!(e.span, "typeck error"), + }; + self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) + }, + ExprKind::Unary(op, operand) => self.expr(operand).and_then(|o| match op { + UnOp::Not => self.constant_not(&o, self.typeck_results.expr_ty(e)), + UnOp::Neg => self.constant_negate(&o, self.typeck_results.expr_ty(e)), + UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }), + }), + ExprKind::If(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), + ExprKind::Binary(op, left, right) => self.binop(op, left, right), + ExprKind::Call(callee, args) => { + // We only handle a few const functions for now. + if_chain! { + if args.is_empty(); + if let ExprKind::Path(qpath) = &callee.kind; + let res = self.typeck_results.qpath_res(qpath, callee.hir_id); + if let Some(def_id) = res.opt_def_id(); + let def_path = self.lcx.get_def_path(def_id); + let def_path: Vec<&str> = def_path.iter().take(4).map(Symbol::as_str).collect(); + if let ["core", "num", int_impl, "max_value"] = *def_path; + then { + let value = match int_impl { + "<impl i8>" => i8::MAX as u128, + "<impl i16>" => i16::MAX as u128, + "<impl i32>" => i32::MAX as u128, + "<impl i64>" => i64::MAX as u128, + "<impl i128>" => i128::MAX as u128, + _ => return None, + }; + Some(Constant::Int(value)) + } else { + None + } + } + }, + ExprKind::Index(arr, index) => self.index(arr, index), + ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), + // TODO: add other expressions. + _ => None, + } + } + + #[expect(clippy::cast_possible_wrap)] + fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> { + use self::Constant::{Bool, Int}; + match *o { + Bool(b) => Some(Bool(!b)), + Int(value) => { + let value = !value; + match *ty.kind() { + ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))), + ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))), + _ => None, + } + }, + _ => None, + } + } + + fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> { + use self::Constant::{Int, F32, F64}; + match *o { + Int(value) => { + let ity = match *ty.kind() { + ty::Int(ity) => ity, + _ => return None, + }; + // sign extend + let value = sext(self.lcx.tcx, value, ity); + let value = value.checked_neg()?; + // clear unused bits + Some(Int(unsext(self.lcx.tcx, value, ity))) + }, + F32(f) => Some(F32(-f)), + F64(f) => Some(F64(-f)), + _ => None, + } + } + + /// Create `Some(Vec![..])` of all constants, unless there is any + /// non-constant part. + fn multi(&mut self, vec: &[Expr<'_>]) -> Option<Vec<Constant>> { + vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>() + } + + /// Lookup a possibly constant expression from an `ExprKind::Path`. + fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant> { + let res = self.typeck_results.qpath_res(qpath, id); + match res { + Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { + // Check if this constant is based on `cfg!(..)`, + // which is NOT constant for our purposes. + if let Some(node) = self.lcx.tcx.hir().get_if_local(def_id) && + let Node::Item(&Item { + kind: ItemKind::Const(_, body_id), + .. + }) = node && + let Node::Expr(&Expr { + kind: ExprKind::Lit(_), + span, + .. + }) = self.lcx.tcx.hir().get(body_id.hir_id) && + is_direct_expn_of(span, "cfg").is_some() { + return None; + } + + let substs = self.typeck_results.node_substs(id); + let substs = if self.substs.is_empty() { + substs + } else { + EarlyBinder(substs).subst(self.lcx.tcx, self.substs) + }; + + let result = self + .lcx + .tcx + .const_eval_resolve( + self.param_env, + ty::Unevaluated::new(ty::WithOptConstParam::unknown(def_id), substs), + None, + ) + .ok() + .map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty))?; + let result = miri_to_const(self.lcx.tcx, result); + if result.is_some() { + self.needed_resolution = true; + } + result + }, + // FIXME: cover all usable cases. + _ => None, + } + } + + fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant> { + let lhs = self.expr(lhs); + let index = self.expr(index); + + match (lhs, index) { + (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + }, + (Some(Constant::Vec(vec)), _) => { + if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { + match vec.get(0) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + } + } else { + None + } + }, + _ => None, + } + } + + /// A block can only yield a constant if it only has one constant expression. + fn block(&mut self, block: &Block<'_>) -> Option<Constant> { + if block.stmts.is_empty() { + block.expr.as_ref().and_then(|b| self.expr(b)) + } else { + None + } + } + + fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option<Constant> { + if let Some(Constant::Bool(b)) = self.expr(cond) { + if b { + self.expr(then) + } else { + otherwise.as_ref().and_then(|expr| self.expr(expr)) + } + } else { + None + } + } + + fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option<Constant> { + let l = self.expr(left)?; + let r = self.expr(right); + match (l, r) { + (Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() { + ty::Int(ity) => { + let l = sext(self.lcx.tcx, l, ity); + let r = sext(self.lcx.tcx, r, ity); + let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity)); + match op.node { + BinOpKind::Add => l.checked_add(r).map(zext), + BinOpKind::Sub => l.checked_sub(r).map(zext), + BinOpKind::Mul => l.checked_mul(r).map(zext), + BinOpKind::Div if r != 0 => l.checked_div(r).map(zext), + BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::BitXor => Some(zext(l ^ r)), + BinOpKind::BitOr => Some(zext(l | r)), + BinOpKind::BitAnd => Some(zext(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + } + }, + ty::Uint(_) => match op.node { + BinOpKind::Add => l.checked_add(r).map(Constant::Int), + BinOpKind::Sub => l.checked_sub(r).map(Constant::Int), + BinOpKind::Mul => l.checked_mul(r).map(Constant::Int), + BinOpKind::Div => l.checked_div(r).map(Constant::Int), + BinOpKind::Rem => l.checked_rem(r).map(Constant::Int), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::BitXor => Some(Constant::Int(l ^ r)), + BinOpKind::BitOr => Some(Constant::Int(l | r)), + BinOpKind::BitAnd => Some(Constant::Int(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + _ => None, + }, + (Constant::F32(l), Some(Constant::F32(r))) => match op.node { + BinOpKind::Add => Some(Constant::F32(l + r)), + BinOpKind::Sub => Some(Constant::F32(l - r)), + BinOpKind::Mul => Some(Constant::F32(l * r)), + BinOpKind::Div => Some(Constant::F32(l / r)), + BinOpKind::Rem => Some(Constant::F32(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (Constant::F64(l), Some(Constant::F64(r))) => match op.node { + BinOpKind::Add => Some(Constant::F64(l + r)), + BinOpKind::Sub => Some(Constant::F64(l - r)), + BinOpKind::Mul => Some(Constant::F64(l * r)), + BinOpKind::Div => Some(Constant::F64(l / r)), + BinOpKind::Rem => Some(Constant::F64(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (l, r) => match (op.node, l, r) { + (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)), + (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)), + (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => { + Some(r) + }, + (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)), + (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)), + (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)), + _ => None, + }, + } + } +} + +pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) -> Option<Constant> { + use rustc_middle::mir::interpret::ConstValue; + match result { + mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(int)), _) => { + match result.ty().kind() { + ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), + ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), + ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( + int.try_into().expect("invalid f32 bit representation"), + ))), + ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( + int.try_into().expect("invalid f64 bit representation"), + ))), + ty::RawPtr(type_and_mut) => { + if let ty::Uint(_) = type_and_mut.ty.kind() { + return Some(Constant::RawPtr(int.assert_bits(int.size()))); + } + None + }, + // FIXME: implement other conversions. + _ => None, + } + }, + mir::ConstantKind::Val(ConstValue::Slice { data, start, end }, _) => match result.ty().kind() { + ty::Ref(_, tam, _) => match tam.kind() { + ty::Str => String::from_utf8( + data.inner() + .inspect_with_uninit_and_ptr_outside_interpreter(start..end) + .to_owned(), + ) + .ok() + .map(Constant::Str), + _ => None, + }, + _ => None, + }, + mir::ConstantKind::Val(ConstValue::ByRef { alloc, offset: _ }, _) => match result.ty().kind() { + ty::Array(sub_type, len) => match sub_type.kind() { + ty::Float(FloatTy::F32) => match len.kind().try_to_machine_usize(tcx) { + Some(len) => alloc + .inner() + .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap())) + .to_owned() + .array_chunks::<4>() + .map(|&chunk| Some(Constant::F32(f32::from_le_bytes(chunk)))) + .collect::<Option<Vec<Constant>>>() + .map(Constant::Vec), + _ => None, + }, + ty::Float(FloatTy::F64) => match len.kind().try_to_machine_usize(tcx) { + Some(len) => alloc + .inner() + .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap())) + .to_owned() + .array_chunks::<8>() + .map(|&chunk| Some(Constant::F64(f64::from_le_bytes(chunk)))) + .collect::<Option<Vec<Constant>>>() + .map(Constant::Vec), + _ => None, + }, + // FIXME: implement other array type conversions. + _ => None, + }, + _ => None, + }, + // FIXME: implement other conversions. + _ => None, + } +} diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs new file mode 100644 index 000000000..7f55db3b3 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs @@ -0,0 +1,249 @@ +//! Clippy wrappers around rustc's diagnostic functions. +//! +//! These functions are used by the `INTERNAL_METADATA_COLLECTOR` lint to collect the corresponding +//! lint applicability. Please make sure that you update the `LINT_EMISSION_FUNCTIONS` variable in +//! `clippy_lints::utils::internal_lints::metadata_collector` when a new function is added +//! or renamed. +//! +//! Thank you! +//! ~The `INTERNAL_METADATA_COLLECTOR` lint + +use rustc_errors::{Applicability, Diagnostic, MultiSpan}; +use rustc_hir::HirId; +use rustc_lint::{LateContext, Lint, LintContext}; +use rustc_span::source_map::Span; +use std::env; + +fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) { + if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() { + if let Some(lint) = lint.name_lower().strip_prefix("clippy::") { + 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.rsplit_once('.').unwrap().1) + }), + lint + )); + } + } +} + +/// 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 +/// +/// ```text +/// 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: impl Into<MultiSpan>, + 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 +/// +/// ```text +/// 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<C, S, F>(cx: &C, lint: &'static Lint, sp: S, msg: &str, f: F) +where + C: LintContext, + S: Into<MultiSpan>, + F: FnOnce(&mut Diagnostic), +{ + 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: impl Into<MultiSpan>, + msg: &str, + f: impl FnOnce(&mut Diagnostic), +) { + 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 +/// +/// ```text +/// 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", 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 Diagnostic, help_msg: &str, sugg: I) +where + I: IntoIterator<Item = (Span, String)>, +{ + multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg); +} + +/// Create a suggestion made from several `span → replacement`. +/// +/// rustfix currently doesn't support the automatic application of suggestions with +/// multiple spans. This is tracked in issue [rustfix#141](https://github.com/rust-lang/rustfix/issues/141). +/// Suggestions with multiple spans will be silently ignored. +pub fn multispan_sugg_with_applicability<I>( + diag: &mut Diagnostic, + help_msg: &str, + applicability: Applicability, + sugg: I, +) where + I: IntoIterator<Item = (Span, String)>, +{ + diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability); +} diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs new file mode 100644 index 000000000..730724b95 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs @@ -0,0 +1,234 @@ +//! 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::ty::{all_predicates_of, is_copy}; +use crate::visitors::is_const_evaluatable; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, PredicateKind}; +use rustc_span::{sym, Symbol}; +use std::cmp; +use std::ops; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum EagernessSuggestion { + // The expression is cheap and should be evaluated eagerly + Eager, + // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to + // eager evaluation. + NoChange, + // The expression is likely expensive and should be evaluated lazily. + Lazy, + // The expression cannot be placed into a closure. + ForceNoChange, +} +impl ops::BitOr for EagernessSuggestion { + type Output = Self; + fn bitor(self, rhs: Self) -> Self { + cmp::max(self, rhs) + } +} +impl ops::BitOrAssign for EagernessSuggestion { + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} + +/// Determine the eagerness of the given function call. +fn fn_eagerness<'tcx>( + cx: &LateContext<'tcx>, + fn_id: DefId, + name: Symbol, + args: &'tcx [Expr<'_>], +) -> EagernessSuggestion { + use EagernessSuggestion::{Eager, Lazy, NoChange}; + let name = name.as_str(); + + let ty = match cx.tcx.impl_of_method(fn_id) { + Some(id) => cx.tcx.type_of(id), + None => return Lazy, + }; + + if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 { + if matches!( + cx.tcx.crate_name(fn_id.krate), + sym::std | sym::core | sym::alloc | sym::proc_macro + ) { + Eager + } else { + NoChange + } + } else if let ty::Adt(def, subs) = ty.kind() { + // Types where the only fields are generic types (or references to) with no trait bounds other + // than marker traits. + // Due to the limited operations on these types functions should be fairly cheap. + if def + .variants() + .iter() + .flat_map(|v| v.fields.iter()) + .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_))) + && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() { + PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker, + _ => true, + }) + && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_))) + { + // Limit the function to either `(self) -> bool` or `(&self) -> bool` + match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output { + [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange, + _ => Lazy, + } + } else { + Lazy + } + } else { + Lazy + } +} + +#[expect(clippy::too_many_lines)] +fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + eagerness: EagernessSuggestion, + } + + impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> { + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + use EagernessSuggestion::{ForceNoChange, Lazy, NoChange}; + if self.eagerness == ForceNoChange { + return; + } + match e.kind { + ExprKind::Call( + &Expr { + kind: ExprKind::Path(ref path), + hir_id, + .. + }, + args, + ) => match self.cx.qpath_res(path, hir_id) { + Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (), + Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (), + // No need to walk the arguments here, `is_const_evaluatable` already did + Res::Def(..) if is_const_evaluatable(self.cx, e) => { + self.eagerness |= NoChange; + return; + }, + Res::Def(_, id) => match path { + QPath::Resolved(_, p) => { + self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args); + }, + QPath::TypeRelative(_, name) => { + self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args); + }, + QPath::LangItem(..) => self.eagerness = Lazy, + }, + _ => self.eagerness = Lazy, + }, + // No need to walk the arguments here, `is_const_evaluatable` already did + ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => { + self.eagerness |= NoChange; + return; + }, + ExprKind::MethodCall(name, args, _) => { + self.eagerness |= self + .cx + .typeck_results() + .type_dependent_def_id(e.hir_id) + .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args)); + }, + ExprKind::Index(_, e) => { + let ty = self.cx.typeck_results().expr_ty_adjusted(e); + if is_copy(self.cx, ty) && !ty.is_ref() { + self.eagerness |= NoChange; + } else { + self.eagerness = Lazy; + } + }, + + // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe. + ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (), + ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange, + + ExprKind::Unary(_, e) + if matches!( + self.cx.typeck_results().expr_ty(e).kind(), + ty::Bool | ty::Int(_) | ty::Uint(_), + ) => {}, + ExprKind::Binary(_, lhs, rhs) + if self.cx.typeck_results().expr_ty(lhs).is_primitive() + && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {}, + + // Can't be moved into a closure + ExprKind::Break(..) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::InlineAsm(_) + | ExprKind::Yield(..) + | ExprKind::Err => { + self.eagerness = ForceNoChange; + return; + }, + + // Memory allocation, custom operator, loop, or call to an unknown function + ExprKind::Box(_) + | ExprKind::Unary(..) + | ExprKind::Binary(..) + | ExprKind::Loop(..) + | ExprKind::Call(..) => self.eagerness = Lazy, + + ExprKind::ConstBlock(_) + | ExprKind::Array(_) + | ExprKind::Tup(_) + | ExprKind::Lit(_) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::DropTemps(_) + | ExprKind::Let(..) + | ExprKind::If(..) + | ExprKind::Match(..) + | ExprKind::Closure { .. } + | ExprKind::Field(..) + | ExprKind::Path(_) + | ExprKind::AddrOf(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Block(Block { stmts: [], .. }, _) => (), + + // Assignment might be to a local defined earlier, so don't eagerly evaluate. + // Blocks with multiple statements might be expensive, so don't eagerly evaluate. + // TODO: Actually check if either of these are true here. + ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange, + } + walk_expr(self, e); + } + } + + let mut v = V { + cx, + eagerness: EagernessSuggestion::Eager, + }; + v.visit_expr(e); + v.eagerness +} + +/// Whether the given expression should be changed to evaluate eagerly +pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + expr_eagerness(cx, expr) == EagernessSuggestion::Eager +} + +/// Whether the given expression should be changed to evaluate lazily +pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + expr_eagerness(cx, expr) == EagernessSuggestion::Lazy +} diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs new file mode 100644 index 000000000..4604ae5c2 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/higher.rs @@ -0,0 +1,469 @@ +//! This module contains functions that retrieve specific elements. + +#![deny(clippy::missing_docs_in_private_items)] + +use crate::consts::{constant_simple, Constant}; +use crate::ty::is_type_diagnostic_item; +use crate::{is_expn_of, match_def_path, paths}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_hir as hir; +use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath}; +use rustc_lint::LateContext; +use rustc_span::{sym, symbol, Span}; + +/// 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 struct ForLoop<'tcx> { + /// `for` loop item + pub pat: &'tcx hir::Pat<'tcx>, + /// `IntoIterator` argument + pub arg: &'tcx hir::Expr<'tcx>, + /// `for` loop body + pub body: &'tcx hir::Expr<'tcx>, + /// Compare this against `hir::Destination.target` + pub loop_id: HirId, + /// entire `for` loop span + pub span: Span, +} + +impl<'tcx> ForLoop<'tcx> { + /// Parses a desugared `for` loop + pub fn hir(expr: &Expr<'tcx>) -> Option<Self> { + if_chain! { + if let hir::ExprKind::DropTemps(e) = expr.kind; + if let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind; + if let hir::ExprKind::Call(_, [arg]) = iterexpr.kind; + if let hir::ExprKind::Loop(block, ..) = arm.body.kind; + if let [stmt] = block.stmts; + if let hir::StmtKind::Expr(e) = stmt.kind; + if let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind; + if let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind; + then { + return Some(Self { + pat: field.pat, + arg, + body: some_arm.body, + loop_id: arm.body.hir_id, + span: expr.span.ctxt().outer_expn_data().call_site, + }); + } + } + None + } +} + +/// An `if` expression without `DropTemps` +pub struct If<'hir> { + /// `if` condition + pub cond: &'hir Expr<'hir>, + /// `if` then expression + pub then: &'hir Expr<'hir>, + /// `else` expression + pub r#else: Option<&'hir Expr<'hir>>, +} + +impl<'hir> If<'hir> { + #[inline] + /// Parses an `if` expression + pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { + if let ExprKind::If( + Expr { + kind: ExprKind::DropTemps(cond), + .. + }, + then, + r#else, + ) = expr.kind + { + Some(Self { cond, then, r#else }) + } else { + None + } + } +} + +/// An `if let` expression +pub struct IfLet<'hir> { + /// `if let` pattern + pub let_pat: &'hir Pat<'hir>, + /// `if let` scrutinee + pub let_expr: &'hir Expr<'hir>, + /// `if let` then expression + pub if_then: &'hir Expr<'hir>, + /// `if let` else expression + pub if_else: Option<&'hir Expr<'hir>>, +} + +impl<'hir> IfLet<'hir> { + /// Parses an `if let` expression + pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> { + if let ExprKind::If( + Expr { + kind: + ExprKind::Let(hir::Let { + pat: let_pat, + init: let_expr, + .. + }), + .. + }, + if_then, + if_else, + ) = expr.kind + { + let mut iter = cx.tcx.hir().parent_iter(expr.hir_id); + if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() { + if let Some(( + _, + Node::Expr(Expr { + kind: ExprKind::Loop(_, _, LoopSource::While, _), + .. + }), + )) = iter.next() + { + // while loop desugar + return None; + } + } + return Some(Self { + let_pat, + let_expr, + if_then, + if_else, + }); + } + None + } +} + +/// An `if let` or `match` expression. Useful for lints that trigger on one or the other. +pub enum IfLetOrMatch<'hir> { + /// Any `match` expression + Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource), + /// scrutinee, pattern, then block, else block + IfLet( + &'hir Expr<'hir>, + &'hir Pat<'hir>, + &'hir Expr<'hir>, + Option<&'hir Expr<'hir>>, + ), +} + +impl<'hir> IfLetOrMatch<'hir> { + /// Parses an `if let` or `match` expression + pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> { + match expr.kind { + ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)), + _ => IfLet::hir(cx, expr).map( + |IfLet { + let_expr, + let_pat, + if_then, + if_else, + }| { Self::IfLet(let_expr, let_pat, if_then, if_else) }, + ), + } + } +} + +/// An `if` or `if let` expression +pub struct IfOrIfLet<'hir> { + /// `if` condition that is maybe a `let` expression + pub cond: &'hir Expr<'hir>, + /// `if` then expression + pub then: &'hir Expr<'hir>, + /// `else` expression + pub r#else: Option<&'hir Expr<'hir>>, +} + +impl<'hir> IfOrIfLet<'hir> { + #[inline] + /// Parses an `if` or `if let` expression + pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { + if let ExprKind::If(cond, then, r#else) = expr.kind { + if let ExprKind::DropTemps(new_cond) = cond.kind { + return Some(Self { + cond: new_cond, + r#else, + then, + }); + } + if let ExprKind::Let(..) = cond.kind { + return Some(Self { cond, then, r#else }); + } + } + None + } +} + +/// 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, +} + +impl<'a> Range<'a> { + /// Higher a `hir` range to something similar to `ast::ExprKind::Range`. + pub fn hir(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::ExprField<'_>]) -> 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(path, 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(path, 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, + } + } +} + +/// 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>]), +} + +impl<'a> VecArgs<'a> { + /// Returns the arguments of the `vec!` macro if this expression was expanded + /// from `vec!`. + pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> { + if_chain! { + if let hir::ExprKind::Call(fun, 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(boxed) = args[0].kind; + if let hir::ExprKind::Array(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 + } +} + +/// A desugared `while` loop +pub struct While<'hir> { + /// `while` loop condition + pub condition: &'hir Expr<'hir>, + /// `while` loop body + pub body: &'hir Expr<'hir>, +} + +impl<'hir> While<'hir> { + #[inline] + /// Parses a desugared `while` loop + pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { + if let ExprKind::Loop( + Block { + expr: + Some(Expr { + kind: + ExprKind::If( + Expr { + kind: ExprKind::DropTemps(condition), + .. + }, + body, + _, + ), + .. + }), + .. + }, + _, + LoopSource::While, + _, + ) = expr.kind + { + return Some(Self { condition, body }); + } + None + } +} + +/// A desugared `while let` loop +pub struct WhileLet<'hir> { + /// `while let` loop item pattern + pub let_pat: &'hir Pat<'hir>, + /// `while let` loop scrutinee + pub let_expr: &'hir Expr<'hir>, + /// `while let` loop body + pub if_then: &'hir Expr<'hir>, +} + +impl<'hir> WhileLet<'hir> { + #[inline] + /// Parses a desugared `while let` loop + pub const fn hir(expr: &Expr<'hir>) -> Option<Self> { + if let ExprKind::Loop( + Block { + expr: + Some(Expr { + kind: + ExprKind::If( + Expr { + kind: + ExprKind::Let(hir::Let { + pat: let_pat, + init: let_expr, + .. + }), + .. + }, + if_then, + _, + ), + .. + }), + .. + }, + _, + LoopSource::While, + _, + ) = expr.kind + { + return Some(Self { + let_pat, + let_expr, + if_then, + }); + } + None + } +} + +/// 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, + } +} + +/// A parsed `Vec` initialization expression +#[derive(Clone, Copy)] +pub enum VecInitKind { + /// `Vec::new()` + New, + /// `Vec::default()` or `Default::default()` + Default, + /// `Vec::with_capacity(123)` + WithConstCapacity(u128), + /// `Vec::with_capacity(slice.len())` + WithExprCapacity(HirId), +} + +/// Checks if given expression is an initialization of `Vec` and returns its kind. +pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> { + if let ExprKind::Call(func, args) = expr.kind { + match func.kind { + ExprKind::Path(QPath::TypeRelative(ty, name)) + if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) => + { + if name.ident.name == sym::new { + return Some(VecInitKind::New); + } else if name.ident.name == symbol::kw::Default { + return Some(VecInitKind::Default); + } else if name.ident.name.as_str() == "with_capacity" { + let arg = args.get(0)?; + return match constant_simple(cx, cx.typeck_results(), arg) { + Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)), + _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)), + }; + }; + }, + ExprKind::Path(QPath::Resolved(_, path)) + if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) => + { + return Some(VecInitKind::Default); + }, + _ => (), + } + } + None +} diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs new file mode 100644 index 000000000..1834e2a2d --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -0,0 +1,1031 @@ +use crate::consts::constant_simple; +use crate::macros::macro_backtrace; +use crate::source::snippet_opt; +use rustc_ast::ast::InlineAsmTemplatePiece; +use rustc_data_structures::fx::FxHasher; +use rustc_hir::def::Res; +use rustc_hir::HirIdMap; +use rustc_hir::{ + ArrayLen, BinOpKind, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard, + HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath, + Stmt, StmtKind, Ty, TyKind, TypeBinding, +}; +use rustc_lexer::{tokenize, TokenKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::TypeckResults; +use rustc_span::{sym, Symbol}; +use std::hash::{Hash, Hasher}; + +/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but +/// other conditions would make them equal. +type SpanlessEqCallback<'a> = dyn FnMut(&Expr<'_>, &Expr<'_>) -> bool + 'a; + +/// 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>, &'tcx TypeckResults<'tcx>)>, + allow_side_effects: bool, + expr_fallback: Option<Box<SpanlessEqCallback<'a>>>, +} + +impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { + pub fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + maybe_typeck_results: cx.maybe_typeck_results().map(|x| (x, x)), + allow_side_effects: true, + expr_fallback: None, + } + } + + /// Consider expressions containing potential side effects as not equal. + #[must_use] + pub fn deny_side_effects(self) -> Self { + Self { + allow_side_effects: false, + ..self + } + } + + #[must_use] + 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`. + pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> { + HirEqInterExpr { + inner: self, + locals: HirIdMap::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(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool { + self.inter_expr().eq_path(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 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`. + pub locals: HirIdMap<HirId>, +} + +impl HirEqInterExpr<'_, '_, '_> { + pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { + match (&left.kind, &right.kind) { + (&StmtKind::Local(l), &StmtKind::Local(r)) => { + // This additional check ensures that the type of the locals are equivalent even if the init + // expression or type have some inferred parts. + if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results { + let l_ty = typeck_lhs.pat_ty(l.pat); + let r_ty = typeck_rhs.pat_ty(r.pat); + if l_ty != r_ty { + return false; + } + } + + // eq_pat adds the HirIds to the locals map. We therefor call it last to make sure that + // these only get added if the init and type is equal. + both(&l.init, &r.init, |l, r| self.eq_expr(l, r)) + && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) + && both(&l.els, &r.els, |l, r| self.eq_block(l, r)) + && self.eq_pat(l.pat, r.pat) + }, + (&StmtKind::Expr(l), &StmtKind::Expr(r)) | (&StmtKind::Semi(l), &StmtKind::Semi(r)) => self.eq_expr(l, r), + _ => false, + } + } + + /// Checks whether two blocks are the same. + fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { + match (left.stmts, left.expr, right.stmts, right.expr) { + ([], None, [], None) => { + // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro + // expanded to nothing, or the cfg attribute was used. + let (left, right) = match ( + snippet_opt(self.inner.cx, left.span), + snippet_opt(self.inner.cx, right.span), + ) { + (Some(left), Some(right)) => (left, right), + _ => return true, + }; + let mut left_pos = 0; + let left = tokenize(&left) + .map(|t| { + let end = left_pos + t.len as usize; + let s = &left[left_pos..end]; + left_pos = end; + (t, s) + }) + .filter(|(t, _)| { + !matches!( + t.kind, + TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace + ) + }) + .map(|(_, s)| s); + let mut right_pos = 0; + let right = tokenize(&right) + .map(|t| { + let end = right_pos + t.len as usize; + let s = &right[right_pos..end]; + right_pos = end; + (t, s) + }) + .filter(|(t, _)| { + !matches!( + t.kind, + TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace + ) + }) + .map(|(_, s)| s); + left.eq(right) + }, + _ => { + over(left.stmts, right.stmts, |l, r| self.eq_stmt(l, r)) + && both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r)) + }, + } + } + + fn should_ignore(&mut self, expr: &Expr<'_>) -> bool { + macro_backtrace(expr.span).last().map_or(false, |macro_call| { + matches!( + &self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id), + Some(sym::todo_macro | sym::unimplemented_macro) + ) + }) + } + + pub fn eq_array_length(&mut self, left: ArrayLen, right: ArrayLen) -> bool { + match (left, right) { + (ArrayLen::Infer(..), ArrayLen::Infer(..)) => true, + (ArrayLen::Body(l_ct), ArrayLen::Body(r_ct)) => self.eq_body(l_ct.body, r_ct.body), + (_, _) => false, + } + } + + pub fn eq_body(&mut self, left: BodyId, right: BodyId) -> bool { + // swap out TypeckResults when hashing a body + let old_maybe_typeck_results = self.inner.maybe_typeck_results.replace(( + self.inner.cx.tcx.typeck_body(left), + self.inner.cx.tcx.typeck_body(right), + )); + let res = self.eq_expr( + &self.inner.cx.tcx.hir().body(left).value, + &self.inner.cx.tcx.hir().body(right).value, + ); + self.inner.maybe_typeck_results = old_maybe_typeck_results; + res + } + + #[expect(clippy::similar_names)] + pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool { + if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() { + return false; + } + + if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results { + if let (Some(l), Some(r)) = ( + constant_simple(self.inner.cx, typeck_lhs, left), + constant_simple(self.inner.cx, typeck_rhs, right), + ) { + if l == r { + return true; + } + } + } + + let is_eq = match ( + reduce_exprkind(self.inner.cx, &left.kind), + reduce_exprkind(self.inner.cx, &right.kind), + ) { + (&ExprKind::AddrOf(lb, l_mut, le), &ExprKind::AddrOf(rb, r_mut, 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(ll, lr, _), &ExprKind::Assign(rl, rr, _)) => { + self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + }, + (&ExprKind::AssignOp(ref lo, ll, lr), &ExprKind::AssignOp(ref ro, rl, rr)) => { + self.inner.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr) + }, + (&ExprKind::Block(l, _), &ExprKind::Block(r, _)) => self.eq_block(l, r), + (&ExprKind::Binary(l_op, ll, lr), &ExprKind::Binary(r_op, rl, 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(l), &ExprKind::Box(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(lx, lt), &ExprKind::Cast(rx, rt)) | (&ExprKind::Type(lx, lt), &ExprKind::Type(rx, rt)) => { + self.eq_expr(lx, rx) && self.eq_ty(lt, rt) + }, + (&ExprKind::Field(l_f_exp, ref l_f_ident), &ExprKind::Field(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(la, li), &ExprKind::Index(ra, ri)) => self.eq_expr(la, ra) && self.eq_expr(li, ri), + (&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => { + self.eq_expr(lc, rc) && self.eq_expr(lt, rt) && both(le, re, |l, r| self.eq_expr(l, r)) + }, + (&ExprKind::Let(l), &ExprKind::Let(r)) => { + self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init) + }, + (&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node, + (&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(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(le, la, ref ls), &ExprKind::Match(re, 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(le, ll), &ExprKind::Repeat(re, rl)) => { + self.eq_expr(le, re) && self.eq_array_length(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(l_path, lf, ref lo), &ExprKind::Struct(r_path, 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_expr_field(l, r)) + }, + (&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup), + (&ExprKind::Unary(l_op, le), &ExprKind::Unary(r_op, re)) => l_op == r_op && self.eq_expr(le, re), + (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r), + (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re), + _ => false, + }; + (is_eq && (!self.should_ignore(left) || !self.should_ignore(right))) + || 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_expr_field(&mut self, left: &ExprField<'_>, right: &ExprField<'_>) -> 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(l), Guard::IfLet(r)) => { + self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init) + }, + _ => false, + } + } + + fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool { + match (left, right) { + (GenericArg::Const(l), GenericArg::Const(r)) => self.eq_body(l.value.body, r.value.body), + (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), + (GenericArg::Infer(l_inf), GenericArg::Infer(r_inf)) => self.eq_ty(&l_inf.to_ty(), &r_inf.to_ty()), + _ => false, + } + } + + fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool { + left.name == right.name + } + + fn eq_pat_field(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool { + let (PatField { ident: li, pat: lp, .. }, PatField { 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(l), &PatKind::Box(r)) => self.eq_pat(l, r), + (&PatKind::Struct(ref lp, la, ..), &PatKind::Struct(ref rp, ra, ..)) => { + self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat_field(l, r)) + }, + (&PatKind::TupleStruct(ref lp, la, ls), &PatKind::TupleStruct(ref rp, 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(l), &PatKind::Lit(r)) => self.eq_expr(l, r), + (&PatKind::Tuple(l, ls), &PatKind::Tuple(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(le, ref lm), &PatKind::Ref(re, ref rm)) => lm == rm && self.eq_pat(le, re), + (&PatKind::Slice(ls, ref li, le), &PatKind::Slice(rs, ref ri, 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, + } + } + + #[expect(clippy::similar_names)] + fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool { + match (left, right) { + (&QPath::Resolved(ref lty, lpath), &QPath::Resolved(ref rty, rpath)) => { + both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath) + }, + (&QPath::TypeRelative(lty, lseg), &QPath::TypeRelative(rty, 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, + } + } + + pub 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)) + } + + pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool { + match (&left.kind, &right.kind) { + (&TyKind::Slice(l_vec), &TyKind::Slice(r_vec)) => self.eq_ty(l_vec, r_vec), + (&TyKind::Array(lt, ll), &TyKind::Array(rt, rl)) => self.eq_ty(lt, rt) && self.eq_array_length(ll, rl), + (&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(l), &TyKind::Tup(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>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'hir ExprKind<'hir> { + if let ExprKind::Block(block, _) = kind { + match (block.stmts, block.expr) { + // From an `if let` expression without an `else` block. The arm for the implicit wild pattern is an empty + // block with an empty span. + ([], None) if block.span.is_empty() => &ExprKind::Tup(&[]), + // `{}` => `()` + ([], None) => match snippet_opt(cx, block.span) { + // Don't reduce if there are any tokens contained in the braces + Some(snip) + if tokenize(&snip) + .map(|t| t.kind) + .filter(|t| { + !matches!( + t, + TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace + ) + }) + .ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) => + { + kind + }, + _ => &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)) +} + +/// Counts how many elements of the slices are equal as per `eq_fn`. +pub fn count_eq<X: Sized>( + left: &mut dyn Iterator<Item = X>, + right: &mut dyn Iterator<Item = X>, + mut eq_fn: impl FnMut(&X, &X) -> bool, +) -> usize { + left.zip(right).take_while(|(l, r)| eq_fn(l, r)).count() +} + +/// 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: FxHasher, +} + +impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { + pub fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + maybe_typeck_results: cx.maybe_typeck_results(), + s: FxHasher::default(), + } + } + + 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(e) = b.expr { + self.hash_expr(e); + } + + std::mem::discriminant(&b.rules).hash(&mut self.s); + } + + #[expect(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.hash(&mut self.s); + if simple_const.is_some() { + return; + } + + std::mem::discriminant(&e.kind).hash(&mut self.s); + + match e.kind { + ExprKind::AddrOf(kind, m, e) => { + std::mem::discriminant(&kind).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(l, r, _) => { + self.hash_expr(l); + self.hash_expr(r); + }, + ExprKind::AssignOp(ref o, l, r) => { + std::mem::discriminant(&o.node).hash(&mut self.s); + self.hash_expr(l); + self.hash_expr(r); + }, + ExprKind::Block(b, _) => { + self.hash_block(b); + }, + ExprKind::Binary(op, l, r) => { + std::mem::discriminant(&op.node).hash(&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(j) = *j { + self.hash_expr(j); + } + }, + ExprKind::Box(e) | ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => { + self.hash_expr(e); + }, + ExprKind::Call(fun, args) => { + self.hash_expr(fun); + self.hash_exprs(args); + }, + ExprKind::Cast(e, ty) | ExprKind::Type(e, ty) => { + self.hash_expr(e); + self.hash_ty(ty); + }, + ExprKind::Closure(&Closure { + capture_clause, body, .. + }) => { + std::mem::discriminant(&capture_clause).hash(&mut self.s); + // closures inherit TypeckResults + self.hash_expr(&self.cx.tcx.hir().body(body).value); + }, + ExprKind::Field(e, ref f) => { + self.hash_expr(e); + self.hash_name(f.name); + }, + ExprKind::Index(a, i) => { + self.hash_expr(a); + self.hash_expr(i); + }, + ExprKind::InlineAsm(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 { anon_const } | InlineAsmOperand::SymFn { anon_const } => { + self.hash_body(anon_const.body); + }, + InlineAsmOperand::SymStatic { path, def_id: _ } => self.hash_qpath(path), + } + } + }, + ExprKind::Let(Let { pat, init, ty, .. }) => { + self.hash_expr(init); + if let Some(ty) = ty { + self.hash_ty(ty); + } + self.hash_pat(pat); + }, + ExprKind::Err => {}, + ExprKind::Lit(ref l) => { + l.node.hash(&mut self.s); + }, + ExprKind::Loop(b, ref i, ..) => { + self.hash_block(b); + if let Some(i) = *i { + self.hash_name(i.ident.name); + } + }, + ExprKind::If(cond, then, ref else_opt) => { + self.hash_expr(cond); + self.hash_expr(then); + if let Some(e) = *else_opt { + self.hash_expr(e); + } + }, + ExprKind::Match(e, arms, ref s) => { + self.hash_expr(e); + + for arm in arms { + self.hash_pat(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(path, 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(e, len) => { + self.hash_expr(e); + self.hash_array_length(len); + }, + ExprKind::Ret(ref e) => { + if let Some(e) = *e { + self.hash_expr(e); + } + }, + ExprKind::Path(ref qpath) => { + self.hash_qpath(qpath); + }, + ExprKind::Struct(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(e) = *expr { + self.hash_expr(e); + } + }, + ExprKind::Tup(tup) => { + self.hash_exprs(tup); + }, + ExprKind::Array(v) => { + self.hash_exprs(v); + }, + ExprKind::Unary(lop, le) => { + std::mem::discriminant(&lop).hash(&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.hash(&mut self.s); + } + + pub fn hash_qpath(&mut self, p: &QPath<'_>) { + match *p { + QPath::Resolved(_, path) => { + self.hash_path(path); + }, + QPath::TypeRelative(_, path) => { + self.hash_name(path.ident.name); + }, + QPath::LangItem(lang_item, ..) => { + std::mem::discriminant(&lang_item).hash(&mut self.s); + }, + } + // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s); + } + + pub fn hash_pat(&mut self, pat: &Pat<'_>) { + std::mem::discriminant(&pat.kind).hash(&mut self.s); + match pat.kind { + PatKind::Binding(ann, _, _, pat) => { + std::mem::discriminant(&ann).hash(&mut self.s); + if let Some(pat) = pat { + self.hash_pat(pat); + } + }, + PatKind::Box(pat) => self.hash_pat(pat), + PatKind::Lit(expr) => self.hash_expr(expr), + PatKind::Or(pats) => { + for pat in pats { + self.hash_pat(pat); + } + }, + PatKind::Path(ref qpath) => self.hash_qpath(qpath), + PatKind::Range(s, e, i) => { + if let Some(s) = s { + self.hash_expr(s); + } + if let Some(e) = e { + self.hash_expr(e); + } + std::mem::discriminant(&i).hash(&mut self.s); + }, + PatKind::Ref(pat, mu) => { + self.hash_pat(pat); + std::mem::discriminant(&mu).hash(&mut self.s); + }, + PatKind::Slice(l, m, r) => { + for pat in l { + self.hash_pat(pat); + } + if let Some(pat) = m { + self.hash_pat(pat); + } + for pat in r { + self.hash_pat(pat); + } + }, + PatKind::Struct(ref qpath, fields, e) => { + self.hash_qpath(qpath); + for f in fields { + self.hash_name(f.ident.name); + self.hash_pat(f.pat); + } + e.hash(&mut self.s); + }, + PatKind::Tuple(pats, e) => { + for pat in pats { + self.hash_pat(pat); + } + e.hash(&mut self.s); + }, + PatKind::TupleStruct(ref qpath, pats, e) => { + self.hash_qpath(qpath); + for pat in pats { + self.hash_pat(pat); + } + e.hash(&mut self.s); + }, + PatKind::Wild => {}, + } + } + + pub fn hash_path(&mut self, path: &Path<'_>) { + match path.res { + // constant hash since equality is dependant on inter-expression context + // e.g. The expressions `if let Some(x) = foo() {}` and `if let Some(y) = foo() {}` are considered equal + // even though the binding names are different and they have different `HirId`s. + Res::Local(_) => 1_usize.hash(&mut self.s), + _ => { + for seg in path.segments { + self.hash_name(seg.ident.name); + self.hash_generic_args(seg.args().args); + } + }, + } + } + + pub fn hash_stmt(&mut self, b: &Stmt<'_>) { + std::mem::discriminant(&b.kind).hash(&mut self.s); + + match &b.kind { + StmtKind::Local(local) => { + self.hash_pat(local.pat); + if let Some(init) = local.init { + self.hash_expr(init); + } + if let Some(els) = local.els { + self.hash_block(els); + } + }, + StmtKind::Item(..) => {}, + StmtKind::Expr(expr) | StmtKind::Semi(expr) => { + self.hash_expr(expr); + }, + } + } + + pub fn hash_guard(&mut self, g: &Guard<'_>) { + match g { + Guard::If(expr) | Guard::IfLet(Let { init: 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(param_id, ref name) = lifetime.name { + std::mem::discriminant(name).hash(&mut self.s); + param_id.hash(&mut self.s); + match name { + ParamName::Plain(ref ident) => { + ident.name.hash(&mut self.s); + }, + ParamName::Fresh | ParamName::Error => {}, + } + } + } + + pub fn hash_ty(&mut self, ty: &Ty<'_>) { + std::mem::discriminant(&ty.kind).hash(&mut self.s); + self.hash_tykind(&ty.kind); + } + + pub fn hash_tykind(&mut self, ty: &TyKind<'_>) { + match ty { + TyKind::Slice(ty) => { + self.hash_ty(ty); + }, + &TyKind::Array(ty, len) => { + self.hash_ty(ty); + self.hash_array_length(len); + }, + TyKind::Ptr(ref mut_ty) => { + self.hash_ty(mut_ty.ty); + mut_ty.mutbl.hash(&mut self.s); + }, + TyKind::Rptr(lifetime, ref 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); + } + std::mem::discriminant(&bfn.decl.output).hash(&mut self.s); + match bfn.decl.output { + FnRetTy::DefaultReturn(_) => {}, + FnRetTy::Return(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(ref qpath) => self.hash_qpath(qpath), + 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_array_length(&mut self, length: ArrayLen) { + match length { + ArrayLen::Infer(..) => {}, + ArrayLen::Body(anon_const) => self.hash_body(anon_const.body), + } + } + + 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(l) => self.hash_lifetime(l), + GenericArg::Type(ref ty) => self.hash_ty(ty), + GenericArg::Const(ref ca) => self.hash_body(ca.value.body), + GenericArg::Infer(ref inf) => self.hash_ty(&inf.to_ty()), + } + } + } +} + +pub fn hash_stmt(cx: &LateContext<'_>, s: &Stmt<'_>) -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_stmt(s); + h.finish() +} + +pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_expr(e); + h.finish() +} diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs new file mode 100644 index 000000000..8322df862 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -0,0 +1,2304 @@ +#![feature(array_chunks)] +#![feature(box_patterns)] +#![feature(control_flow_enum)] +#![feature(let_else)] +#![feature(let_chains)] +#![feature(lint_reasons)] +#![feature(once_cell)] +#![feature(rustc_private)] +#![recursion_limit = "512"] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] +// warn on the same lints as `clippy_lints` +#![warn(trivial_casts, trivial_numeric_casts)] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] +// warn on rustc internal lints +#![warn(rustc::internal)] + +// FIXME: switch to something more ergonomic here, once available. +// (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_ast; +extern crate rustc_ast_pretty; +extern crate rustc_attr; +extern crate rustc_data_structures; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_infer; +extern crate rustc_lexer; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; +extern crate rustc_trait_selection; +extern crate rustc_typeck; + +#[macro_use] +pub mod sym_helper; + +pub mod ast_utils; +pub mod attrs; +pub mod comparisons; +pub mod consts; +pub mod diagnostics; +pub mod eager_or_lazy; +pub mod higher; +mod hir_utils; +pub mod macros; +pub mod msrvs; +pub mod numeric_literal; +pub mod paths; +pub mod ptr; +pub mod qualify_min_const_fn; +pub mod source; +pub mod str_utils; +pub mod sugg; +pub mod ty; +pub mod usage; +pub mod visitors; + +pub use self::attrs::*; +pub use self::hir_utils::{ + both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash, +}; + +use std::collections::hash_map::Entry; +use std::hash::BuildHasherDefault; +use std::sync::OnceLock; +use std::sync::{Mutex, MutexGuard}; + +use if_chain::if_chain; +use rustc_ast::ast::{self, LitKind}; +use rustc_ast::Attribute; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::unhash::UnhashMap; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, CRATE_DEF_ID}; +use rustc_hir::hir_id::{HirIdMap, HirIdSet}; +use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; +use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; +use rustc_hir::{ + def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness, Destination, Expr, + ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, + Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, + TraitRef, TyKind, UnOp, +}; +use rustc_lint::{LateContext, Level, Lint, LintContext}; +use rustc_middle::hir::place::PlaceBase; +use rustc_middle::ty as rustc_ty; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; +use rustc_middle::ty::binding::BindingMode; +use rustc_middle::ty::fast_reject::SimplifiedTypeGen::{ + ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType, + PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType, +}; +use rustc_middle::ty::{ + layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture, +}; +use rustc_middle::ty::{FloatTy, IntTy, UintTy}; +use rustc_semver::RustcVersion; +use rustc_session::Session; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::source_map::original_sp; +use rustc_span::sym; +use rustc_span::symbol::{kw, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use rustc_target::abi::Integer; + +use crate::consts::{constant, Constant}; +use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param}; +use crate::visitors::expr_visitor_no_bodies; + +pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> { + if let Ok(version) = RustcVersion::parse(msrv) { + return Some(version); + } else if let Some(sess) = sess { + if let Some(span) = span { + sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv)); + } + } + None +} + +pub fn meets_msrv(msrv: Option<RustcVersion>, lint_msrv: RustcVersion) -> bool { + msrv.map_or(true, |msrv| msrv.meets(lint_msrv)) +} + +#[macro_export] +macro_rules! extract_msrv_attr { + ($context:ident) => { + fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) { + let sess = rustc_lint::LintContext::sess(cx); + match $crate::get_unique_inner_attr(sess, attrs, "msrv") { + Some(msrv_attr) => { + if let Some(msrv) = msrv_attr.value_str() { + self.msrv = $crate::parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span)); + } else { + sess.span_err(msrv_attr.span, "bad clippy attribute"); + } + }, + _ => (), + } + } + }; +} + +/// If the given expression is a local binding, find the initializer expression. +/// If that initializer expression is another local binding, find its initializer again. +/// This process repeats as long as possible (but usually no more than once). Initializer +/// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`] +/// instead. +/// +/// Examples: +/// ``` +/// let abc = 1; +/// // ^ output +/// let def = abc; +/// dbg!(def); +/// // ^^^ input +/// +/// // or... +/// let abc = 1; +/// let def = abc + 2; +/// // ^^^^^^^ output +/// dbg!(def); +/// // ^^^ input +/// ``` +pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> { + while let Some(init) = path_to_local(expr) + .and_then(|id| find_binding_init(cx, id)) + .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty()) + { + expr = init; + } + expr +} + +/// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable. +/// By only considering immutable bindings, we guarantee that the returned expression represents the +/// value of the binding wherever it is referenced. +/// +/// Example: For `let x = 1`, if the `HirId` of `x` is provided, the `Expr` `1` is returned. +/// Note: If you have an expression that references a binding `x`, use `path_to_local` to get the +/// canonical binding `HirId`. +pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> { + let hir = cx.tcx.hir(); + if_chain! { + if let Some(Node::Pat(pat)) = hir.find(hir_id); + if matches!(pat.kind, PatKind::Binding(BindingAnnotation::Unannotated, ..)); + let parent = hir.get_parent_node(hir_id); + if let Some(Node::Local(local)) = hir.find(parent); + then { + return local.init; + } + } + None +} + +/// Returns `true` if the given `NodeId` is inside a constant context +/// +/// # Example +/// +/// ```rust,ignore +/// if in_constant(cx, expr.hir_id) { +/// // Do something +/// } +/// ``` +pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool { + let parent_id = cx.tcx.hir().get_parent_item(id); + match cx.tcx.hir().get_by_def_id(parent_id) { + Node::Item(&Item { + kind: ItemKind::Const(..) | ItemKind::Static(..), + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + .. + }) + | Node::AnonConst(_) => true, + Node::Item(&Item { + kind: ItemKind::Fn(ref sig, ..), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(ref sig, _), + .. + }) => sig.header.constness == Constness::Const, + _ => false, + } +} + +/// Checks if a `QPath` resolves to a constructor of a `LangItem`. +/// For example, use this to check whether a function call or a pattern is `Some(..)`. +pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem) -> bool { + if let QPath::Resolved(_, path) = qpath { + if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res { + if let Ok(item_id) = cx.tcx.lang_items().require(lang_item) { + return cx.tcx.parent(ctor_id) == item_id; + } + } + } + false +} + +pub fn is_unit_expr(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block( + Block { + stmts: [], + expr: None, + .. + }, + _ + ) | ExprKind::Tup([]) + ) +} + +/// Checks if given pattern is a wildcard (`_`) +pub fn is_wild(pat: &Pat<'_>) -> bool { + matches!(pat.kind, PatKind::Wild) +} + +/// Checks if the method call given in `expr` belongs to the given trait. +/// This is a deprecated function, consider using [`is_trait_method`]. +pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool { + let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + let trt_id = cx.tcx.trait_of_item(def_id); + trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path)) +} + +/// Checks if a method is defined in an impl of a diagnostic item +pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { + if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { + if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() { + return cx.tcx.is_diagnostic_item(diag_item, adt.did()); + } + } + false +} + +/// Checks if a method is in a diagnostic item trait +pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { + if let Some(trait_did) = cx.tcx.trait_of_item(def_id) { + return cx.tcx.is_diagnostic_item(diag_item, trait_did); + } + false +} + +/// Checks if the method call given in `expr` belongs to the given trait. +pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { + cx.typeck_results() + .type_dependent_def_id(expr.hir_id) + .map_or(false, |did| is_diag_trait_item(cx, did, diag_item)) +} + +/// Checks if the given expression is a path referring an item on the trait +/// that is marked with the given diagnostic item. +/// +/// For checking method call expressions instead of path expressions, use +/// [`is_trait_method`]. +/// +/// For example, this can be used to find if an expression like `u64::default` +/// refers to an item of the trait `Default`, which is associated with the +/// `diag_item` of `sym::Default`. +pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { + if let hir::ExprKind::Path(ref qpath) = expr.kind { + cx.qpath_res(qpath, expr.hir_id) + .opt_def_id() + .map_or(false, |def_id| is_diag_trait_item(cx, def_id, diag_item)) + } else { + false + } +} + +pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { + match *path { + QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"), + QPath::TypeRelative(_, seg) => seg, + QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"), + } +} + +pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> { + last_path_segment(qpath) + .args + .map_or(&[][..], |a| a.args) + .iter() + .filter_map(|a| match a { + hir::GenericArg::Type(ty) => Some(ty), + _ => None, + }) +} + +/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the +/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from +/// `QPath::Resolved.1.res.opt_def_id()`. +/// +/// Matches a `QPath` against a slice of segment string literals. +/// +/// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a +/// `rustc_hir::QPath`. +/// +/// # Examples +/// ```rust,ignore +/// match_qpath(path, &["std", "rt", "begin_unwind"]) +/// ``` +pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { + match *path { + QPath::Resolved(_, path) => match_path(path, segments), + QPath::TypeRelative(ty, segment) => match ty.kind { + TyKind::Path(ref inner_path) => { + if let [prefix @ .., end] = segments { + if match_qpath(inner_path, prefix) { + return segment.ident.name.as_str() == *end; + } + } + false + }, + _ => false, + }, + QPath::LangItem(..) => false, + } +} + +/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path. +/// +/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item. +pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool { + path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments)) +} + +/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given +/// diagnostic item. +pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { + path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) +} + +/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the +/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from +/// `QPath::Resolved.1.res.opt_def_id()`. +/// +/// Matches a `Path` against a slice of segment string literals. +/// +/// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a +/// `rustc_hir::Path`. +/// +/// # Examples +/// +/// ```rust,ignore +/// if match_path(&trait_ref.path, &paths::HASH) { +/// // This is the `std::hash::Hash` trait. +/// } +/// +/// if match_path(ty_path, &["rustc", "lint", "Lint"]) { +/// // This is a `rustc_middle::lint::Lint`. +/// } +/// ``` +pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool { + path.segments + .iter() + .rev() + .zip(segments.iter().rev()) + .all(|(a, b)| a.ident.name.as_str() == *b) +} + +/// If the expression is a path to a local, returns the canonical `HirId` of the local. +pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> { + if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind { + if let Res::Local(id) = path.res { + return Some(id); + } + } + None +} + +/// Returns true if the expression is a path to a local with the specified `HirId`. +/// Use this function to see if an expression matches a function argument or a match binding. +pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool { + path_to_local(expr) == Some(id) +} + +pub trait MaybePath<'hir> { + fn hir_id(&self) -> HirId; + fn qpath_opt(&self) -> Option<&QPath<'hir>>; +} + +macro_rules! maybe_path { + ($ty:ident, $kind:ident) => { + impl<'hir> MaybePath<'hir> for hir::$ty<'hir> { + fn hir_id(&self) -> HirId { + self.hir_id + } + fn qpath_opt(&self) -> Option<&QPath<'hir>> { + match &self.kind { + hir::$kind::Path(qpath) => Some(qpath), + _ => None, + } + } + } + }; +} +maybe_path!(Expr, ExprKind); +maybe_path!(Pat, PatKind); +maybe_path!(Ty, TyKind); + +/// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err` +pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res { + match maybe_path.qpath_opt() { + None => Res::Err, + Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()), + } +} + +/// If `maybe_path` is a path node which resolves to an item, retrieves the item ID +pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option<DefId> { + path_res(cx, maybe_path).opt_def_id() +} + +/// Resolves a def path like `std::vec::Vec`. +/// This function is expensive and should be used sparingly. +pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { + fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str) -> Option<Res> { + match tcx.def_kind(def_id) { + DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx + .module_children(def_id) + .iter() + .find(|item| item.ident.name.as_str() == name) + .map(|child| child.res.expect_non_local()), + DefKind::Impl => tcx + .associated_item_def_ids(def_id) + .iter() + .copied() + .find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name) + .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)), + _ => None, + } + } + fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx { + let single = |ty| tcx.incoherent_impls(ty).iter().copied(); + let empty = || [].iter().copied(); + match name { + "bool" => single(BoolSimplifiedType), + "char" => single(CharSimplifiedType), + "str" => single(StrSimplifiedType), + "array" => single(ArraySimplifiedType), + "slice" => single(SliceSimplifiedType), + // FIXME: rustdoc documents these two using just `pointer`. + // + // Maybe this is something we should do here too. + "const_ptr" => single(PtrSimplifiedType(Mutability::Not)), + "mut_ptr" => single(PtrSimplifiedType(Mutability::Mut)), + "isize" => single(IntSimplifiedType(IntTy::Isize)), + "i8" => single(IntSimplifiedType(IntTy::I8)), + "i16" => single(IntSimplifiedType(IntTy::I16)), + "i32" => single(IntSimplifiedType(IntTy::I32)), + "i64" => single(IntSimplifiedType(IntTy::I64)), + "i128" => single(IntSimplifiedType(IntTy::I128)), + "usize" => single(UintSimplifiedType(UintTy::Usize)), + "u8" => single(UintSimplifiedType(UintTy::U8)), + "u16" => single(UintSimplifiedType(UintTy::U16)), + "u32" => single(UintSimplifiedType(UintTy::U32)), + "u64" => single(UintSimplifiedType(UintTy::U64)), + "u128" => single(UintSimplifiedType(UintTy::U128)), + "f32" => single(FloatSimplifiedType(FloatTy::F32)), + "f64" => single(FloatSimplifiedType(FloatTy::F64)), + _ => empty(), + } + } + fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> { + tcx.crates(()) + .iter() + .copied() + .find(|&num| tcx.crate_name(num).as_str() == name) + .map(CrateNum::as_def_id) + } + + let (base, first, path) = match *path { + [base, first, ref path @ ..] => (base, first, path), + [primitive] => { + return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy); + }, + _ => return Res::Err, + }; + let tcx = cx.tcx; + let starts = find_primitive(tcx, base) + .chain(find_crate(tcx, base)) + .filter_map(|id| item_child_by_name(tcx, id, first)); + + for first in starts { + let last = path + .iter() + .copied() + // for each segment, find the child item + .try_fold(first, |res, segment| { + let def_id = res.def_id(); + if let Some(item) = item_child_by_name(tcx, def_id, segment) { + Some(item) + } else if matches!(res, Res::Def(DefKind::Enum | DefKind::Struct, _)) { + // it is not a child item so check inherent impl items + tcx.inherent_impls(def_id) + .iter() + .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment)) + } else { + None + } + }); + + if let Some(last) = last { + return last; + } + } + + Res::Err +} + +/// Convenience function to get the `DefId` of a trait by path. +/// It could be a trait or trait alias. +pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> { + match def_path_res(cx, path) { + Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), + _ => None, + } +} + +/// Gets the `hir::TraitRef` of the trait the given method is implemented for. +/// +/// Use this if you want to find the `TraitRef` of the `Add` trait in this example: +/// +/// ```rust +/// struct Point(isize, isize); +/// +/// impl std::ops::Add for Point { +/// type Output = Self; +/// +/// fn add(self, other: Self) -> Self { +/// Point(0, 0) +/// } +/// } +/// ``` +pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, def_id: LocalDefId) -> Option<&'tcx TraitRef<'tcx>> { + // Get the implemented trait for the current function + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); + let parent_impl = cx.tcx.hir().get_parent_item(hir_id); + if_chain! { + if parent_impl != CRATE_DEF_ID; + if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl); + if let hir::ItemKind::Impl(impl_) = &item.kind; + then { + return impl_.of_trait.as_ref(); + } + } + None +} + +/// This method will return tuple of projection stack and root of the expression, +/// used in `can_mut_borrow_both`. +/// +/// For example, if `e` represents the `v[0].a.b[x]` +/// this method will return a tuple, composed of a `Vec` +/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]` +/// and an `Expr` for root of them, `v` +fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) { + let mut result = vec![]; + let root = loop { + match e.kind { + ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => { + result.push(e); + e = ep; + }, + _ => break e, + }; + }; + result.reverse(); + (result, root) +} + +/// Gets the mutability of the custom deref adjustment, if any. +pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> { + cx.typeck_results() + .expr_adjustments(e) + .iter() + .find_map(|a| match a.kind { + Adjust::Deref(Some(d)) => Some(Some(d.mutbl)), + Adjust::Deref(None) => None, + _ => Some(None), + }) + .and_then(|x| x) +} + +/// Checks if two expressions can be mutably borrowed simultaneously +/// and they aren't dependent on borrowing same thing twice +pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool { + let (s1, r1) = projection_stack(e1); + let (s2, r2) = projection_stack(e2); + if !eq_expr_value(cx, r1, r2) { + return true; + } + if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() { + return false; + } + + for (x1, x2) in s1.iter().zip(s2.iter()) { + if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() { + return false; + } + + match (&x1.kind, &x2.kind) { + (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => { + if i1 != i2 { + return true; + } + }, + (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => { + if !eq_expr_value(cx, i1, i2) { + return false; + } + }, + _ => return false, + } + } + false +} + +/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent" +/// constructor from the std library +fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool { + let std_types_symbols = &[ + sym::String, + sym::Vec, + sym::VecDeque, + sym::LinkedList, + sym::HashMap, + sym::BTreeMap, + sym::HashSet, + sym::BTreeSet, + sym::BinaryHeap, + ]; + + if let QPath::TypeRelative(_, method) = path { + if method.ident.name == sym::new { + if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { + if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() { + return std_types_symbols + .iter() + .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did())); + } + } + } + } + false +} + +/// Return true if the expr is equal to `Default::default` when evaluated. +pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool { + if_chain! { + if let hir::ExprKind::Path(ref repl_func_qpath) = repl_func.kind; + if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); + if is_diag_trait_item(cx, repl_def_id, sym::Default) + || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath); + then { true } else { false } + } +} + +/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated. +/// It doesn't cover all cases, for example indirect function calls (some of std +/// functions are supported) but it is the best we have. +pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Bool(false) | LitKind::Int(0, _) => true, + LitKind::Str(s, _) => s.is_empty(), + _ => false, + }, + ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)), + ExprKind::Repeat(x, ArrayLen::Body(len)) => if_chain! { + if let ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(len.body).value.kind; + if let LitKind::Int(v, _) = const_lit.node; + if v <= 32 && is_default_equivalent(cx, x); + then { + true + } + else { + false + } + }, + ExprKind::Call(repl_func, _) => is_default_equivalent_call(cx, repl_func), + ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone), + ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])), + _ => false, + } +} + +/// Checks if the top level expression can be moved into a closure as is. +/// Currently checks for: +/// * Break/Continue outside the given loop HIR ids. +/// * Yield/Return statements. +/// * Inline assembly. +/// * Usages of a field of a local where the type of the local can be partially moved. +/// +/// For example, given the following function: +/// +/// ``` +/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) { +/// for item in iter { +/// let s = item.1; +/// if item.0 > 10 { +/// continue; +/// } else { +/// s.clear(); +/// } +/// } +/// } +/// ``` +/// +/// When called on the expression `item.0` this will return false unless the local `item` is in the +/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it +/// isn't always safe to move into a closure when only a single field is needed. +/// +/// When called on the `continue` expression this will return false unless the outer loop expression +/// is in the `loop_ids` set. +/// +/// Note that this check is not recursive, so passing the `if` expression will always return true +/// even though sub-expressions might return false. +pub fn can_move_expr_to_closure_no_visit<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + loop_ids: &[HirId], + ignore_locals: &HirIdSet, +) -> bool { + match expr.kind { + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) + | ExprKind::Continue(Destination { target_id: Ok(id), .. }) + if loop_ids.contains(&id) => + { + true + }, + ExprKind::Break(..) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::Yield(..) + | ExprKind::InlineAsm(_) => false, + // Accessing a field of a local value can only be done if the type isn't + // partially moved. + ExprKind::Field( + &Expr { + hir_id, + kind: + ExprKind::Path(QPath::Resolved( + _, + Path { + res: Res::Local(local_id), + .. + }, + )), + .. + }, + _, + ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => { + // TODO: check if the local has been partially moved. Assume it has for now. + false + }, + _ => true, + } +} + +/// How a local is captured by a closure +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CaptureKind { + Value, + Ref(Mutability), +} +impl CaptureKind { + pub fn is_imm_ref(self) -> bool { + self == Self::Ref(Mutability::Not) + } +} +impl std::ops::BitOr for CaptureKind { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value, + (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_)) + | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut), + (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not), + } + } +} +impl std::ops::BitOrAssign for CaptureKind { + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} + +/// Given an expression referencing a local, determines how it would be captured in a closure. +/// Note as this will walk up to parent expressions until the capture can be determined it should +/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or +/// function argument (other than a receiver). +pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind { + fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { + let mut capture = CaptureKind::Ref(Mutability::Not); + pat.each_binding_or_first(&mut |_, id, span, _| match cx + .typeck_results() + .extract_binding_mode(cx.sess(), id, span) + .unwrap() + { + BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => { + capture = CaptureKind::Value; + }, + BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => { + capture = CaptureKind::Ref(Mutability::Mut); + }, + _ => (), + }); + capture + } + + debug_assert!(matches!( + e.kind, + ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. })) + )); + + let mut child_id = e.hir_id; + let mut capture = CaptureKind::Value; + let mut capture_expr_ty = e; + + for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) { + if let [ + Adjustment { + kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), + target, + }, + ref adjust @ .., + ] = *cx + .typeck_results() + .adjustments() + .get(child_id) + .map_or(&[][..], |x| &**x) + { + if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) = + *adjust.last().map_or(target, |a| a.target).kind() + { + return CaptureKind::Ref(mutability); + } + } + + match parent { + Node::Expr(e) => match e.kind { + ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability), + ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not), + ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => { + return CaptureKind::Ref(Mutability::Mut); + }, + ExprKind::Field(..) => { + if capture == CaptureKind::Value { + capture_expr_ty = e; + } + }, + ExprKind::Let(let_expr) => { + let mutability = match pat_capture_kind(cx, let_expr.pat) { + CaptureKind::Value => Mutability::Not, + CaptureKind::Ref(m) => m, + }; + return CaptureKind::Ref(mutability); + }, + ExprKind::Match(_, arms, _) => { + let mut mutability = Mutability::Not; + for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) { + match capture { + CaptureKind::Value => break, + CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut, + CaptureKind::Ref(Mutability::Not) => (), + } + } + return CaptureKind::Ref(mutability); + }, + _ => break, + }, + Node::Local(l) => match pat_capture_kind(cx, l.pat) { + CaptureKind::Value => break, + capture @ CaptureKind::Ref(_) => return capture, + }, + _ => break, + } + + child_id = parent_id; + } + + if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) { + // Copy types are never automatically captured by value. + CaptureKind::Ref(Mutability::Not) + } else { + capture + } +} + +/// Checks if the expression can be moved into a closure as is. This will return a list of captures +/// if so, otherwise, `None`. +pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + // Stack of potential break targets contained in the expression. + loops: Vec<HirId>, + /// Local variables created in the expression. These don't need to be captured. + locals: HirIdSet, + /// Whether this expression can be turned into a closure. + allow_closure: bool, + /// Locals which need to be captured, and whether they need to be by value, reference, or + /// mutable reference. + captures: HirIdMap<CaptureKind>, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if !self.allow_closure { + return; + } + + match e.kind { + ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => { + if !self.locals.contains(&l) { + let cap = capture_local_usage(self.cx, e); + self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap); + } + }, + ExprKind::Closure { .. } => { + let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id); + for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) { + let local_id = match capture.place.base { + PlaceBase::Local(id) => id, + PlaceBase::Upvar(var) => var.var_path.hir_id, + _ => continue, + }; + if !self.locals.contains(&local_id) { + let capture = match capture.info.capture_kind { + UpvarCapture::ByValue => CaptureKind::Value, + UpvarCapture::ByRef(kind) => match kind { + BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not), + BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => { + CaptureKind::Ref(Mutability::Mut) + }, + }, + }; + self.captures + .entry(local_id) + .and_modify(|e| *e |= capture) + .or_insert(capture); + } + } + }, + ExprKind::Loop(b, ..) => { + self.loops.push(e.hir_id); + self.visit_block(b); + self.loops.pop(); + }, + _ => { + self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals); + walk_expr(self, e); + }, + } + } + + fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) { + p.each_binding_or_first(&mut |_, id, _, _| { + self.locals.insert(id); + }); + } + } + + let mut v = V { + cx, + allow_closure: true, + loops: Vec::new(), + locals: HirIdSet::default(), + captures: HirIdMap::default(), + }; + v.visit_expr(expr); + v.allow_closure.then_some(v.captures) +} + +/// Returns the method names and argument list of nested method call expressions that make up +/// `expr`. method/span lists are sorted with the most recent call first. +pub fn method_calls<'tcx>( + expr: &'tcx Expr<'tcx>, + max_depth: usize, +) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) { + let mut method_names = Vec::with_capacity(max_depth); + let mut arg_lists = Vec::with_capacity(max_depth); + let mut spans = Vec::with_capacity(max_depth); + + let mut current = expr; + for _ in 0..max_depth { + if let ExprKind::MethodCall(path, args, _) = ¤t.kind { + if args.iter().any(|e| e.span.from_expansion()) { + break; + } + method_names.push(path.ident.name); + arg_lists.push(&**args); + spans.push(path.ident.span); + current = &args[0]; + } else { + break; + } + } + + (method_names, arg_lists, spans) +} + +/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s. +/// +/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`, +/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec` +/// containing the `Expr`s for +/// `.bar()` and `.baz()` +pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> { + let mut current = expr; + let mut matched = Vec::with_capacity(methods.len()); + for method_name in methods.iter().rev() { + // method chains are stored last -> first + if let ExprKind::MethodCall(path, args, _) = current.kind { + if path.ident.name.as_str() == *method_name { + if args.iter().any(|e| e.span.from_expansion()) { + return None; + } + matched.push(args); // build up `matched` backwards + current = &args[0]; // go to parent expression + } else { + return None; + } + } else { + return None; + } + } + // Reverse `matched` so that it is in the same order as `methods`. + matched.reverse(); + Some(matched) +} + +/// Returns `true` if the provided `def_id` is an entrypoint to a program. +pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool { + cx.tcx + .entry_fn(()) + .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id) +} + +/// Returns `true` if the expression is in the program's `#[panic_handler]`. +pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + let parent = cx.tcx.hir().get_parent_item(e.hir_id); + Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl() +} + +/// Gets the name of the item the expression is in, if available. +pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + match cx.tcx.hir().find_by_def_id(parent_id) { + Some( + Node::Item(Item { ident, .. }) + | Node::TraitItem(TraitItem { ident, .. }) + | Node::ImplItem(ImplItem { ident, .. }), + ) => Some(ident.name), + _ => None, + } +} + +pub struct ContainsName { + pub name: Symbol, + pub result: bool, +} + +impl<'tcx> Visitor<'tcx> for ContainsName { + fn visit_name(&mut self, _: Span, name: Symbol) { + if self.name == name { + self.result = true; + } + } +} + +/// Checks if an `Expr` contains a certain name. +pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool { + let mut cn = ContainsName { name, result: false }; + cn.visit_expr(expr); + cn.result +} + +/// Returns `true` if `expr` contains a return expression +pub fn contains_return(expr: &hir::Expr<'_>) -> bool { + let mut found = false; + expr_visitor_no_bodies(|expr| { + if !found { + if let hir::ExprKind::Ret(..) = &expr.kind { + found = true; + } + } + !found + }) + .visit_expr(expr); + found +} + +/// Extends the span to the beginning of the spans line, incl. whitespaces. +/// +/// ```rust +/// let x = (); +/// // ^^ +/// // will be converted to +/// let x = (); +/// // ^^^^^^^^^^^^^^ +/// ``` +fn line_span<T: LintContext>(cx: &T, span: Span) -> Span { + let span = original_sp(span, DUMMY_SP); + let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap(); + let line_no = source_map_and_line.line; + let line_start = source_map_and_line.sf.lines(|lines| lines[line_no]); + span.with_lo(line_start) +} + +/// Gets the parent node, if any. +pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> { + tcx.hir().parent_iter(id).next().map(|(_, node)| node) +} + +/// Gets the parent expression, if any –- this is useful to constrain a lint. +pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + get_parent_expr_for_hir(cx, e.hir_id) +} + +/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for +/// constraint lints +pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> { + match get_parent_node(cx.tcx, hir_id) { + Some(Node::Expr(parent)) => Some(parent), + _ => None, + } +} + +pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> { + let map = &cx.tcx.hir(); + let enclosing_node = map + .get_enclosing_scope(hir_id) + .and_then(|enclosing_id| map.find(enclosing_id)); + enclosing_node.and_then(|node| match node { + Node::Block(block) => Some(block), + Node::Item(&Item { + kind: ItemKind::Fn(_, _, eid), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(_, eid), + .. + }) => match cx.tcx.hir().body(eid).value.kind { + ExprKind::Block(block, _) => Some(block), + _ => None, + }, + _ => None, + }) +} + +/// Gets the loop or closure enclosing the given expression, if any. +pub fn get_enclosing_loop_or_multi_call_closure<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, +) -> Option<&'tcx Expr<'tcx>> { + for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) { + match node { + Node::Expr(e) => match e.kind { + ExprKind::Closure { .. } => { + if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind() + && subs.as_closure().kind() == ClosureKind::FnOnce + { + continue; + } + let is_once = walk_to_expr_usage(cx, e, |node, id| { + let Node::Expr(e) = node else { + return None; + }; + match e.kind { + ExprKind::Call(f, _) if f.hir_id == id => Some(()), + ExprKind::Call(f, args) => { + let i = args.iter().position(|arg| arg.hir_id == id)?; + let sig = expr_sig(cx, f)?; + let predicates = sig + .predicates_id() + .map_or(cx.param_env, |id| cx.tcx.param_env(id)) + .caller_bounds(); + sig.input(i).and_then(|ty| { + ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(()) + }) + }, + ExprKind::MethodCall(_, args, _) => { + let i = args.iter().position(|arg| arg.hir_id == id)?; + let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?; + let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i]; + ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(()) + }, + _ => None, + } + }) + .is_some(); + if !is_once { + return Some(e); + } + }, + ExprKind::Loop(..) => return Some(e), + _ => (), + }, + Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), + _ => break, + } + } + None +} + +/// Gets the parent node if it's an impl block. +pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> { + match tcx.hir().parent_iter(id).next() { + Some(( + _, + Node::Item(Item { + kind: ItemKind::Impl(imp), + .. + }), + )) => Some(imp), + _ => None, + } +} + +/// Removes blocks around an expression, only if the block contains just one expression +/// and no statements. Unsafe blocks are not removed. +/// +/// Examples: +/// * `{}` -> `{}` +/// * `{ x }` -> `x` +/// * `{{ x }}` -> `x` +/// * `{ x; }` -> `{ x; }` +/// * `{ x; y }` -> `{ x; y }` +/// * `{ unsafe { x } }` -> `unsafe { x }` +pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { + while let ExprKind::Block( + Block { + stmts: [], + expr: Some(inner), + rules: BlockCheckMode::DefaultBlock, + .. + }, + _, + ) = expr.kind + { + expr = inner; + } + expr +} + +/// Removes blocks around an expression, only if the block contains just one expression +/// or just one expression statement with a semicolon. Unsafe blocks are not removed. +/// +/// Examples: +/// * `{}` -> `{}` +/// * `{ x }` -> `x` +/// * `{ x; }` -> `x` +/// * `{{ x; }}` -> `x` +/// * `{ x; y }` -> `{ x; y }` +/// * `{ unsafe { x } }` -> `unsafe { x }` +pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { + while let ExprKind::Block( + Block { + stmts: [], + expr: Some(inner), + rules: BlockCheckMode::DefaultBlock, + .. + } + | Block { + stmts: + [ + Stmt { + kind: StmtKind::Expr(inner) | StmtKind::Semi(inner), + .. + }, + ], + expr: None, + rules: BlockCheckMode::DefaultBlock, + .. + }, + _, + ) = expr.kind + { + expr = inner; + } + expr +} + +/// Checks if the given expression is the else clause of either an `if` or `if let` expression. +pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { + let mut iter = tcx.hir().parent_iter(expr.hir_id); + match iter.next() { + Some(( + _, + Node::Expr(Expr { + kind: ExprKind::If(_, _, Some(else_expr)), + .. + }), + )) => else_expr.hir_id == expr.hir_id, + _ => false, + } +} + +/// Checks whether the given expression is a constant integer of the given value. +/// unlike `is_integer_literal`, this version does const folding +pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool { + if is_integer_literal(e, value) { + return true; + } + let enclosing_body = cx.tcx.hir().enclosing_body_owner(e.hir_id); + if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) { + return value == v; + } + false +} + +/// Checks whether the given expression is a constant literal of the given value. +pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool { + // FIXME: use constant folding + if let ExprKind::Lit(ref spanned) = expr.kind { + if let LitKind::Int(v, _) = spanned.node { + return v == value; + } + } + false +} + +/// Returns `true` if the given `Expr` has been coerced before. +/// +/// Examples of coercions can be found in the Nomicon at +/// <https://doc.rust-lang.org/nomicon/coercions.html>. +/// +/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::check::coercion` for more +/// information on adjustments and coercions. +pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + cx.typeck_results().adjustments().get(e.hir_id).is_some() +} + +/// Returns the pre-expansion span if this comes from an expansion of the +/// macro `name`. +/// See also [`is_direct_expn_of`]. +#[must_use] +pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> { + loop { + if span.from_expansion() { + let data = span.ctxt().outer_expn_data(); + let new_span = data.call_site; + + if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if mac_name.as_str() == name { + return Some(new_span); + } + } + + span = new_span; + } else { + return None; + } + } +} + +/// Returns the pre-expansion span if the span directly comes from an expansion +/// of the macro `name`. +/// The difference with [`is_expn_of`] is that in +/// ```rust +/// # macro_rules! foo { ($name:tt!$args:tt) => { $name!$args } } +/// # macro_rules! bar { ($e:expr) => { $e } } +/// foo!(bar!(42)); +/// ``` +/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only +/// from `bar!` by `is_direct_expn_of`. +#[must_use] +pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> { + if span.from_expansion() { + let data = span.ctxt().outer_expn_data(); + let new_span = data.call_site; + + if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if mac_name.as_str() == name { + return Some(new_span); + } + } + } + + None +} + +/// Convenience function to get the return type of a function. +pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> { + let fn_def_id = cx.tcx.hir().local_def_id(fn_item); + let ret_ty = cx.tcx.fn_sig(fn_def_id).output(); + cx.tcx.erase_late_bound_regions(ret_ty) +} + +/// Convenience function to get the nth argument type of a function. +pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId, nth: usize) -> Ty<'tcx> { + let fn_def_id = cx.tcx.hir().local_def_id(fn_item); + let arg = cx.tcx.fn_sig(fn_def_id).input(nth); + cx.tcx.erase_late_bound_regions(arg) +} + +/// Checks if an expression is constructing a tuple-like enum variant or struct +pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(fun, _) = expr.kind { + if let ExprKind::Path(ref qp) = fun.kind { + let res = cx.qpath_res(qp, fun.hir_id); + return match res { + def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true, + def::Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id), + _ => false, + }; + } + } + false +} + +/// Returns `true` if a pattern is refutable. +// TODO: should be implemented using rustc/mir_build/thir machinery +pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { + fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool { + matches!( + cx.qpath_res(qpath, id), + def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _) + ) + } + + fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool { + i.into_iter().any(|pat| is_refutable(cx, pat)) + } + + match pat.kind { + PatKind::Wild => false, + PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)), + PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat), + PatKind::Lit(..) | PatKind::Range(..) => true, + PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), + PatKind::Or(pats) => { + // TODO: should be the honest check, that pats is exhaustive set + are_refutable(cx, pats) + }, + PatKind::Tuple(pats, _) => are_refutable(cx, pats), + PatKind::Struct(ref qpath, fields, _) => { + is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat)) + }, + PatKind::TupleStruct(ref qpath, pats, _) => is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats), + PatKind::Slice(head, middle, tail) => { + match &cx.typeck_results().node_type(pat.hir_id).kind() { + rustc_ty::Slice(..) => { + // [..] is the only irrefutable slice pattern. + !head.is_empty() || middle.is_none() || !tail.is_empty() + }, + rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())), + _ => { + // unreachable!() + true + }, + } + }, + } +} + +/// If the pattern is an `or` pattern, call the function once for each sub pattern. Otherwise, call +/// the function once on the given pattern. +pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) { + if let PatKind::Or(pats) = pat.kind { + pats.iter().for_each(f); + } else { + f(pat); + } +} + +pub fn is_self(slf: &Param<'_>) -> bool { + if let PatKind::Binding(.., name, _) = slf.pat.kind { + name.name == kw::SelfLower + } else { + false + } +} + +pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { + if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind { + if let Res::SelfTy { .. } = path.res { + return true; + } + } + false +} + +pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> { + (0..decl.inputs.len()).map(move |i| &body.params[i]) +} + +/// Checks if a given expression is a match expression expanded from the `?` +/// operator or the `try` macro. +pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + if_chain! { + if let PatKind::TupleStruct(ref path, pat, None) = arm.pat.kind; + if is_lang_ctor(cx, path, ResultOk); + if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind; + if path_to_local_id(arm.body, hir_id); + then { + return true; + } + } + false + } + + fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind { + is_lang_ctor(cx, path, ResultErr) + } else { + false + } + } + + if let ExprKind::Match(_, arms, ref source) = expr.kind { + // desugared from a `?` operator + if *source == MatchSource::TryDesugar { + return Some(expr); + } + + if_chain! { + if arms.len() == 2; + if arms[0].guard.is_none(); + if arms[1].guard.is_none(); + if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])); + then { + return Some(expr); + } + } + } + + None +} + +/// Returns `true` if the lint is allowed in the current context. This is useful for +/// skipping long running code when it's unnecessary +/// +/// This function should check the lint level for the same node, that the lint will +/// be emitted at. If the information is buffered to be emitted at a later point, please +/// make sure to use `span_lint_hir` functions to emit the lint. This ensures that +/// expectations at the checked nodes will be fulfilled. +pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool { + cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow +} + +pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> { + while let PatKind::Ref(subpat, _) = pat.kind { + pat = subpat; + } + pat +} + +pub fn int_bits(tcx: TyCtxt<'_>, ity: rustc_ty::IntTy) -> u64 { + Integer::from_int_ty(&tcx, ity).size().bits() +} + +#[expect(clippy::cast_possible_wrap)] +/// Turn a constant int byte representation into an i128 +pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::IntTy) -> i128 { + let amt = 128 - int_bits(tcx, ity); + ((u as i128) << amt) >> amt +} + +#[expect(clippy::cast_sign_loss)] +/// clip unused bytes +pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: rustc_ty::IntTy) -> u128 { + let amt = 128 - int_bits(tcx, ity); + ((u as u128) << amt) >> amt +} + +/// clip unused bytes +pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: rustc_ty::UintTy) -> u128 { + let bits = Integer::from_uint_ty(&tcx, ity).size().bits(); + let amt = 128 - bits; + (u << amt) >> amt +} + +pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool { + attrs.iter().any(|attr| attr.has_name(symbol)) +} + +pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool { + let map = &tcx.hir(); + let mut prev_enclosing_node = None; + let mut enclosing_node = node; + while Some(enclosing_node) != prev_enclosing_node { + if has_attr(map.attrs(enclosing_node), symbol) { + return true; + } + prev_enclosing_node = Some(enclosing_node); + enclosing_node = map.local_def_id_to_hir_id(map.get_parent_item(enclosing_node)); + } + + false +} + +pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { + any_parent_has_attr(tcx, node, sym::automatically_derived) +} + +/// Matches a function call with the given path and returns the arguments. +/// +/// Usage: +/// +/// ```rust,ignore +/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX); +/// ``` +pub fn match_function_call<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + path: &[&str], +) -> Option<&'tcx [Expr<'tcx>]> { + if_chain! { + if let ExprKind::Call(fun, args) = expr.kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); + if match_def_path(cx, fun_def_id, path); + then { + return Some(args); + } + }; + None +} + +/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if +/// any. +/// +/// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items. +pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> { + let search_path = cx.get_def_path(did); + paths + .iter() + .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied())) +} + +/// Checks if the given `DefId` matches the path. +pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { + // We should probably move to Symbols in Clippy as well rather than interning every time. + let path = cx.get_def_path(did); + syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied()) +} + +/// Checks if the given `DefId` matches the `libc` item. +pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool { + let path = cx.get_def_path(did); + // libc is meant to be used as a flat list of names, but they're all actually defined in different + // modules based on the target platform. Ignore everything but crate name and the item name. + path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name) +} + +/// Returns the list of condition expressions and the list of blocks in a +/// sequence of `if/else`. +/// E.g., this returns `([a, b], [c, d, e])` for the expression +/// `if a { c } else if b { d } else { e }`. +pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) { + let mut conds = Vec::new(); + let mut blocks: Vec<&Block<'_>> = Vec::new(); + + while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) { + conds.push(cond); + if let ExprKind::Block(block, _) = then.kind { + blocks.push(block); + } else { + panic!("ExprKind::If node is not an ExprKind::Block"); + } + + if let Some(else_expr) = r#else { + expr = else_expr; + } else { + break; + } + } + + // final `else {..}` + if !blocks.is_empty() { + if let ExprKind::Block(block, _) = expr.kind { + blocks.push(block); + } + } + + (conds, blocks) +} + +/// Checks if the given function kind is an async function. +pub fn is_async_fn(kind: FnKind<'_>) -> bool { + matches!(kind, FnKind::ItemFn(_, _, header) if header.asyncness == IsAsync::Async) +} + +/// Peels away all the compiler generated code surrounding the body of an async function, +pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call( + _, + &[ + Expr { + kind: ExprKind::Closure(&Closure { body, .. }), + .. + }, + ], + ) = body.value.kind + { + if let ExprKind::Block( + Block { + stmts: [], + expr: + Some(Expr { + kind: ExprKind::DropTemps(expr), + .. + }), + .. + }, + _, + ) = tcx.hir().body(body).value.kind + { + return Some(expr); + } + }; + None +} + +// check if expr is calling method or function with #[must_use] attribute +pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let did = match expr.kind { + ExprKind::Call(path, _) => if_chain! { + if let ExprKind::Path(ref qpath) = path.kind; + if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id); + then { + Some(did) + } else { + None + } + }, + ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }; + + did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use)) +} + +/// Checks if an expression represents the identity function +/// Only examines closures and `std::convert::identity` +pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + /// Checks if a function's body represents the identity function. Looks for bodies of the form: + /// * `|x| x` + /// * `|x| return x` + /// * `|x| { return x }` + /// * `|x| { return x; }` + fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { + let id = if_chain! { + if let [param] = func.params; + if let PatKind::Binding(_, id, _, _) = param.pat.kind; + then { + id + } else { + return false; + } + }; + + let mut expr = &func.value; + loop { + match expr.kind { + #[rustfmt::skip] + ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, ) + | ExprKind::Ret(Some(e)) => expr = e, + #[rustfmt::skip] + ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => { + if_chain! { + if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind; + if let ExprKind::Ret(Some(ret_val)) = e.kind; + then { + expr = ret_val; + } else { + return false; + } + } + }, + _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(), + } + } + } + + match expr.kind { + ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)), + _ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)), + } +} + +/// Gets the node where an expression is either used, or it's type is unified with another branch. +/// Returns both the node and the `HirId` of the closest child node. +pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> { + let mut child_id = expr.hir_id; + let mut iter = tcx.hir().parent_iter(child_id); + loop { + match iter.next() { + None => break None, + Some((id, Node::Block(_))) => child_id = id, + Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id, + Some((_, Node::Expr(expr))) => match expr.kind { + ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id, + ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id, + ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None, + _ => break Some((Node::Expr(expr), child_id)), + }, + Some((_, node)) => break Some((node, child_id)), + } + } +} + +/// Checks if the result of an expression is used, or it's type is unified with another branch. +pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { + !matches!( + get_expr_use_or_unification_node(tcx, expr), + None | Some(( + Node::Stmt(Stmt { + kind: StmtKind::Expr(_) + | StmtKind::Semi(_) + | StmtKind::Local(Local { + pat: Pat { + kind: PatKind::Wild, + .. + }, + .. + }), + .. + }), + _ + )) + ) +} + +/// Checks if the expression is the final expression returned from a block. +pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { + matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..))) +} + +pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { + if !is_no_std_crate(cx) { + Some("std") + } else if !is_no_core_crate(cx) { + Some("core") + } else { + None + } +} + +pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { + cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { + if let ast::AttrKind::Normal(ref attr, _) = attr.kind { + attr.path == sym::no_std + } else { + false + } + }) +} + +pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool { + cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { + if let ast::AttrKind::Normal(ref attr, _) = attr.kind { + attr.path == sym::no_core + } else { + false + } + }) +} + +/// Check if parent of a hir node is a trait implementation block. +/// For example, `f` in +/// ```rust +/// # struct S; +/// # trait Trait { fn f(); } +/// impl Trait for S { +/// fn f() {} +/// } +/// ``` +pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool { + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })) + } else { + false + } +} + +/// Check if it's even possible to satisfy the `where` clause for the item. +/// +/// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example: +/// +/// ```ignore +/// fn foo() where i32: Iterator { +/// for _ in 2i32 {} +/// } +/// ``` +pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool { + use rustc_trait_selection::traits; + let predicates = cx + .tcx + .predicates_of(did) + .predicates + .iter() + .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); + traits::impossible_predicates( + cx.tcx, + traits::elaborate_predicates(cx.tcx, predicates) + .map(|o| o.predicate) + .collect::<Vec<_>>(), + ) +} + +/// Returns the `DefId` of the callee if the given expression is a function or method call. +pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> { + match &expr.kind { + ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + ExprKind::Call( + Expr { + kind: ExprKind::Path(qpath), + hir_id: path_hir_id, + .. + }, + .., + ) => { + // Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or + // deref to fn pointers, dyn Fn, impl Fn - #8850 + if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) = + cx.typeck_results().qpath_res(qpath, *path_hir_id) + { + Some(id) + } else { + None + } + }, + _ => None, + } +} + +/// Returns Option<String> where String is a textual representation of the type encapsulated in the +/// slice iff the given expression is a slice of primitives (as defined in the +/// `is_recursively_primitive_type` function) and None otherwise. +pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> { + let expr_type = cx.typeck_results().expr_ty_adjusted(expr); + let expr_kind = expr_type.kind(); + let is_primitive = match expr_kind { + rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type), + rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => { + if let rustc_ty::Slice(element_type) = inner_ty.kind() { + is_recursively_primitive_type(*element_type) + } else { + unreachable!() + } + }, + _ => false, + }; + + if is_primitive { + // if we have wrappers like Array, Slice or Tuple, print these + // and get the type enclosed in the slice ref + match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() { + rustc_ty::Slice(..) => return Some("slice".into()), + rustc_ty::Array(..) => return Some("array".into()), + rustc_ty::Tuple(..) => return Some("tuple".into()), + _ => { + // is_recursively_primitive_type() should have taken care + // of the rest and we can rely on the type that is found + let refs_peeled = expr_type.peel_refs(); + return Some(refs_peeled.walk().last().unwrap().to_string()); + }, + } + } + None +} + +/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)` +/// `hash` must be comformed with `eq` +pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)> +where + Hash: Fn(&T) -> u64, + Eq: Fn(&T, &T) -> bool, +{ + match exprs { + [a, b] if eq(a, b) => return vec![(a, b)], + _ if exprs.len() <= 2 => return vec![], + _ => {}, + } + + let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); + + let mut map: UnhashMap<u64, Vec<&_>> = + UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); + + for expr in exprs { + match map.entry(hash(expr)) { + Entry::Occupied(mut o) => { + for o in o.get() { + if eq(o, expr) { + match_expr_list.push((o, expr)); + } + } + o.get_mut().push(expr); + }, + Entry::Vacant(v) => { + v.insert(vec![expr]); + }, + } + } + + match_expr_list +} + +/// Peels off all references on the pattern. Returns the underlying pattern and the number of +/// references removed. +pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { + fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) { + if let PatKind::Ref(pat, _) = pat.kind { + peel(pat, count + 1) + } else { + (pat, count) + } + } + peel(pat, 0) +} + +/// Peels of expressions while the given closure returns `Some`. +pub fn peel_hir_expr_while<'tcx>( + mut expr: &'tcx Expr<'tcx>, + mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>, +) -> &'tcx Expr<'tcx> { + while let Some(e) = f(expr) { + expr = e; + } + expr +} + +/// Peels off up to the given number of references on the expression. Returns the underlying +/// expression and the number of references removed. +pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { + let mut remaining = count; + let e = peel_hir_expr_while(expr, |e| match e.kind { + ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => { + remaining -= 1; + Some(e) + }, + _ => None, + }); + (e, count - remaining) +} + +/// Peels off all references on the expression. Returns the underlying expression and the number of +/// references removed. +pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { + let mut count = 0; + let e = peel_hir_expr_while(expr, |e| match e.kind { + ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => { + count += 1; + Some(e) + }, + _ => None, + }); + (e, count) +} + +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) { + let mut count = 0; + loop { + match &ty.kind { + TyKind::Rptr(_, ref_ty) => { + ty = ref_ty.ty; + count += 1; + }, + _ => break (ty, count), + } + } +} + +/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is +/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. +pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { + loop { + match expr.kind { + ExprKind::AddrOf(_, _, e) => expr = e, + ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e, + _ => break, + } + } + expr +} + +pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { + if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { + if let Res::Def(_, def_id) = path.res { + return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr); + } + } + false +} + +static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalDefId, Vec<Symbol>>>> = OnceLock::new(); + +fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalDefId, f: impl Fn(&[Symbol]) -> bool) -> bool { + let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default())); + let mut map: MutexGuard<'_, FxHashMap<LocalDefId, Vec<Symbol>>> = cache.lock().unwrap(); + let value = map.entry(module); + match value { + Entry::Occupied(entry) => f(entry.get()), + Entry::Vacant(entry) => { + let mut names = Vec::new(); + for id in tcx.hir().module_items(module) { + if matches!(tcx.def_kind(id.def_id), DefKind::Const) + && let item = tcx.hir().item(id) + && let ItemKind::Const(ty, _body) = item.kind { + if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { + // We could also check for the type name `test::TestDescAndFn` + if let Res::Def(DefKind::Struct, _) = path.res { + let has_test_marker = tcx + .hir() + .attrs(item.hir_id()) + .iter() + .any(|a| a.has_name(sym::rustc_test_marker)); + if has_test_marker { + names.push(item.ident.name); + } + } + } + } + } + names.sort_unstable(); + f(entry.insert(names)) + }, + } +} + +/// Checks if the function containing the given `HirId` is a `#[test]` function +/// +/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function +pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { + with_test_item_names(tcx, tcx.parent_module(id), |names| { + tcx.hir() + .parent_iter(id) + // Since you can nest functions we need to collect all until we leave + // function scope + .any(|(_id, node)| { + if let Node::Item(item) = node { + if let ItemKind::Fn(_, _, _) = item.kind { + // Note that we have sorted the item names in the visitor, + // so the binary_search gets the same as `contains`, but faster. + return names.binary_search(&item.ident.name).is_ok(); + } + } + false + }) + }) +} + +/// Checks if the item containing the given `HirId` has `#[cfg(test)]` attribute applied +/// +/// Note: Add `// compile-flags: --test` to UI tests with a `#[cfg(test)]` function +pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { + fn is_cfg_test(attr: &Attribute) -> bool { + if attr.has_name(sym::cfg) + && let Some(items) = attr.meta_item_list() + && let [item] = &*items + && item.has_name(sym::test) + { + true + } else { + false + } + } + tcx.hir() + .parent_iter(id) + .flat_map(|(parent_id, _)| tcx.hir().attrs(parent_id)) + .any(is_cfg_test) +} + +/// Checks whether item either has `test` attribute applied, or +/// is a module with `test` in its name. +/// +/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function +pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { + is_in_test_function(tcx, item.hir_id()) + || matches!(item.kind, ItemKind::Mod(..)) + && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") +} + +/// Walks the HIR tree from the given expression, up to the node where the value produced by the +/// expression is consumed. Calls the function for every node encountered this way until it returns +/// `Some`. +/// +/// This allows walking through `if`, `match`, `break`, block expressions to find where the value +/// produced by the expression is consumed. +pub fn walk_to_expr_usage<'tcx, T>( + cx: &LateContext<'tcx>, + e: &Expr<'tcx>, + mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>, +) -> Option<T> { + let map = cx.tcx.hir(); + let mut iter = map.parent_iter(e.hir_id); + let mut child_id = e.hir_id; + + while let Some((parent_id, parent)) = iter.next() { + if let Some(x) = f(parent, child_id) { + return Some(x); + } + let parent = match parent { + Node::Expr(e) => e, + Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { + child_id = parent_id; + continue; + }, + Node::Arm(a) if a.body.hir_id == child_id => { + child_id = parent_id; + continue; + }, + _ => return None, + }; + match parent.kind { + ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, + ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { + child_id = id; + iter = map.parent_iter(id); + }, + ExprKind::Block(..) => child_id = parent_id, + _ => return None, + } + } + None +} + +macro_rules! op_utils { + ($($name:ident $assign:ident)*) => { + /// Binary operation traits like `LangItem::Add` + pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*]; + + /// Operator-Assign traits like `LangItem::AddAssign` + pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*]; + + /// Converts `BinOpKind::Add` to `(LangItem::Add, LangItem::AddAssign)`, for example + pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> { + match kind { + $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)* + _ => None, + } + } + }; +} + +op_utils! { + Add AddAssign + Sub SubAssign + Mul MulAssign + Div DivAssign + Rem RemAssign + BitXor BitXorAssign + BitAnd BitAndAssign + BitOr BitOrAssign + Shl ShlAssign + Shr ShrAssign +} diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs new file mode 100644 index 000000000..a268e339b --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -0,0 +1,583 @@ +#![allow(clippy::similar_names)] // `expr` and `expn` + +use crate::visitors::expr_visitor_no_bodies; + +use arrayvec::ArrayVec; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; +use rustc_lint::LateContext; +use rustc_span::def_id::DefId; +use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; +use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol}; +use std::ops::ControlFlow; + +const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[ + sym::assert_eq_macro, + sym::assert_macro, + sym::assert_ne_macro, + sym::debug_assert_eq_macro, + sym::debug_assert_macro, + sym::debug_assert_ne_macro, + sym::eprint_macro, + sym::eprintln_macro, + sym::format_args_macro, + sym::format_macro, + sym::print_macro, + sym::println_macro, + sym::std_panic_macro, + sym::write_macro, + sym::writeln_macro, +]; + +/// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`) +pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { + if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) { + FORMAT_MACRO_DIAG_ITEMS.contains(&name) + } else { + false + } +} + +/// A macro call, like `vec![1, 2, 3]`. +/// +/// Use `tcx.item_name(macro_call.def_id)` to get the macro name. +/// Even better is to check if it is a diagnostic item. +/// +/// This structure is similar to `ExpnData` but it precludes desugaring expansions. +#[derive(Debug)] +pub struct MacroCall { + /// Macro `DefId` + pub def_id: DefId, + /// Kind of macro + pub kind: MacroKind, + /// The expansion produced by the macro call + pub expn: ExpnId, + /// Span of the macro call site + pub span: Span, +} + +impl MacroCall { + pub fn is_local(&self) -> bool { + span_is_local(self.span) + } +} + +/// Returns an iterator of expansions that created the given span +pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> { + std::iter::from_fn(move || { + let ctxt = span.ctxt(); + if ctxt == SyntaxContext::root() { + return None; + } + let expn = ctxt.outer_expn(); + let data = expn.expn_data(); + span = data.call_site; + Some((expn, data)) + }) +} + +/// Checks whether the span is from the root expansion or a locally defined macro +pub fn span_is_local(span: Span) -> bool { + !span.from_expansion() || expn_is_local(span.ctxt().outer_expn()) +} + +/// Checks whether the expansion is the root expansion or a locally defined macro +pub fn expn_is_local(expn: ExpnId) -> bool { + if expn == ExpnId::root() { + return true; + } + let data = expn.expn_data(); + let backtrace = expn_backtrace(data.call_site); + std::iter::once((expn, data)) + .chain(backtrace) + .find_map(|(_, data)| data.macro_def_id) + .map_or(true, DefId::is_local) +} + +/// Returns an iterator of macro expansions that created the given span. +/// Note that desugaring expansions are skipped. +pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> { + expn_backtrace(span).filter_map(|(expn, data)| match data { + ExpnData { + kind: ExpnKind::Macro(kind, _), + macro_def_id: Some(def_id), + call_site: span, + .. + } => Some(MacroCall { + def_id, + kind, + expn, + span, + }), + _ => None, + }) +} + +/// If the macro backtrace of `span` has a macro call at the root expansion +/// (i.e. not a nested macro call), returns `Some` with the `MacroCall` +pub fn root_macro_call(span: Span) -> Option<MacroCall> { + macro_backtrace(span).last() +} + +/// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node" +/// produced by the macro call, as in [`first_node_in_macro`]. +pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> { + if first_node_in_macro(cx, node) != Some(ExpnId::root()) { + return None; + } + root_macro_call(node.span()) +} + +/// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the +/// macro call, as in [`first_node_in_macro`]. +pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> { + let span = node.span(); + first_node_in_macro(cx, node) + .into_iter() + .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn)) +} + +/// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the +/// macro call site (i.e. the parent of the macro expansion). This generally means that `node` +/// is the outermost node of an entire macro expansion, but there are some caveats noted below. +/// This is useful for finding macro calls while visiting the HIR without processing the macro call +/// at every node within its expansion. +/// +/// If you already have immediate access to the parent node, it is simpler to +/// just check the context of that span directly (e.g. `parent.span.from_expansion()`). +/// +/// If a macro call is in statement position, it expands to one or more statements. +/// In that case, each statement *and* their immediate descendants will all yield `Some` +/// with the `ExpnId` of the containing block. +/// +/// A node may be the "first node" of multiple macro calls in a macro backtrace. +/// The expansion of the outermost macro call site is returned in such cases. +pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> { + // get the macro expansion or return `None` if not found + // `macro_backtrace` importantly ignores desugaring expansions + let expn = macro_backtrace(node.span()).next()?.expn; + + // get the parent node, possibly skipping over a statement + // if the parent is not found, it is sensible to return `Some(root)` + let hir = cx.tcx.hir(); + let mut parent_iter = hir.parent_iter(node.hir_id()); + let (parent_id, _) = match parent_iter.next() { + None => return Some(ExpnId::root()), + Some((_, Node::Stmt(_))) => match parent_iter.next() { + None => return Some(ExpnId::root()), + Some(next) => next, + }, + Some(next) => next, + }; + + // get the macro expansion of the parent node + let parent_span = hir.span(parent_id); + let Some(parent_macro_call) = macro_backtrace(parent_span).next() else { + // the parent node is not in a macro + return Some(ExpnId::root()); + }; + + if parent_macro_call.expn.is_descendant_of(expn) { + // `node` is input to a macro call + return None; + } + + Some(parent_macro_call.expn) +} + +/* Specific Macro Utils */ + +/// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros +pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool { + let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false }; + matches!( + name.as_str(), + "core_panic_macro" + | "std_panic_macro" + | "core_panic_2015_macro" + | "std_panic_2015_macro" + | "core_panic_2021_macro" + ) +} + +pub enum PanicExpn<'a> { + /// No arguments - `panic!()` + Empty, + /// A string literal or any `&str` - `panic!("message")` or `panic!(message)` + Str(&'a Expr<'a>), + /// A single argument that implements `Display` - `panic!("{}", object)` + Display(&'a Expr<'a>), + /// Anything else - `panic!("error {}: {}", a, b)` + Format(FormatArgsExpn<'a>), +} + +impl<'a> PanicExpn<'a> { + pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> { + if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) { + return None; + } + let ExprKind::Call(callee, [arg]) = &expr.kind else { return None }; + let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None }; + let result = match path.segments.last().unwrap().ident.as_str() { + "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty, + "panic" | "panic_str" => Self::Str(arg), + "panic_display" => { + let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None }; + Self::Display(e) + }, + "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?), + _ => return None, + }; + Some(result) + } +} + +/// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion +pub fn find_assert_args<'a>( + cx: &LateContext<'_>, + expr: &'a Expr<'a>, + expn: ExpnId, +) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> { + find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p)) +} + +/// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro +/// expansion +pub fn find_assert_eq_args<'a>( + cx: &LateContext<'_>, + expr: &'a Expr<'a>, + expn: ExpnId, +) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> { + find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p)) +} + +fn find_assert_args_inner<'a, const N: usize>( + cx: &LateContext<'_>, + expr: &'a Expr<'a>, + expn: ExpnId, +) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> { + let macro_id = expn.expn_data().macro_def_id?; + let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") { + None => (expr, expn), + Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?, + }; + let mut args = ArrayVec::new(); + let mut panic_expn = None; + expr_visitor_no_bodies(|e| { + if args.is_full() { + if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() { + panic_expn = PanicExpn::parse(cx, e); + } + panic_expn.is_none() + } else if is_assert_arg(cx, e, expn) { + args.push(e); + false + } else { + true + } + }) + .visit_expr(expr); + let args = args.into_inner().ok()?; + // if no `panic!(..)` is found, use `PanicExpn::Empty` + // to indicate that the default assertion message is used + let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty); + Some((args, panic_expn)) +} + +fn find_assert_within_debug_assert<'a>( + cx: &LateContext<'_>, + expr: &'a Expr<'a>, + expn: ExpnId, + assert_name: Symbol, +) -> Option<(&'a Expr<'a>, ExpnId)> { + let mut found = None; + expr_visitor_no_bodies(|e| { + if found.is_some() || !e.span.from_expansion() { + return false; + } + let e_expn = e.span.ctxt().outer_expn(); + if e_expn == expn { + return true; + } + if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) { + found = Some((e, e_expn)); + } + false + }) + .visit_expr(expr); + found +} + +fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool { + if !expr.span.from_expansion() { + return true; + } + let result = macro_backtrace(expr.span).try_for_each(|macro_call| { + if macro_call.expn == assert_expn { + ControlFlow::Break(false) + } else { + match cx.tcx.item_name(macro_call.def_id) { + // `cfg!(debug_assertions)` in `debug_assert!` + sym::cfg => ControlFlow::CONTINUE, + // assert!(other_macro!(..)) + _ => ControlFlow::Break(true), + } + } + }); + match result { + ControlFlow::Break(is_assert_arg) => is_assert_arg, + ControlFlow::Continue(()) => true, + } +} + +/// A parsed `format_args!` expansion +#[derive(Debug)] +pub struct FormatArgsExpn<'tcx> { + /// Span of the first argument, the format string + pub format_string_span: Span, + /// The format string split by formatted args like `{..}` + pub format_string_parts: Vec<Symbol>, + /// Values passed after the format string + pub value_args: Vec<&'tcx Expr<'tcx>>, + /// Each element is a `value_args` index and a formatting trait (e.g. `sym::Debug`) + pub formatters: Vec<(usize, Symbol)>, + /// List of `fmt::v1::Argument { .. }` expressions. If this is empty, + /// then `formatters` represents the format args (`{..}`). + /// If this is non-empty, it represents the format args, and the `position` + /// parameters within the struct expressions are indexes of `formatters`. + pub specs: Vec<&'tcx Expr<'tcx>>, +} + +impl<'tcx> FormatArgsExpn<'tcx> { + /// Parses an expanded `format_args!` or `format_args_nl!` invocation + pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> { + macro_backtrace(expr.span).find(|macro_call| { + matches!( + cx.tcx.item_name(macro_call.def_id), + sym::const_format_args | sym::format_args | sym::format_args_nl + ) + })?; + let mut format_string_span: Option<Span> = None; + let mut format_string_parts: Vec<Symbol> = Vec::new(); + let mut value_args: Vec<&Expr<'_>> = Vec::new(); + let mut formatters: Vec<(usize, Symbol)> = Vec::new(); + let mut specs: Vec<&Expr<'_>> = Vec::new(); + expr_visitor_no_bodies(|e| { + // if we're still inside of the macro definition... + if e.span.ctxt() == expr.span.ctxt() { + // ArgumentV1::new_<format_trait>(<value>) + if_chain! { + if let ExprKind::Call(callee, [val]) = e.kind; + if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind; + if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind; + if path.segments.last().unwrap().ident.name == sym::ArgumentV1; + if seg.ident.name.as_str().starts_with("new_"); + then { + let val_idx = if_chain! { + if val.span.ctxt() == expr.span.ctxt(); + if let ExprKind::Field(_, field) = val.kind; + if let Ok(idx) = field.name.as_str().parse(); + then { + // tuple index + idx + } else { + // assume the value expression is passed directly + formatters.len() + } + }; + let fmt_trait = match seg.ident.name.as_str() { + "new_display" => "Display", + "new_debug" => "Debug", + "new_lower_exp" => "LowerExp", + "new_upper_exp" => "UpperExp", + "new_octal" => "Octal", + "new_pointer" => "Pointer", + "new_binary" => "Binary", + "new_lower_hex" => "LowerHex", + "new_upper_hex" => "UpperHex", + _ => unreachable!(), + }; + formatters.push((val_idx, Symbol::intern(fmt_trait))); + } + } + if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind { + if path.segments.last().unwrap().ident.name == sym::Argument { + specs.push(e); + } + } + // walk through the macro expansion + return true; + } + // assume that the first expr with a differing context represents + // (and has the span of) the format string + if format_string_span.is_none() { + format_string_span = Some(e.span); + let span = e.span; + // walk the expr and collect string literals which are format string parts + expr_visitor_no_bodies(|e| { + if e.span.ctxt() != span.ctxt() { + // defensive check, probably doesn't happen + return false; + } + if let ExprKind::Lit(lit) = &e.kind { + if let LitKind::Str(symbol, _s) = lit.node { + format_string_parts.push(symbol); + } + } + true + }) + .visit_expr(e); + } else { + // assume that any further exprs with a differing context are value args + value_args.push(e); + } + // don't walk anything not from the macro expansion (e.a. inputs) + false + }) + .visit_expr(expr); + Some(FormatArgsExpn { + format_string_span: format_string_span?, + format_string_parts, + value_args, + formatters, + specs, + }) + } + + /// Finds a nested call to `format_args!` within a `format!`-like macro call + pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> { + let mut format_args = None; + expr_visitor_no_bodies(|e| { + if format_args.is_some() { + return false; + } + let e_ctxt = e.span.ctxt(); + if e_ctxt == expr.span.ctxt() { + return true; + } + if e_ctxt.outer_expn().is_descendant_of(expn_id) { + format_args = FormatArgsExpn::parse(cx, e); + } + false + }) + .visit_expr(expr); + format_args + } + + /// Returns a vector of `FormatArgsArg`. + pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> { + if self.specs.is_empty() { + let args = std::iter::zip(&self.value_args, &self.formatters) + .map(|(value, &(_, format_trait))| FormatArgsArg { + value, + format_trait, + spec: None, + }) + .collect(); + return Some(args); + } + self.specs + .iter() + .map(|spec| { + if_chain! { + // struct `core::fmt::rt::v1::Argument` + if let ExprKind::Struct(_, fields, _) = spec.kind; + if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position); + if let ExprKind::Lit(lit) = &position_field.expr.kind; + if let LitKind::Int(position, _) = lit.node; + if let Ok(i) = usize::try_from(position); + if let Some(&(j, format_trait)) = self.formatters.get(i); + then { + Some(FormatArgsArg { + value: self.value_args[j], + format_trait, + spec: Some(spec), + }) + } else { + None + } + } + }) + .collect() + } + + /// Source callsite span of all inputs + pub fn inputs_span(&self) -> Span { + match *self.value_args { + [] => self.format_string_span, + [.., last] => self + .format_string_span + .to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())), + } + } +} + +/// Type representing a `FormatArgsExpn`'s format arguments +pub struct FormatArgsArg<'tcx> { + /// An element of `value_args` according to `position` + pub value: &'tcx Expr<'tcx>, + /// An element of `args` according to `position` + pub format_trait: Symbol, + /// An element of `specs` + pub spec: Option<&'tcx Expr<'tcx>>, +} + +impl<'tcx> FormatArgsArg<'tcx> { + /// Returns true if any formatting parameters are used that would have an effect on strings, + /// like `{:+2}` instead of just `{}`. + pub fn has_string_formatting(&self) -> bool { + self.spec.map_or(false, |spec| { + // `!` because these conditions check that `self` is unformatted. + !if_chain! { + // struct `core::fmt::rt::v1::Argument` + if let ExprKind::Struct(_, fields, _) = spec.kind; + if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format); + // struct `core::fmt::rt::v1::FormatSpec` + if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind; + if subfields.iter().all(|field| match field.ident.name { + sym::precision | sym::width => match field.expr.kind { + ExprKind::Path(QPath::Resolved(_, path)) => { + path.segments.last().unwrap().ident.name == sym::Implied + } + _ => false, + } + _ => true, + }); + then { true } else { false } + } + }) + } +} + +/// A node with a `HirId` and a `Span` +pub trait HirNode { + fn hir_id(&self) -> HirId; + fn span(&self) -> Span; +} + +macro_rules! impl_hir_node { + ($($t:ident),*) => { + $(impl HirNode for hir::$t<'_> { + fn hir_id(&self) -> HirId { + self.hir_id + } + fn span(&self) -> Span { + self.span + } + })* + }; +} + +impl_hir_node!(Expr, Pat); + +impl HirNode for hir::Item<'_> { + fn hir_id(&self) -> HirId { + self.hir_id() + } + + fn span(&self) -> Span { + self.span + } +} diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs new file mode 100644 index 000000000..9e238c6f1 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/msrvs.rs @@ -0,0 +1,39 @@ +use rustc_semver::RustcVersion; + +macro_rules! msrv_aliases { + ($($major:literal,$minor:literal,$patch:literal { + $($name:ident),* $(,)? + })*) => { + $($( + pub const $name: RustcVersion = RustcVersion::new($major, $minor, $patch); + )*)* + }; +} + +// names may refer to stabilized feature flags or library items +msrv_aliases! { + 1,62,0 { BOOL_THEN_SOME } + 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN } + 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } + 1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS } + 1,50,0 { BOOL_THEN } + 1,47,0 { TAU } + 1,46,0 { CONST_IF_MATCH } + 1,45,0 { STR_STRIP_PREFIX } + 1,43,0 { LOG2_10, LOG10_2 } + 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS } + 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE } + 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF } + 1,38,0 { POINTER_CAST, REM_EUCLID } + 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS } + 1,36,0 { ITERATOR_COPIED } + 1,35,0 { OPTION_COPIED, RANGE_CONTAINS } + 1,34,0 { TRY_FROM } + 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } + 1,28,0 { FROM_BOOL } + 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } + 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } + 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } + 1,16,0 { STR_REPEAT } + 1,24,0 { IS_ASCII_DIGIT } +} diff --git a/src/tools/clippy/clippy_utils/src/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs new file mode 100644 index 000000000..3fb5415ce --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs @@ -0,0 +1,248 @@ +use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind}; +use std::iter; + +#[derive(Debug, PartialEq, Eq, 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>> { + let unsigned_src = src.strip_prefix('-').map_or(src, |s| s); + if lit_kind.is_numeric() + && unsigned_src + .trim_start() + .chars() + .next() + .map_or(false, |c| c.is_ascii_digit()) + { + 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 radix == Radix::Decimal { + (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 { + if exponent != "0" { + output.push_str(separator); + Self::group_digits(&mut output, exponent, group_size, true, false); + } + } + + if let Some(suffix) = self.suffix { + if output.ends_with('.') { + output.push('0'); + } + 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 != '_'); + + // The exponent may have a sign, output it early, otherwise it will be + // treated as a digit + if digits.clone().next() == Some('-') { + let _ = digits.next(); + output.push('-'); + } + + 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 iter::zip(digits, (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) +} diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs new file mode 100644 index 000000000..05429d05d --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -0,0 +1,196 @@ +//! 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. + +#[cfg(feature = "internal")] +pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"]; +#[cfg(feature = "internal")] +pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [ + ["rustc_lint_defs", "Applicability", "Unspecified"], + ["rustc_lint_defs", "Applicability", "HasPlaceholders"], + ["rustc_lint_defs", "Applicability", "MaybeIncorrect"], + ["rustc_lint_defs", "Applicability", "MachineApplicable"], +]; +#[cfg(feature = "internal")] +pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"]; +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 const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; +pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"]; +pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; +pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"]; +pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; +pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; +pub const CORE_ITER_COLLECT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "collect"]; +pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"]; +pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"]; +pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"]; +pub const CORE_ITER_INTO_ITER: [&str; 6] = ["core", "iter", "traits", "collect", "IntoIterator", "into_iter"]; +pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"]; +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"]; +/// Preferably use the diagnostic item `sym::deref_method` where possible +pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; +pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"]; +pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"]; +#[cfg(feature = "internal")] +pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"]; +#[cfg(feature = "internal")] +pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"]; +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 FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"]; +pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"]; +pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"]; +pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"]; +pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"]; +pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; +pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"]; +pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"]; +#[cfg(feature = "internal")] +pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"]; +#[cfg(feature = "internal")] +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 IO_READ: [&str; 3] = ["std", "io", "Read"]; +pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"]; +pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"]; +pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"]; +pub const ITER_COUNT: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "count"]; +pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"]; +pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"]; +#[cfg(feature = "internal")] +pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; +#[cfg(feature = "internal")] +pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"]; +#[cfg(feature = "internal")] +pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"]; +#[cfg(feature = "internal")] +pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"]; +pub const MEM_SWAP: [&str; 3] = ["core", "mem", "swap"]; +pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"]; +pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"]; +/// Preferably use the diagnostic item `sym::Option` where possible +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_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 const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"]; +pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"]; +pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"]; +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 PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"]; +#[cfg_attr(not(unix), allow(clippy::invalid_paths))] +pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"]; +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_COPY: [&str; 3] = ["core", "intrinsics", "copy"]; +pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"]; +pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"]; +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 PTR_READ: [&str; 3] = ["core", "ptr", "read"]; +pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"]; +pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"]; +pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"]; +pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"]; +pub const PTR_UNALIGNED_VOLATILE_LOAD: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_load"]; +pub const PTR_UNALIGNED_VOLATILE_STORE: [&str; 3] = ["core", "intrinsics", "unaligned_volatile_store"]; +pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"]; +pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"]; +pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"]; +pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"]; +pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"]; +pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"]; +pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"]; +pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"]; +pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"]; +/// Preferably use the diagnostic item `sym::Result` where possible +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"]; +#[cfg(feature = "internal")] +pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"]; +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_GET: [&str; 4] = ["core", "slice", "<impl [T]>", "get"]; +pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"]; +pub const SLICE_INTO: [&str; 4] = ["core", "slice", "<impl [T]>", "iter"]; +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 CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"]; +pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"]; +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 STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"]; +pub const STR_BYTES: [&str; 4] = ["core", "str", "<impl str>", "bytes"]; +pub const STR_CHARS: [&str; 4] = ["core", "str", "<impl str>", "chars"]; +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_FROM_UTF8_UNCHECKED: [&str; 4] = ["core", "str", "converts", "from_utf8_unchecked"]; +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")] +pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"]; +#[cfg(feature = "internal")] +pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"]; +#[cfg(feature = "internal")] +pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"]; +#[cfg(feature = "internal")] +pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"]; +#[cfg(feature = "internal")] +pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"]; +#[cfg(feature = "internal")] +pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]; +pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"]; +pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"]; +#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates +pub const TOKIO_IO_ASYNCWRITEEXT: [&str; 5] = ["tokio", "io", "util", "async_write_ext", "AsyncWriteExt"]; +pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"]; +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_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"]; +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 PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"]; diff --git a/src/tools/clippy/clippy_utils/src/ptr.rs b/src/tools/clippy/clippy_utils/src/ptr.rs new file mode 100644 index 000000000..649b7b994 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ptr.rs @@ -0,0 +1,57 @@ +use crate::source::snippet; +use crate::visitors::expr_visitor_no_bodies; +use crate::{path_to_local_id, strip_pat_refs}; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind}; +use rustc_lint::LateContext; +use rustc_span::Span; +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)) { + if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind { + extract_clone_suggestions(cx, binding_id, replacements, body) + } else { + Some(vec![]) + } + } else { + Some(vec![]) + } +} + +fn extract_clone_suggestions<'tcx>( + cx: &LateContext<'tcx>, + id: HirId, + replace: &[(&'static str, &'static str)], + body: &'tcx Body<'_>, +) -> Option<Vec<(Span, Cow<'static, str>)>> { + let mut abort = false; + let mut spans = Vec::new(); + expr_visitor_no_bodies(|expr| { + if abort { + return false; + } + if let ExprKind::MethodCall(seg, [recv], _) = expr.kind { + if path_to_local_id(recv, id) { + if seg.ident.name.as_str() == "capacity" { + abort = true; + return false; + } + for &(fn_name, suffix) in replace { + if seg.ident.name.as_str() == fn_name { + spans.push((expr.span, snippet(cx, recv.span, "_") + suffix)); + return false; + } + } + } + } + !abort + }) + .visit_body(body); + if abort { None } else { Some(spans) } +} diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs new file mode 100644 index 000000000..3bf75bcbe --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -0,0 +1,371 @@ +// This code used to be a part of `rustc` but moved to Clippy as a result of +// https://github.com/rust-lang/rust/issues/76618. Because of that, it contains unused code and some +// of terminologies might not be relevant in the context of Clippy. Note that its behavior might +// differ from the time of `rustc` even if the name stays the same. + +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_semver::RustcVersion; +use rustc_span::symbol::sym; +use rustc_span::Span; +use std::borrow::Cow; + +type McfResult = Result<(), (Span, Cow<'static, str>)>; + +pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<RustcVersion>) -> 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::Trait(..) + | 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::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate), + } + } + 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(), msrv)?; + for stmt in &bb.statements { + check_statement(tcx, body, def_id, stmt)?; + } + } + Ok(()) +} + +fn check_ty<'tcx>(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>( + 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::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => { + check_place(tcx, *place, span, body) + }, + Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body), + Rvalue::Repeat(operand, _) + | Rvalue::Use(operand) + | Rvalue::Cast( + CastKind::PointerFromExposedAddress + | CastKind::Misc + | 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())) + } + }, + Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => { + Err((span, "casting pointers to ints is unstable in const fn".into())) + }, + // binops are fine on integers + Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (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 | NullOp::AlignOf, _) | Rvalue::ShallowInitBox(_, _) => Ok(()), + 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>( + 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(box (_, place)) => check_place(tcx, *place, span, body), + // just an assignment + StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => { + check_place(tcx, **place, span, body) + }, + + StatementKind::CopyNonOverlapping(box rustc_middle::mir::CopyNonOverlapping { dst, src, count }) => { + check_operand(tcx, dst, span, body)?; + check_operand(tcx, src, span, body)?; + check_operand(tcx, count, span, body) + }, + // These are all NOPs + StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Retag { .. } + | StatementKind::AscribeUserType(..) + | StatementKind::Coverage(..) + | StatementKind::Nop => Ok(()), + } +} + +fn check_operand<'tcx>(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>(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<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + terminator: &Terminator<'tcx>, + msrv: Option<RustcVersion>, +) -> 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: _, + target: _, + cleanup: _, + fn_span: _, + } => { + let fn_ty = func.ty(body, tcx); + if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { + if !is_const_fn(tcx, fn_def_id, msrv) { + 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.is_intrinsic(fn_def_id) && 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())), + } +} + +fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<RustcVersion>) -> bool { + tcx.is_const_fn(def_id) + && tcx.lookup_const_stability(def_id).map_or(true, |const_stab| { + if let rustc_attr::StabilityLevel::Stable { since, .. } = const_stab.level { + // Checking MSRV is manually necessary because `rustc` has no such concept. This entire + // function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`. + // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262. + crate::meets_msrv( + msrv, + RustcVersion::parse(since.as_str()) + .expect("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted"), + ) + } else { + // Unstable const fn with the feature enabled. + msrv.is_none() + } + }) +} diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs new file mode 100644 index 000000000..1197fe914 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/source.rs @@ -0,0 +1,508 @@ +//! Utils for extracting, inspecting or transforming source code + +#![allow(clippy::module_name_repetitions)] + +use crate::line_span; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_span::hygiene; +use rustc_span::source_map::SourceMap; +use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext}; +use std::borrow::Cow; + +/// Checks if the span starts with the given text. This will return false if the span crosses +/// multiple files or if source is not available. +/// +/// This is used to check for proc macros giving unhelpful spans to things. +pub fn span_starts_with<T: LintContext>(cx: &T, span: Span, text: &str) -> bool { + fn helper(sm: &SourceMap, span: Span, text: &str) -> bool { + let pos = sm.lookup_byte_offset(span.lo()); + let Some(ref src) = pos.sf.src else { + return false; + }; + let end = span.hi() - pos.sf.start_pos; + src.get(pos.pos.0 as usize..end.0 as usize) + // Expression spans can include wrapping parenthesis. Remove them first. + .map_or(false, |s| s.trim_start_matches('(').starts_with(text)) + } + helper(cx.sess().source_map(), span, text) +} + +/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. +/// Also takes an `Option<String>` which can be put inside the braces. +pub fn expr_block<'a, T: LintContext>( + cx: &T, + expr: &Expr<'_>, + option: Option<String>, + default: &'a str, + indent_relative_to: Option<Span>, +) -> Cow<'a, str> { + let code = snippet_block(cx, expr.span, default, indent_relative_to); + let string = option.unwrap_or_default(); + if expr.span.from_expansion() { + Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default))) + } else if let ExprKind::Block(_, _) = expr.kind { + Cow::Owned(format!("{}{}", code, string)) + } else if string.is_empty() { + Cow::Owned(format!("{{ {} }}", code)) + } else { + Cow::Owned(format!("{{\n{};\n{}\n}}", code, string)) + } +} + +/// Returns a new Span that extends the original Span to the first non-whitespace char of the first +/// line. +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ +/// // will be converted to +/// let x = (); +/// // ^^^^^^^^^^ +/// ``` +pub fn first_line_of_span<T: LintContext>(cx: &T, span: Span) -> Span { + first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos)) +} + +fn first_char_in_first_line<T: LintContext>(cx: &T, span: Span) -> Option<BytePos> { + let line_span = line_span(cx, span); + snippet_opt(cx, line_span).and_then(|snip| { + snip.find(|c: char| !c.is_whitespace()) + .map(|pos| line_span.lo() + BytePos::from_usize(pos)) + }) +} + +/// Returns the indentation of the line of a span +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ -- will return 0 +/// let x = (); +/// // ^^ -- will return 4 +/// ``` +pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> { + snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace())) +} + +/// Gets a snippet of the indentation of the line of a span +pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> { + snippet_opt(cx, line_span(cx, span)).map(|mut s| { + let len = s.len() - s.trim_start().len(); + s.truncate(len); + s + }) +} + +// If the snippet is empty, it's an attribute that was inserted during macro +// expansion and we want to ignore those, because they could come from external +// sources that the user has no control over. +// For some reason these attributes don't have any expansion info on them, so +// we have to check it this way until there is a better way. +pub fn is_present_in_source<T: LintContext>(cx: &T, span: Span) -> bool { + if let Some(snippet) = snippet_opt(cx, span) { + if snippet.is_empty() { + return false; + } + } + true +} + +/// Returns the position just before rarrow +/// +/// ```rust,ignore +/// fn into(self) -> () {} +/// ^ +/// // in case of unformatted code +/// fn into2(self)-> () {} +/// ^ +/// fn into3(self) -> () {} +/// ^ +/// ``` +pub fn position_before_rarrow(s: &str) -> Option<usize> { + s.rfind("->").map(|rpos| { + let mut rpos = rpos; + let chars: Vec<char> = s.chars().collect(); + while rpos > 1 { + if let Some(c) = chars.get(rpos - 1) { + if c.is_whitespace() { + rpos -= 1; + continue; + } + } + break; + } + rpos + }) +} + +/// Reindent a multiline string with possibility of ignoring the first line. +#[expect(clippy::needless_pass_by_value)] +pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> { + let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' '); + let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t'); + reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into() +} + +fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String { + let x = s + .lines() + .skip(usize::from(ignore_first)) + .filter_map(|l| { + if l.is_empty() { + None + } else { + // ignore empty lines + Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0) + } + }) + .min() + .unwrap_or(0); + let indent = indent.unwrap_or(0); + s.lines() + .enumerate() + .map(|(i, l)| { + if (ignore_first && i == 0) || l.is_empty() { + l.to_owned() + } else if x > indent { + l.split_at(x - indent).1.to_owned() + } else { + " ".repeat(indent - x) + l + } + }) + .collect::<Vec<String>>() + .join("\n") +} + +/// Converts a span to a code snippet if available, otherwise returns the default. +/// +/// This is useful if you want to provide suggestions for your lint or more generally, if you want +/// to convert a given `Span` to a `str`. To create suggestions consider using +/// [`snippet_with_applicability`] to ensure that the applicability stays correct. +/// +/// # Example +/// ```rust,ignore +/// // Given two spans one for `value` and one for the `init` expression. +/// let value = Vec::new(); +/// // ^^^^^ ^^^^^^^^^^ +/// // span1 span2 +/// +/// // The snipped call would return the corresponding code snippet +/// snippet(cx, span1, "..") // -> "value" +/// snippet(cx, span2, "..") // -> "Vec::new()" +/// ``` +pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { + snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from) +} + +/// Same as [`snippet`], 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 snippet_with_applicability<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + applicability: &mut Applicability, +) -> Cow<'a, str> { + if *applicability != Applicability::Unspecified && span.from_expansion() { + *applicability = Applicability::MaybeIncorrect; + } + snippet_opt(cx, span).map_or_else( + || { + if *applicability == Applicability::MachineApplicable { + *applicability = Applicability::HasPlaceholders; + } + Cow::Borrowed(default) + }, + From::from, + ) +} + +/// Same as `snippet`, but should only be used when it's clear that the input span is +/// not a macro argument. +pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { + snippet(cx, span.source_callsite(), default) +} + +/// Converts a span to a code snippet. Returns `None` if not available. +pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> { + cx.sess().source_map().span_to_snippet(span).ok() +} + +/// Converts a span (from a block) to a code snippet if available, otherwise use default. +/// +/// This trims the code of indentation, except for the first line. Use it for blocks or block-like +/// things which need to be printed as such. +/// +/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the +/// resulting snippet of the given span. +/// +/// # Example +/// +/// ```rust,ignore +/// snippet_block(cx, block.span, "..", None) +/// // where, `block` is the block of the if expr +/// if x { +/// y; +/// } +/// // will return the snippet +/// { +/// y; +/// } +/// ``` +/// +/// ```rust,ignore +/// snippet_block(cx, block.span, "..", Some(if_expr.span)) +/// // where, `block` is the block of the if expr +/// if x { +/// y; +/// } +/// // will return the snippet +/// { +/// y; +/// } // aligned with `if` +/// ``` +/// Note that the first line of the snippet always has 0 indentation. +pub fn snippet_block<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + indent_relative_to: Option<Span>, +) -> Cow<'a, str> { + let snip = snippet(cx, span, default); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + reindent_multiline(snip, true, indent) +} + +/// Same as `snippet_block`, but adapts the applicability level by the rules of +/// `snippet_with_applicability`. +pub fn snippet_block_with_applicability<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + indent_relative_to: Option<Span>, + applicability: &mut Applicability, +) -> Cow<'a, str> { + let snip = snippet_with_applicability(cx, span, default, applicability); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + reindent_multiline(snip, true, indent) +} + +/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This +/// will result in the macro call, rather then the expansion, if the span is from a child context. +/// If the span is not from a child context, it will be used directly instead. +/// +/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node +/// would result in `box []`. If given the context of the address of expression, this function will +/// correctly get a snippet of `vec![]`. +/// +/// This will also return whether or not the snippet is a macro call. +pub fn snippet_with_context<'a>( + cx: &LateContext<'_>, + span: Span, + outer: SyntaxContext, + default: &'a str, + applicability: &mut Applicability, +) -> (Cow<'a, str>, bool) { + let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else( + || { + // The span is from a macro argument, and the outer context is the macro using the argument + if *applicability != Applicability::Unspecified { + *applicability = Applicability::MaybeIncorrect; + } + // TODO: get the argument span. + (span, false) + }, + |outer_span| (outer_span, span.ctxt() != outer), + ); + + ( + snippet_with_applicability(cx, span, default, applicability), + is_macro_call, + ) +} + +/// Walks the span up to the target context, thereby returning the macro call site if the span is +/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the +/// case of the span being in a macro expansion, but the target context is from expanding a macro +/// argument. +/// +/// Given the following +/// +/// ```rust,ignore +/// macro_rules! m { ($e:expr) => { f($e) }; } +/// g(m!(0)) +/// ``` +/// +/// If called with a span of the call to `f` and a context of the call to `g` this will return a +/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span +/// containing `0` as the context is the same as the outer context. +/// +/// This will traverse through multiple macro calls. Given the following: +/// +/// ```rust,ignore +/// macro_rules! m { ($e:expr) => { n!($e, 0) }; } +/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; } +/// g(m!(0)) +/// ``` +/// +/// If called with a span of the call to `f` and a context of the call to `g` this will return a +/// span containing `m!(0)`. +pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> { + let outer_span = hygiene::walk_chain(span, outer); + (outer_span.ctxt() == outer).then_some(outer_span) +} + +/// Removes block comments from the given `Vec` of lines. +/// +/// # Examples +/// +/// ```rust,ignore +/// without_block_comments(vec!["/*", "foo", "*/"]); +/// // => vec![] +/// +/// without_block_comments(vec!["bar", "/*", "foo", "*/"]); +/// // => vec!["bar"] +/// ``` +pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> { + let mut without = vec![]; + + let mut nest_level = 0; + + for line in lines { + if line.contains("/*") { + nest_level += 1; + continue; + } else if line.contains("*/") { + nest_level -= 1; + continue; + } + + if nest_level == 0 { + without.push(line); + } + } + + without +} + +/// Trims the whitespace from the start and the end of the span. +pub fn trim_span(sm: &SourceMap, span: Span) -> Span { + let data = span.data(); + let sf: &_ = &sm.lookup_source_file(data.lo); + let Some(src) = sf.src.as_deref() else { + return span; + }; + let Some(snip) = &src.get((data.lo - sf.start_pos).to_usize()..(data.hi - sf.start_pos).to_usize()) else { + return span; + }; + let trim_start = snip.len() - snip.trim_start().len(); + let trim_end = snip.len() - snip.trim_end().len(); + SpanData { + lo: data.lo + BytePos::from_usize(trim_start), + hi: data.hi - BytePos::from_usize(trim_end), + ctxt: data.ctxt, + parent: data.parent, + } + .span() +} + +#[cfg(test)] +mod test { + use super::{reindent_multiline, without_block_comments}; + + #[test] + fn test_reindent_multiline_single_line() { + assert_eq!("", reindent_multiline("".into(), false, None)); + assert_eq!("...", reindent_multiline("...".into(), false, None)); + assert_eq!("...", reindent_multiline(" ...".into(), false, None)); + assert_eq!("...", reindent_multiline("\t...".into(), false, None)); + assert_eq!("...", reindent_multiline("\t\t...".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_reindent_multiline_block() { + assert_eq!("\ + if x { + y + } else { + z + }", reindent_multiline(" if x { + y + } else { + z + }".into(), false, None)); + assert_eq!("\ + if x { + \ty + } else { + \tz + }", reindent_multiline(" if x { + \ty + } else { + \tz + }".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_reindent_multiline_empty_line() { + assert_eq!("\ + if x { + y + + } else { + z + }", reindent_multiline(" if x { + y + + } else { + z + }".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_reindent_multiline_lines_deeper() { + assert_eq!("\ + if x { + y + } else { + z + }", reindent_multiline("\ + if x { + y + } else { + z + }".into(), true, Some(8))); + } + + #[test] + fn test_without_block_comments_lines_without_block_comments() { + let result = without_block_comments(vec!["/*", "", "*/"]); + println!("result: {:?}", result); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]); + assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]); + + let result = without_block_comments(vec!["/* rust", "", "*/"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* one-line comment */"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["foo", "bar", "baz"]); + assert_eq!(result, vec!["foo", "bar", "baz"]); + } +} diff --git a/src/tools/clippy/clippy_utils/src/str_utils.rs b/src/tools/clippy/clippy_utils/src/str_utils.rs new file mode 100644 index 000000000..03a9d3c25 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/str_utils.rs @@ -0,0 +1,325 @@ +/// Dealing with sting indices can be hard, this struct ensures that both the +/// character and byte index are provided for correct indexing. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct StrIndex { + pub char_index: usize, + pub byte_index: usize, +} + +impl StrIndex { + pub fn new(char_index: usize, byte_index: usize) -> Self { + Self { char_index, byte_index } + } +} + +/// Returns the index of the character after the first camel-case component of `s`. +/// +/// ``` +/// # use clippy_utils::str_utils::{camel_case_until, StrIndex}; +/// assert_eq!(camel_case_until("AbcDef"), StrIndex::new(6, 6)); +/// assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_until("AbcDD"), StrIndex::new(3, 3)); +/// assert_eq!(camel_case_until("Abc\u{f6}\u{f6}DD"), StrIndex::new(5, 7)); +/// ``` +#[must_use] +pub fn camel_case_until(s: &str) -> StrIndex { + let mut iter = s.char_indices().enumerate(); + if let Some((_char_index, (_, first))) = iter.next() { + if !first.is_uppercase() { + return StrIndex::new(0, 0); + } + } else { + return StrIndex::new(0, 0); + } + let mut up = true; + let mut last_index = StrIndex::new(0, 0); + for (char_index, (byte_index, c)) in iter { + if up { + if c.is_lowercase() { + up = false; + } else { + return last_index; + } + } else if c.is_uppercase() { + up = true; + last_index.byte_index = byte_index; + last_index.char_index = char_index; + } else if !c.is_lowercase() { + return StrIndex::new(char_index, byte_index); + } + } + + if up { + last_index + } else { + StrIndex::new(s.chars().count(), s.len()) + } +} + +/// Returns index of the first camel-case component of `s`. +/// +/// ``` +/// # use clippy_utils::str_utils::{camel_case_start, StrIndex}; +/// assert_eq!(camel_case_start("AbcDef"), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_start("abcDef"), StrIndex::new(3, 3)); +/// assert_eq!(camel_case_start("ABCD"), StrIndex::new(4, 4)); +/// assert_eq!(camel_case_start("abcd"), StrIndex::new(4, 4)); +/// assert_eq!(camel_case_start("\u{f6}\u{f6}cd"), StrIndex::new(4, 6)); +/// ``` +#[must_use] +pub fn camel_case_start(s: &str) -> StrIndex { + camel_case_start_from_idx(s, 0) +} + +/// Returns `StrIndex` of the last camel-case component of `s[idx..]`. +/// +/// ``` +/// # use clippy_utils::str_utils::{camel_case_start_from_idx, StrIndex}; +/// assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3)); +/// assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0)); +/// assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3)); +/// assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7)); +/// ``` +pub fn camel_case_start_from_idx(s: &str, start_idx: usize) -> StrIndex { + let char_count = s.chars().count(); + let range = 0..char_count; + let mut iter = range.rev().zip(s.char_indices().rev()); + if let Some((_, (_, first))) = iter.next() { + if !first.is_lowercase() { + return StrIndex::new(char_count, s.len()); + } + } else { + return StrIndex::new(char_count, s.len()); + } + + let mut down = true; + let mut last_index = StrIndex::new(char_count, s.len()); + for (char_index, (byte_index, c)) in iter { + if byte_index < start_idx { + break; + } + if down { + if c.is_uppercase() { + down = false; + last_index.byte_index = byte_index; + last_index.char_index = char_index; + } else if !c.is_lowercase() { + return last_index; + } + } else if c.is_lowercase() { + down = true; + } else if c.is_uppercase() { + last_index.byte_index = byte_index; + last_index.char_index = char_index; + } else { + return last_index; + } + } + + last_index +} + +/// Get the indexes of camel case components of a string `s` +/// +/// ``` +/// # use clippy_utils::str_utils::{camel_case_indices, StrIndex}; +/// assert_eq!( +/// camel_case_indices("AbcDef"), +/// vec![StrIndex::new(0, 0), StrIndex::new(3, 3), StrIndex::new(6, 6)] +/// ); +/// assert_eq!( +/// camel_case_indices("abcDef"), +/// vec![StrIndex::new(3, 3), StrIndex::new(6, 6)] +/// ); +/// ``` +pub fn camel_case_indices(s: &str) -> Vec<StrIndex> { + let mut result = Vec::new(); + let mut str_idx = camel_case_start(s); + + while str_idx.byte_index < s.len() { + let next_idx = str_idx.byte_index + 1; + result.push(str_idx); + str_idx = camel_case_start_from_idx(s, next_idx); + } + result.push(str_idx); + + result +} + +/// Split camel case string into a vector of its components +/// +/// ``` +/// # use clippy_utils::str_utils::{camel_case_split, StrIndex}; +/// assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); +/// ``` +pub fn camel_case_split(s: &str) -> Vec<&str> { + let mut offsets = camel_case_indices(s) + .iter() + .map(|e| e.byte_index) + .collect::<Vec<usize>>(); + if offsets[0] != 0 { + offsets.insert(0, 0); + } + + offsets.windows(2).map(|w| &s[w[0]..w[1]]).collect() +} + +/// Dealing with sting comparison can be complicated, this struct ensures that both the +/// character and byte count are provided for correct indexing. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct StrCount { + pub char_count: usize, + pub byte_count: usize, +} + +impl StrCount { + pub fn new(char_count: usize, byte_count: usize) -> Self { + Self { char_count, byte_count } + } +} + +/// Returns the number of chars that match from the start +/// +/// ``` +/// # use clippy_utils::str_utils::{count_match_start, StrCount}; +/// assert_eq!(count_match_start("hello_mouse", "hello_penguin"), StrCount::new(6, 6)); +/// assert_eq!(count_match_start("hello_clippy", "bye_bugs"), StrCount::new(0, 0)); +/// assert_eq!(count_match_start("hello_world", "hello_world"), StrCount::new(11, 11)); +/// assert_eq!(count_match_start("T\u{f6}ffT\u{f6}ff", "T\u{f6}ff"), StrCount::new(4, 5)); +/// ``` +#[must_use] +pub fn count_match_start(str1: &str, str2: &str) -> StrCount { + // (char_index, char1) + let char_count = str1.chars().count(); + let iter1 = (0..=char_count).zip(str1.chars()); + // (byte_index, char2) + let iter2 = str2.char_indices(); + + iter1 + .zip(iter2) + .take_while(|((_, c1), (_, c2))| c1 == c2) + .last() + .map_or_else(StrCount::default, |((char_index, _), (byte_index, character))| { + StrCount::new(char_index + 1, byte_index + character.len_utf8()) + }) +} + +/// Returns the number of chars and bytes that match from the end +/// +/// ``` +/// # use clippy_utils::str_utils::{count_match_end, StrCount}; +/// assert_eq!(count_match_end("hello_cat", "bye_cat"), StrCount::new(4, 4)); +/// assert_eq!(count_match_end("if_item_thing", "enum_value"), StrCount::new(0, 0)); +/// assert_eq!(count_match_end("Clippy", "Clippy"), StrCount::new(6, 6)); +/// assert_eq!(count_match_end("MyT\u{f6}ff", "YourT\u{f6}ff"), StrCount::new(4, 5)); +/// ``` +#[must_use] +pub fn count_match_end(str1: &str, str2: &str) -> StrCount { + let char_count = str1.chars().count(); + if char_count == 0 { + return StrCount::default(); + } + + // (char_index, char1) + let iter1 = (0..char_count).rev().zip(str1.chars().rev()); + // (byte_index, char2) + let byte_count = str2.len(); + let iter2 = str2.char_indices().rev(); + + iter1 + .zip(iter2) + .take_while(|((_, c1), (_, c2))| c1 == c2) + .last() + .map_or_else(StrCount::default, |((char_index, _), (byte_index, _))| { + StrCount::new(char_count - char_index, byte_count - byte_index) + }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn camel_case_start_full() { + assert_eq!(camel_case_start("AbcDef"), StrIndex::new(0, 0)); + assert_eq!(camel_case_start("Abc"), StrIndex::new(0, 0)); + assert_eq!(camel_case_start("ABcd"), StrIndex::new(0, 0)); + assert_eq!(camel_case_start("ABcdEf"), StrIndex::new(0, 0)); + assert_eq!(camel_case_start("AabABcd"), StrIndex::new(0, 0)); + } + + #[test] + fn camel_case_start_partial() { + assert_eq!(camel_case_start("abcDef"), StrIndex::new(3, 3)); + assert_eq!(camel_case_start("aDbc"), StrIndex::new(1, 1)); + assert_eq!(camel_case_start("aabABcd"), StrIndex::new(3, 3)); + assert_eq!(camel_case_start("\u{f6}\u{f6}AabABcd"), StrIndex::new(2, 4)); + } + + #[test] + fn camel_case_start_not() { + assert_eq!(camel_case_start("AbcDef_"), StrIndex::new(7, 7)); + assert_eq!(camel_case_start("AbcDD"), StrIndex::new(5, 5)); + assert_eq!(camel_case_start("all_small"), StrIndex::new(9, 9)); + assert_eq!(camel_case_start("\u{f6}_all_small"), StrIndex::new(11, 12)); + } + + #[test] + fn camel_case_start_caps() { + assert_eq!(camel_case_start("ABCD"), StrIndex::new(4, 4)); + } + + #[test] + fn camel_case_until_full() { + assert_eq!(camel_case_until("AbcDef"), StrIndex::new(6, 6)); + assert_eq!(camel_case_until("Abc"), StrIndex::new(3, 3)); + assert_eq!(camel_case_until("Abc\u{f6}\u{f6}\u{f6}"), StrIndex::new(6, 9)); + } + + #[test] + fn camel_case_until_not() { + assert_eq!(camel_case_until("abcDef"), StrIndex::new(0, 0)); + assert_eq!(camel_case_until("aDbc"), StrIndex::new(0, 0)); + } + + #[test] + fn camel_case_until_partial() { + assert_eq!(camel_case_until("AbcDef_"), StrIndex::new(6, 6)); + assert_eq!(camel_case_until("CallTypeC"), StrIndex::new(8, 8)); + assert_eq!(camel_case_until("AbcDD"), StrIndex::new(3, 3)); + assert_eq!(camel_case_until("Abc\u{f6}\u{f6}DD"), StrIndex::new(5, 7)); + } + + #[test] + fn until_caps() { + assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0)); + } + + #[test] + fn camel_case_start_from_idx_full() { + assert_eq!(camel_case_start_from_idx("AbcDef", 0), StrIndex::new(0, 0)); + assert_eq!(camel_case_start_from_idx("AbcDef", 1), StrIndex::new(3, 3)); + assert_eq!(camel_case_start_from_idx("AbcDef", 4), StrIndex::new(6, 6)); + assert_eq!(camel_case_start_from_idx("AbcDefGhi", 0), StrIndex::new(0, 0)); + assert_eq!(camel_case_start_from_idx("AbcDefGhi", 1), StrIndex::new(3, 3)); + assert_eq!(camel_case_start_from_idx("Abcdefg", 1), StrIndex::new(7, 7)); + } + + #[test] + fn camel_case_indices_full() { + assert_eq!(camel_case_indices("Abc\u{f6}\u{f6}DD"), vec![StrIndex::new(7, 9)]); + } + + #[test] + fn camel_case_split_full() { + assert_eq!(camel_case_split("A"), vec!["A"]); + assert_eq!(camel_case_split("AbcDef"), vec!["Abc", "Def"]); + assert_eq!(camel_case_split("Abc"), vec!["Abc"]); + assert_eq!(camel_case_split("abcDef"), vec!["abc", "Def"]); + assert_eq!( + camel_case_split("\u{f6}\u{f6}AabABcd"), + vec!["\u{f6}\u{f6}", "Aab", "A", "Bcd"] + ); + } +} diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs new file mode 100644 index 000000000..bad291dfc --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -0,0 +1,1099 @@ +//! Contains utility functions to generate suggestions. +#![deny(clippy::missing_docs_in_private_items)] + +use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite}; +use crate::ty::expr_sig; +use crate::{get_parent_expr_for_hir, higher}; +use rustc_ast::util::parser::AssocOp; +use rustc_ast::{ast, token}; +use rustc_ast_pretty::pprust::token_kind_to_string; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{EarlyContext, LateContext, LintContext}; +use rustc_middle::hir::place::ProjectionKind; +use rustc_middle::mir::{FakeReadCause, Mutability}; +use rustc_middle::ty; +use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext}; +use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; +use std::borrow::Cow; +use std::fmt::{Display, Write as _}; +use std::ops::{Add, Neg, Not, Sub}; + +/// A helper type to build suggestion correctly handling parentheses. +#[derive(Clone, PartialEq)] +pub enum Sugg<'a> { + /// An expression that never needs parentheses 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>, 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) => s.fmt(f), + Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f), + } + } +} + +#[expect(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> { + let get_snippet = |span| snippet(cx, span, ""); + snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_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 get_snippet = |span| snippet_with_macro_callsite(cx, span, default); + Self::hir_from_snippet(expr, get_snippet) + } + + /// Same as `hir`, but first walks the span up to the given context. This will result in the + /// macro call, rather then the expansion, if the span is from a child context. If the span is + /// not from a child context, it will be used directly instead. + /// + /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR + /// node would result in `box []`. If given the context of the address of expression, this + /// function will correctly get a snippet of `vec![]`. + pub fn hir_with_context( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + ctxt: SyntaxContext, + default: &'a str, + applicability: &mut Applicability, + ) -> Self { + if expr.span.ctxt() == ctxt { + Self::hir_from_snippet(expr, |span| snippet(cx, span, default)) + } else { + let snip = snippet_with_applicability(cx, expr.span, default, applicability); + Sugg::NonParen(snip) + } + } + + /// 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<'_>, get_snippet: impl Fn(Span) -> Cow<'a, str>) -> Self { + if let Some(range) = higher::Range::hir(expr) { + let op = match range.limits { + ast::RangeLimits::HalfOpen => AssocOp::DotDot, + ast::RangeLimits::Closed => AssocOp::DotDotEq, + }; + let start = range.start.map_or("".into(), |expr| get_snippet(expr.span)); + let end = range.end.map_or("".into(), |expr| get_snippet(expr.span)); + + return Sugg::BinOp(op, start, end); + } + + match expr.kind { + hir::ExprKind::AddrOf(..) + | hir::ExprKind::Box(..) + | hir::ExprKind::If(..) + | hir::ExprKind::Let(..) + | hir::ExprKind::Closure { .. } + | hir::ExprKind::Unary(..) + | hir::ExprKind::Match(..) => Sugg::MaybeParen(get_snippet(expr.span)), + 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::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(get_snippet(expr.span)), + hir::ExprKind::Assign(lhs, rhs, _) => { + Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span)) + }, + hir::ExprKind::AssignOp(op, lhs, rhs) => { + Sugg::BinOp(hirbinop2assignop(op), get_snippet(lhs.span), get_snippet(rhs.span)) + }, + hir::ExprKind::Binary(op, lhs, rhs) => Sugg::BinOp( + AssocOp::from_ast_binop(op.node.into()), + get_snippet(lhs.span), + get_snippet(rhs.span), + ), + hir::ExprKind::Cast(lhs, ty) => Sugg::BinOp(AssocOp::As, get_snippet(lhs.span), get_snippet(ty.span)), + hir::ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::Colon, get_snippet(lhs.span), get_snippet(ty.span)), + } + } + + /// Prepare a suggestion from an expression. + pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self { + use rustc_ast::ast::RangeLimits; + + let get_whole_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(get_whole_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::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::Yeet(..) + | 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(get_whole_snippet()), + ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp( + AssocOp::DotDot, + lhs.as_ref().map_or("".into(), |lhs| snippet(cx, lhs.span, default)), + rhs.as_ref().map_or("".into(), |rhs| snippet(cx, rhs.span, default)), + ), + ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp( + AssocOp::DotDotEq, + lhs.as_ref().map_or("".into(), |lhs| snippet(cx, lhs.span, default)), + rhs.as_ref().map_or("".into(), |rhs| snippet(cx, rhs.span, default)), + ), + ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp( + AssocOp::Assign, + snippet(cx, lhs.span, default), + snippet(cx, rhs.span, default), + ), + ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp( + astbinop2assignop(op), + snippet(cx, lhs.span, default), + snippet(cx, rhs.span, default), + ), + ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp( + AssocOp::from_ast_binop(op.node), + snippet(cx, lhs.span, default), + snippet(cx, rhs.span, default), + ), + ast::ExprKind::Cast(ref lhs, ref ty) => Sugg::BinOp( + AssocOp::As, + snippet(cx, lhs.span, default), + snippet(cx, ty.span, default), + ), + ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp( + AssocOp::Colon, + snippet(cx, lhs.span, default), + snippet(cx, ty.span, default), + ), + } + } + + /// 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. + 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 parentheses 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()`). + #[must_use] + 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 has_enclosing_paren(&sugg) { + Sugg::MaybeParen(sugg) + } else { + Sugg::NonParen(format!("({})", sugg).into()) + } + }, + Sugg::BinOp(op, lhs, rhs) => { + let sugg = binop_to_string(op, &lhs, &rhs); + Sugg::NonParen(format!("({})", sugg).into()) + }, + } + } +} + +/// Generates a string from the operator and both sides. +fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String { + match op { + AssocOp::Add + | AssocOp::Subtract + | AssocOp::Multiply + | AssocOp::Divide + | AssocOp::Modulus + | AssocOp::LAnd + | AssocOp::LOr + | AssocOp::BitXor + | AssocOp::BitAnd + | AssocOp::BitOr + | AssocOp::ShiftLeft + | AssocOp::ShiftRight + | AssocOp::Equal + | AssocOp::Less + | AssocOp::LessEqual + | AssocOp::NotEqual + | AssocOp::Greater + | AssocOp::GreaterEqual => 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), + } +} + +/// Return `true` if `sugg` is enclosed in parenthesis. +pub fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool { + let mut chars = sugg.as_ref().chars(); + if chars.next() == Some('(') { + let mut depth = 1; + for c in &mut chars { + if c == '(' { + depth += 1; + } else if c == ')' { + depth -= 1; + } + if depth == 0 { + break; + } + } + chars.next().is_none() + } else { + false + } +} + +/// Copied from the rust standard 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<'a> Not for Sugg<'a> { + type Output = Sugg<'a>; + fn not(self) -> Sugg<'a> { + use AssocOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual}; + + if let Sugg::BinOp(op, lhs, rhs) = self { + let to_op = match op { + Equal => NotEqual, + NotEqual => Equal, + Less => GreaterEqual, + GreaterEqual => Less, + Greater => LessEqual, + LessEqual => Greater, + _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)), + }; + Sugg::BinOp(to_op, lhs, rhs) + } else { + 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 an 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).to_string(); + let rhs = ParenHelper::new(rhs_paren, rhs).to_string(); + Sugg::BinOp(op, lhs.into(), rhs.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 `Diagnostic`. +pub trait DiagnosticExt<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> DiagnosticExt<T> for rustc_errors::Diagnostic { + 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, "", applicability); + } +} + +/// Suggestion results for handling closure +/// args dereferencing and borrowing +pub struct DerefClosure { + /// confidence on the built suggestion + pub applicability: Applicability, + /// gradually built suggestion + pub suggestion: String, +} + +/// Build suggestion gradually by handling closure arg specific usages, +/// such as explicit deref and borrowing cases. +/// Returns `None` if no such use cases have been triggered in closure body +/// +/// note: this only works on single line immutable closures with exactly one input parameter. +pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'_>) -> Option<DerefClosure> { + if let hir::ExprKind::Closure(&Closure { fn_decl, body, .. }) = closure.kind { + let closure_body = cx.tcx.hir().body(body); + // is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`) + // a type annotation is present if param `kind` is different from `TyKind::Infer` + let closure_arg_is_type_annotated_double_ref = if let TyKind::Rptr(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind + { + matches!(ty.kind, TyKind::Rptr(_, MutTy { .. })) + } else { + false + }; + + let mut visitor = DerefDelegate { + cx, + closure_span: closure.span, + closure_arg_is_type_annotated_double_ref, + next_pos: closure.span.lo(), + suggestion_start: String::new(), + applicability: Applicability::MachineApplicable, + }; + + let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id); + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results()) + .consume_body(closure_body); + }); + + if !visitor.suggestion_start.is_empty() { + return Some(DerefClosure { + applicability: visitor.applicability, + suggestion: visitor.finish(), + }); + } + } + None +} + +/// Visitor struct used for tracking down +/// dereferencing and borrowing of closure's args +struct DerefDelegate<'a, 'tcx> { + /// The late context of the lint + cx: &'a LateContext<'tcx>, + /// The span of the input closure to adapt + closure_span: Span, + /// Indicates if the arg of the closure is a type annotated double reference + closure_arg_is_type_annotated_double_ref: bool, + /// last position of the span to gradually build the suggestion + next_pos: BytePos, + /// starting part of the gradually built suggestion + suggestion_start: String, + /// confidence on the built suggestion + applicability: Applicability, +} + +impl<'tcx> DerefDelegate<'_, 'tcx> { + /// build final suggestion: + /// - create the ending part of suggestion + /// - concatenate starting and ending parts + /// - potentially remove needless borrowing + pub fn finish(&mut self) -> String { + let end_span = Span::new(self.next_pos, self.closure_span.hi(), self.closure_span.ctxt(), None); + let end_snip = snippet_with_applicability(self.cx, end_span, "..", &mut self.applicability); + let sugg = format!("{}{}", self.suggestion_start, end_snip); + if self.closure_arg_is_type_annotated_double_ref { + sugg.replacen('&', "", 1) + } else { + sugg + } + } + + /// indicates whether the function from `parent_expr` takes its args by double reference + fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool { + let ty = match parent_expr.kind { + ExprKind::MethodCall(_, call_args, _) => { + if let Some(sig) = self + .cx + .typeck_results() + .type_dependent_def_id(parent_expr.hir_id) + .map(|did| self.cx.tcx.fn_sig(did).skip_binder()) + { + call_args + .iter() + .position(|arg| arg.hir_id == cmt_hir_id) + .map(|i| sig.inputs()[i]) + } else { + return false; + } + }, + ExprKind::Call(func, call_args) => { + if let Some(sig) = expr_sig(self.cx, func) { + call_args + .iter() + .position(|arg| arg.hir_id == cmt_hir_id) + .and_then(|i| sig.input(i)) + .map(ty::Binder::skip_binder) + } else { + return false; + } + }, + _ => return false, + }; + + ty.map_or(false, |ty| matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref())) + } +} + +impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { + fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { + if let PlaceBase::Local(id) = cmt.place.base { + let map = self.cx.tcx.hir(); + let span = map.span(cmt.hir_id); + let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None); + let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability); + + // identifier referring to the variable currently triggered (i.e.: `fp`) + let ident_str = map.name(id).to_string(); + // full identifier that includes projection (i.e.: `fp.field`) + let ident_str_with_proj = snippet(self.cx, span, "..").to_string(); + + if cmt.place.projections.is_empty() { + // handle item without any projection, that needs an explicit borrowing + // i.e.: suggest `&x` instead of `x` + let _ = write!(self.suggestion_start, "{}&{}", start_snip, ident_str); + } else { + // cases where a parent `Call` or `MethodCall` is using the item + // i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()` + // + // Note about method calls: + // - compiler automatically dereference references if the target type is a reference (works also for + // function call) + // - `self` arguments in the case of `x.is_something()` are also automatically (de)referenced, and + // no projection should be suggested + if let Some(parent_expr) = get_parent_expr_for_hir(self.cx, cmt.hir_id) { + match &parent_expr.kind { + // given expression is the self argument and will be handled completely by the compiler + // i.e.: `|x| x.is_something()` + ExprKind::MethodCall(_, [self_expr, ..], _) if self_expr.hir_id == cmt.hir_id => { + let _ = write!(self.suggestion_start, "{}{}", start_snip, ident_str_with_proj); + self.next_pos = span.hi(); + return; + }, + // item is used in a call + // i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)` + ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, [_, call_args @ ..], _) => { + let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id); + let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind(); + + if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) { + // suggest ampersand if call function is taking args by double reference + let takes_arg_by_double_ref = + self.func_takes_arg_by_double_ref(parent_expr, cmt.hir_id); + + // compiler will automatically dereference field or index projection, so no need + // to suggest ampersand, but full identifier that includes projection is required + let has_field_or_index_projection = + cmt.place.projections.iter().any(|proj| { + matches!(proj.kind, ProjectionKind::Field(..) | ProjectionKind::Index) + }); + + // no need to bind again if the function doesn't take arg by double ref + // and if the item is already a double ref + let ident_sugg = if !call_args.is_empty() + && !takes_arg_by_double_ref + && (self.closure_arg_is_type_annotated_double_ref || has_field_or_index_projection) + { + let ident = if has_field_or_index_projection { + ident_str_with_proj + } else { + ident_str + }; + format!("{}{}", start_snip, ident) + } else { + format!("{}&{}", start_snip, ident_str) + }; + self.suggestion_start.push_str(&ident_sugg); + self.next_pos = span.hi(); + return; + } + + self.applicability = Applicability::Unspecified; + }, + _ => (), + } + } + + let mut replacement_str = ident_str; + let mut projections_handled = false; + cmt.place.projections.iter().enumerate().for_each(|(i, proj)| { + match proj.kind { + // Field projection like `|v| v.foo` + // no adjustment needed here, as field projections are handled by the compiler + ProjectionKind::Field(..) => match cmt.place.ty_before_projection(i).kind() { + ty::Adt(..) | ty::Tuple(_) => { + replacement_str = ident_str_with_proj.clone(); + projections_handled = true; + }, + _ => (), + }, + // Index projection like `|x| foo[x]` + // the index is dropped so we can't get it to build the suggestion, + // so the span is set-up again to get more code, using `span.hi()` (i.e.: `foo[x]`) + // instead of `span.lo()` (i.e.: `foo`) + ProjectionKind::Index => { + let start_span = Span::new(self.next_pos, span.hi(), span.ctxt(), None); + start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability); + replacement_str.clear(); + projections_handled = true; + }, + // note: unable to trigger `Subslice` kind in tests + ProjectionKind::Subslice => (), + ProjectionKind::Deref => { + // Explicit derefs are typically handled later on, but + // some items do not need explicit deref, such as array accesses, + // so we mark them as already processed + // i.e.: don't suggest `*sub[1..4].len()` for `|sub| sub[1..4].len() == 3` + if let ty::Ref(_, inner, _) = cmt.place.ty_before_projection(i).kind() { + if matches!(inner.kind(), ty::Ref(_, innermost, _) if innermost.is_array()) { + projections_handled = true; + } + } + }, + } + }); + + // handle `ProjectionKind::Deref` by removing one explicit deref + // if no special case was detected (i.e.: suggest `*x` instead of `**x`) + if !projections_handled { + let last_deref = cmt + .place + .projections + .iter() + .rposition(|proj| proj.kind == ProjectionKind::Deref); + + if let Some(pos) = last_deref { + let mut projections = cmt.place.projections.clone(); + projections.truncate(pos); + + for item in projections { + if item.kind == ProjectionKind::Deref { + replacement_str = format!("*{}", replacement_str); + } + } + } + } + + let _ = write!(self.suggestion_start, "{}{}", start_snip, replacement_str); + } + self.next_pos = span.hi(); + } + } + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &rustc_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +#[cfg(test)] +mod test { + use super::Sugg; + + use rustc_ast::util::parser::AssocOp; + 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()); + } + + #[test] + fn binop_maybe_par() { + let sugg = Sugg::BinOp(AssocOp::Add, "1".into(), "1".into()); + assert_eq!("(1 + 1)", sugg.maybe_par().to_string()); + + let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into(), "(1 + 1)".into()); + assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string()); + } + #[test] + fn not_op() { + use AssocOp::{Add, Equal, Greater, GreaterEqual, LAnd, LOr, Less, LessEqual, NotEqual}; + + fn test_not(op: AssocOp, correct: &str) { + let sugg = Sugg::BinOp(op, "x".into(), "y".into()); + assert_eq!((!sugg).to_string(), correct); + } + + // Invert the comparison operator. + test_not(Equal, "x != y"); + test_not(NotEqual, "x == y"); + test_not(Less, "x >= y"); + test_not(LessEqual, "x > y"); + test_not(Greater, "x <= y"); + test_not(GreaterEqual, "x < y"); + + // Other operators are inverted like !(..). + test_not(Add, "!(x + y)"); + test_not(LAnd, "!(x && y)"); + test_not(LOr, "!(x || y)"); + } +} diff --git a/src/tools/clippy/clippy_utils/src/sym_helper.rs b/src/tools/clippy/clippy_utils/src/sym_helper.rs new file mode 100644 index 000000000..f47dc80eb --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/sym_helper.rs @@ -0,0 +1,7 @@ +#[macro_export] +/// Convenience wrapper around rustc's `Symbol::intern` +macro_rules! sym { + ($tt:tt) => { + rustc_span::symbol::Symbol::intern(stringify!($tt)) + }; +} diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs new file mode 100644 index 000000000..a05d633d9 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -0,0 +1,829 @@ +//! Util methods for [`rustc_middle::ty`] + +#![allow(clippy::module_name_repetitions)] + +use core::ops::ControlFlow; +use rustc_ast::ast::Mutability; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir as hir; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::LateContext; +use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; +use rustc_middle::ty::{ + self, AdtDef, Binder, BoundRegion, DefIdTree, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, ProjectionTy, + Region, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, VariantDef, VariantDiscr, +}; +use rustc_span::symbol::Ident; +use rustc_span::{sym, Span, Symbol, DUMMY_SP}; +use rustc_target::abi::{Size, VariantIdx}; +use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::query::normalize::AtExt; +use std::iter; + +use crate::{match_def_path, path_res, paths}; + +// Checks if the given type implements copy. +pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env) +} + +/// Checks whether a type can be partially moved. +pub fn can_partially_move_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + if has_drop(cx, ty) || is_copy(cx, ty) { + return false; + } + match ty.kind() { + ty::Param(_) => false, + ty::Adt(def, subs) => def.all_fields().any(|f| !is_copy(cx, f.ty(cx.tcx, subs))), + _ => true, + } +} + +/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty` +pub fn contains_ty<'tcx>(ty: Ty<'tcx>, other_ty: Ty<'tcx>) -> bool { + ty.walk().any(|inner| match inner.unpack() { + GenericArgKind::Type(inner_ty) => other_ty == inner_ty, + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, + }) +} + +/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt +/// constructor. +pub fn contains_adt_constructor<'tcx>(ty: Ty<'tcx>, adt: AdtDef<'tcx>) -> bool { + ty.walk().any(|inner| match inner.unpack() { + GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt), + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, + }) +} + +/// Resolves `<T as Iterator>::Item` for `T` +/// Do not invoke without first verifying that the type implements `Iterator` +pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { + cx.tcx + .get_diagnostic_item(sym::Iterator) + .and_then(|iter_did| get_associated_type(cx, ty, iter_did, "Item")) +} + +/// Returns the associated type `name` for `ty` as an implementation of `trait_id`. +/// Do not invoke without first verifying that the type implements the trait. +pub fn get_associated_type<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + name: &str, +) -> Option<Ty<'tcx>> { + cx.tcx + .associated_items(trait_id) + .find_by_name_and_kind(cx.tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) + .and_then(|assoc| { + let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[])); + cx.tcx.try_normalize_erasing_regions(cx.param_env, proj).ok() + }) +} + +/// Get the diagnostic name of a type, e.g. `sym::HashMap`. To check if a type +/// implements a trait marked with a diagnostic item use [`implements_trait`]. +/// +/// For a further exploitation what diagnostic items are see [diagnostic items] in +/// rustc-dev-guide. +/// +/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html +pub fn get_type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Symbol> { + match ty.kind() { + ty::Adt(adt, _) => cx.tcx.get_diagnostic_name(adt.did()), + _ => None, + } +} + +/// Returns true if ty has `iter` or `iter_mut` methods +pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> { + // FIXME: instead of this hard-coded list, we should check if `<adt>::iter` + // exists and has the desired signature. Unfortunately FnCtxt is not exported + // so we can't use its `lookup_method` method. + let into_iter_collections: &[Symbol] = &[ + sym::Vec, + sym::Option, + sym::Result, + sym::BTreeMap, + sym::BTreeSet, + sym::VecDeque, + sym::LinkedList, + sym::BinaryHeap, + sym::HashSet, + sym::HashMap, + sym::PathBuf, + sym::Path, + sym::Receiver, + ]; + + let ty_to_check = match probably_ref_ty.kind() { + ty::Ref(_, ty_to_check, _) => *ty_to_check, + _ => probably_ref_ty, + }; + + let def_id = match ty_to_check.kind() { + ty::Array(..) => return Some(sym::array), + ty::Slice(..) => return Some(sym::slice), + ty::Adt(adt, _) => adt.did(), + _ => return None, + }; + + for &name in into_iter_collections { + if cx.tcx.is_diagnostic_item(name, def_id) { + return Some(cx.tcx.item_name(def_id)); + } + } + None +} + +/// Checks whether a type implements a trait. +/// The function returns false in case the type contains an inference variable. +/// +/// See: +/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`]. +/// * [Common tools for writing lints] for an example how to use this function and other options. +/// +/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait +pub fn implements_trait<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + ty_params: &[GenericArg<'tcx>], +) -> bool { + implements_trait_with_env(cx.tcx, cx.param_env, ty, trait_id, ty_params) +} + +/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context. +pub fn implements_trait_with_env<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + ty_params: &[GenericArg<'tcx>], +) -> bool { + // Clippy shouldn't have infer types + assert!(!ty.needs_infer()); + + let ty = tcx.erase_regions(ty); + if ty.has_escaping_bound_vars() { + return false; + } + let ty_params = tcx.mk_substs(ty_params.iter()); + tcx.infer_ctxt().enter(|infcx| { + infcx + .type_implements_trait(trait_id, ty, ty_params, param_env) + .must_apply_modulo_regions() + }) +} + +/// Checks whether this type implements `Drop`. +pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.ty_adt_def() { + Some(def) => def.has_dtor(cx.tcx), + None => false, + } +} + +// Returns whether the type has #[must_use] attribute +pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Adt(adt, _) => cx.tcx.has_attr(adt.did(), sym::must_use), + ty::Foreign(did) => cx.tcx.has_attr(*did, sym::must_use), + ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => { + // for the Array case we don't need to care for the len == 0 case + // because we don't want to lint functions returning empty arrays + is_must_use_ty(cx, *ty) + }, + ty::Tuple(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)), + ty::Opaque(def_id, _) => { + for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) { + if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() { + if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) { + return true; + } + } + } + false + }, + ty::Dynamic(binder, _) => { + for predicate in binder.iter() { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { + if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) { + return true; + } + } + } + false + }, + _ => false, + } +} + +// FIXME: Per https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize +// this function can be removed once the `normalize` method does not panic when normalization does +// not succeed +/// Checks if `Ty` is normalizable. This function is useful +/// to avoid crashes on `layout_of`. +pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool { + is_normalizable_helper(cx, param_env, ty, &mut FxHashMap::default()) +} + +fn is_normalizable_helper<'tcx>( + cx: &LateContext<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + cache: &mut FxHashMap<Ty<'tcx>, bool>, +) -> bool { + if let Some(&cached_result) = cache.get(&ty) { + return cached_result; + } + // prevent recursive loops, false-negative is better than endless loop leading to stack overflow + cache.insert(ty, false); + let result = cx.tcx.infer_ctxt().enter(|infcx| { + let cause = rustc_middle::traits::ObligationCause::dummy(); + if infcx.at(&cause, param_env).normalize(ty).is_ok() { + match ty.kind() { + ty::Adt(def, substs) => def.variants().iter().all(|variant| { + variant + .fields + .iter() + .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, substs), cache)) + }), + _ => ty.walk().all(|generic_arg| match generic_arg.unpack() { + GenericArgKind::Type(inner_ty) if inner_ty != ty => { + is_normalizable_helper(cx, param_env, inner_ty, cache) + }, + _ => true, // if inner_ty == ty, we've already checked it + }), + } + } else { + false + } + }); + cache.insert(ty, result); + result +} + +/// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any +/// integer or floating-point number type). For checking aggregation of primitive types (e.g. +/// tuples and slices of primitive type) see `is_recursively_primitive_type` +pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool { + matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_)) +} + +/// Returns `true` if the given type is a primitive (a `bool` or `char`, any integer or +/// floating-point number type, a `str`, or an array, slice, or tuple of those types). +pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { + match *ty.kind() { + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, + ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true, + ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type), + ty::Tuple(inner_types) => inner_types.iter().all(is_recursively_primitive_type), + _ => false, + } +} + +/// Checks if the type is a reference equals to a diagnostic item +pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { + match ty.kind() { + ty::Ref(_, ref_ty, _) => match ref_ty.kind() { + ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()), + _ => false, + }, + _ => false, + } +} + +/// Checks if the type is equal to a diagnostic item. To check if a type implements a +/// trait marked with a diagnostic item use [`implements_trait`]. +/// +/// For a further exploitation what diagnostic items are see [diagnostic items] in +/// rustc-dev-guide. +/// +/// --- +/// +/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` +/// +/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html +pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { + match ty.kind() { + ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()), + _ => false, + } +} + +/// Checks if the type is equal to a lang item. +/// +/// Returns `false` if the `LangItem` is not defined. +pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool { + match ty.kind() { + ty::Adt(adt, _) => cx + .tcx + .lang_items() + .require(lang_item) + .map_or(false, |li| li == adt.did()), + _ => false, + } +} + +/// Return `true` if the passed `typ` is `isize` or `usize`. +pub fn is_isize_or_usize(typ: Ty<'_>) -> bool { + matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) +} + +/// Checks if type is struct, enum or union type with the given def path. +/// +/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead. +/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` +pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool { + match ty.kind() { + ty::Adt(adt, _) => match_def_path(cx, adt.did(), path), + _ => false, + } +} + +/// Checks if the drop order for a type matters. Some std types implement drop solely to +/// deallocate memory. For these types, and composites containing them, changing the drop order +/// won't result in any observable side effects. +pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + fn needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool { + if !seen.insert(ty) { + return false; + } + if !ty.has_significant_drop(cx.tcx, cx.param_env) { + false + } + // Check for std types which implement drop, but only for memory allocation. + else if is_type_lang_item(cx, ty, LangItem::OwnedBox) + || matches!( + get_type_diagnostic_name(cx, ty), + Some(sym::HashSet | sym::Rc | sym::Arc | sym::cstring_type) + ) + || match_type(cx, ty, &paths::WEAK_RC) + || match_type(cx, ty, &paths::WEAK_ARC) + { + // Check all of the generic arguments. + if let ty::Adt(_, subs) = ty.kind() { + subs.types().any(|ty| needs_ordered_drop_inner(cx, ty, seen)) + } else { + true + } + } else if !cx + .tcx + .lang_items() + .drop_trait() + .map_or(false, |id| implements_trait(cx, ty, id, &[])) + { + // This type doesn't implement drop, so no side effects here. + // Check if any component type has any. + match ty.kind() { + ty::Tuple(fields) => fields.iter().any(|ty| needs_ordered_drop_inner(cx, ty, seen)), + ty::Array(ty, _) => needs_ordered_drop_inner(cx, *ty, seen), + ty::Adt(adt, subs) => adt + .all_fields() + .map(|f| f.ty(cx.tcx, subs)) + .any(|ty| needs_ordered_drop_inner(cx, ty, seen)), + _ => true, + } + } else { + true + } + } + + needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) +} + +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { + fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) { + if let ty::Ref(_, ty, _) = ty.kind() { + peel(*ty, count + 1) + } else { + (ty, count) + } + } + peel(ty, 0) +} + +/// Peels off all references on the type.Returns the underlying type, the number of references +/// removed, and whether the pointer is ultimately mutable or not. +pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { + fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { + match ty.kind() { + ty::Ref(_, ty, Mutability::Mut) => f(*ty, count + 1, mutability), + ty::Ref(_, ty, Mutability::Not) => f(*ty, count + 1, Mutability::Not), + _ => (ty, count, mutability), + } + } + f(ty, 0, Mutability::Mut) +} + +/// Returns `true` if the given type is an `unsafe` function. +pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe, + _ => false, + } +} + +/// Returns the base type for HIR references and pointers. +pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { + match ty.kind { + TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(mut_ty.ty), + _ => ty, + } +} + +/// Returns the base type for references and raw pointers, and count reference +/// depth. +pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { + fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { + match ty.kind() { + ty::Ref(_, ty, _) => inner(*ty, depth + 1), + _ => (ty, depth), + } + } + inner(ty, 0) +} + +/// Returns `true` if types `a` and `b` are same types having same `Const` generic args, +/// otherwise returns `false` +pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { + match (&a.kind(), &b.kind()) { + (&ty::Adt(did_a, substs_a), &ty::Adt(did_b, substs_b)) => { + if did_a != did_b { + return false; + } + + substs_a + .iter() + .zip(substs_b.iter()) + .all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) { + (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b, + (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => { + same_type_and_consts(type_a, type_b) + }, + _ => true, + }) + }, + _ => a == b, + } +} + +/// Checks if a given type looks safe to be uninitialized. +pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + match *ty.kind() { + ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component), + ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)), + ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did()), + _ => false, + } +} + +/// Gets an iterator over all predicates which apply to the given item. +pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(Predicate<'_>, Span)> { + let mut next_id = Some(id); + iter::from_fn(move || { + next_id.take().map(|id| { + let preds = tcx.predicates_of(id); + next_id = preds.parent; + preds.predicates.iter() + }) + }) + .flatten() +} + +/// A signature for a function like type. +#[derive(Clone, Copy)] +pub enum ExprFnSig<'tcx> { + Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>), + Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), + Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>), +} +impl<'tcx> ExprFnSig<'tcx> { + /// Gets the argument type at the given offset. This will return `None` when the index is out of + /// bounds only for variadic functions, otherwise this will panic. + pub fn input(self, i: usize) -> Option<Binder<'tcx, Ty<'tcx>>> { + match self { + Self::Sig(sig, _) => { + if sig.c_variadic() { + sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose() + } else { + Some(sig.input(i)) + } + }, + Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])), + Self::Trait(inputs, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])), + } + } + + /// Gets the argument type at the given offset. For closures this will also get the type as + /// written. This will return `None` when the index is out of bounds only for variadic + /// functions, otherwise this will panic. + pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Binder<'tcx, Ty<'tcx>>)> { + match self { + Self::Sig(sig, _) => { + if sig.c_variadic() { + sig.inputs() + .map_bound(|inputs| inputs.get(i).copied()) + .transpose() + .map(|arg| (None, arg)) + } else { + Some((None, sig.input(i))) + } + }, + Self::Closure(decl, sig) => Some(( + decl.and_then(|decl| decl.inputs.get(i)), + sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), + )), + Self::Trait(inputs, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))), + } + } + + /// Gets the result type, if one could be found. Note that the result type of a trait may not be + /// specified. + pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> { + match self { + Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()), + Self::Trait(_, output) => output, + } + } + + pub fn predicates_id(&self) -> Option<DefId> { + if let ExprFnSig::Sig(_, id) = *self { id } else { None } + } +} + +/// If the expression is function like, get the signature for it. +pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> { + if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) { + Some(ExprFnSig::Sig(cx.tcx.fn_sig(id), Some(id))) + } else { + ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) + } +} + +fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> { + if ty.is_box() { + return ty_sig(cx, ty.boxed_ty()); + } + match *ty.kind() { + ty::Closure(id, subs) => { + let decl = id + .as_local() + .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id))); + Some(ExprFnSig::Closure(decl, subs.as_closure().sig())) + }, + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))), + ty::Opaque(id, _) => ty_sig(cx, cx.tcx.type_of(id)), + ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)), + ty::Dynamic(bounds, _) => { + let lang_items = cx.tcx.lang_items(); + match bounds.principal() { + Some(bound) + if Some(bound.def_id()) == lang_items.fn_trait() + || Some(bound.def_id()) == lang_items.fn_once_trait() + || Some(bound.def_id()) == lang_items.fn_mut_trait() => + { + let output = bounds + .projection_bounds() + .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) + .map(|p| p.map_bound(|p| p.term.ty().unwrap())); + Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) + }, + _ => None, + } + }, + ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) { + Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty), + _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty)), + }, + ty::Param(_) => sig_from_bounds(cx, ty), + _ => None, + } +} + +fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); + + for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { + match pred.kind().skip_binder() { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) + && p.self_ty() == ty => + { + if inputs.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + inputs = Some(pred.kind().rebind(p.trait_ref.substs.type_at(1))); + }, + PredicateKind::Projection(p) + if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() + && p.projection_ty.self_ty() == ty => + { + if output.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + output = Some(pred.kind().rebind(p.term.ty().unwrap())); + }, + _ => (), + } + } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) +} + +fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option<ExprFnSig<'tcx>> { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); + + for pred in cx + .tcx + .bound_explicit_item_bounds(ty.item_def_id) + .transpose_iter() + .map(|x| x.map_bound(|(p, _)| p)) + { + match pred.0.kind().skip_binder() { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) => + { + if inputs.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + inputs = Some( + pred.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1))) + .subst(cx.tcx, ty.substs), + ); + }, + PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => { + if output.is_some() { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + output = Some( + pred.map_bound(|pred| pred.kind().rebind(p.term.ty().unwrap())) + .subst(cx.tcx, ty.substs), + ); + }, + _ => (), + } + } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) +} + +#[derive(Clone, Copy)] +pub enum EnumValue { + Unsigned(u128), + Signed(i128), +} +impl core::ops::Add<u32> for EnumValue { + type Output = Self; + fn add(self, n: u32) -> Self::Output { + match self { + Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)), + Self::Signed(x) => Self::Signed(x + i128::from(n)), + } + } +} + +/// Attempts to read the given constant as though it were an an enum value. +#[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] +pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> { + if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { + match tcx.type_of(id).kind() { + ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() { + 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), + 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), + 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), + 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), + 16 => value.assert_bits(Size::from_bytes(16)) as i128, + _ => return None, + })), + ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() { + 1 => value.assert_bits(Size::from_bytes(1)), + 2 => value.assert_bits(Size::from_bytes(2)), + 4 => value.assert_bits(Size::from_bytes(4)), + 8 => value.assert_bits(Size::from_bytes(8)), + 16 => value.assert_bits(Size::from_bytes(16)), + _ => return None, + })), + _ => None, + } + } else { + None + } +} + +/// Gets the value of the given variant. +pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: AdtDef<'_>, i: VariantIdx) -> EnumValue { + let variant = &adt.variant(i); + match variant.discr { + VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap(), + VariantDiscr::Relative(x) => match adt.variant((i.as_usize() - x as usize).into()).discr { + VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap() + x, + VariantDiscr::Relative(_) => EnumValue::Unsigned(x.into()), + }, + } +} + +/// Check if the given type is either `core::ffi::c_void`, `std::os::raw::c_void`, or one of the +/// platform specific `libc::<platform>::c_void` types in libc. +pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, _) = ty.kind() + && let &[krate, .., name] = &*cx.get_def_path(adt.did()) + && let sym::libc | sym::core | sym::std = krate + && name.as_str() == "c_void" + { + true + } else { + false + } +} + +pub fn for_each_top_level_late_bound_region<B>( + ty: Ty<'_>, + f: impl FnMut(BoundRegion) -> ControlFlow<B>, +) -> ControlFlow<B> { + struct V<F> { + index: u32, + f: F, + } + impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<'tcx> for V<F> { + type BreakTy = B; + fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow<Self::BreakTy> { + if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index { + (self.f)(bound) + } else { + ControlFlow::Continue(()) + } + } + fn visit_binder<T: TypeVisitable<'tcx>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> { + self.index += 1; + let res = t.super_visit_with(self); + self.index -= 1; + res + } + } + ty.visit_with(&mut V { index: 0, f }) +} + +/// Gets the struct or enum variant from the given `Res` +pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> { + match res { + Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()), + Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)), + Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()), + Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => { + let var_id = cx.tcx.parent(id); + Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id)) + }, + Res::SelfCtor(id) => Some(cx.tcx.type_of(id).ty_adt_def().unwrap().non_enum_variant()), + _ => None, + } +} + +/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`. +pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [Predicate<'_>]) -> bool { + let ty::Param(ty) = *ty.kind() else { + return false; + }; + let lang = tcx.lang_items(); + let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id)) + = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait()) + else { + return false; + }; + predicates + .iter() + .try_fold(false, |found, p| { + if let PredicateKind::Trait(p) = p.kind().skip_binder() + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() + && ty.index == self_ty.index + { + // This should use `super_traits_of`, but that's a private function. + if p.trait_ref.def_id == fn_once_id { + return Some(true); + } else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id { + return None; + } + } + Some(found) + }) + .unwrap_or(false) +} diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs new file mode 100644 index 000000000..3af5dfb62 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -0,0 +1,216 @@ +use crate as utils; +use crate::visitors::{expr_visitor, expr_visitor_no_bodies}; +use rustc_hir as hir; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::HirIdSet; +use rustc_hir::{Expr, ExprKind, HirId, Node}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; +use rustc_typeck::expr_use_visitor::{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<HirIdSet> { + let mut delegate = MutVarsDelegate { + used_mutably: HirIdSet::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: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { + mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable)) +} + +struct MutVarsDelegate { + used_mutably: HirIdSet, + skip: bool, +} + +impl<'tcx> MutVarsDelegate { + 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) {} + + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) { + if bk == ty::BorrowKind::MutBorrow { + self.update(cmt); + } + } + + fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + self.update(cmt); + } + + fn fake_read(&mut self, _: &rustc_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +pub struct ParamBindingIdCollector { + pub 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 { + 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); + } +} + +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 NestedFilter = nested_filter::OnlyBodies; + + 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) -> Self::Map { + self.cx.tcx.hir() + } +} + +pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool { + let mut seen_return_break_continue = false; + expr_visitor_no_bodies(|ex| { + if seen_return_break_continue { + return false; + } + match &ex.kind { + ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => { + 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 ex.span.from_expansion() { + seen_return_break_continue = true; + } + }, + } + !seen_return_break_continue + }) + .visit_expr(expression); + seen_return_break_continue +} + +pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool { + let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false }; + + // for _ in 1..3 { + // local + // } + // + // let closure = || local; + // closure(); + // closure(); + let in_loop_or_closure = cx + .tcx + .hir() + .parent_iter(after.hir_id) + .take_while(|&(id, _)| id != block.hir_id) + .any(|(_, node)| { + matches!( + node, + Node::Expr(Expr { + kind: ExprKind::Loop(..) | ExprKind::Closure { .. }, + .. + }) + ) + }); + if in_loop_or_closure { + return true; + } + + let mut used_after_expr = false; + let mut past_expr = false; + expr_visitor(cx, |expr| { + if used_after_expr { + return false; + } + + if expr.hir_id == after.hir_id { + past_expr = true; + return false; + } + + if past_expr && utils::path_to_local_id(expr, local_id) { + used_after_expr = true; + } + !used_after_expr + }) + .visit_block(block); + used_after_expr +} diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs new file mode 100644 index 000000000..bae8ad9f5 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -0,0 +1,733 @@ +use crate::ty::needs_ordered_drop; +use crate::{get_enclosing_block, path_to_local_id}; +use core::ops::ControlFlow; +use rustc_hir as hir; +use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor}; +use rustc_hir::{ + Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp, + UnsafeSource, Unsafety, +}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::adjustment::Adjust; +use rustc_middle::ty::{self, Ty, TypeckResults}; +use rustc_span::Span; + +mod internal { + /// Trait for visitor functions to control whether or not to descend to child nodes. Implemented + /// for only two types. `()` always descends. `Descend` allows controlled descent. + pub trait Continue { + fn descend(&self) -> bool; + } +} +use internal::Continue; + +impl Continue for () { + fn descend(&self) -> bool { + true + } +} + +/// Allows for controlled descent when using visitor functions. Use `()` instead when always +/// descending into child nodes. +#[derive(Clone, Copy)] +pub enum Descend { + Yes, + No, +} +impl From<bool> for Descend { + fn from(from: bool) -> Self { + if from { Self::Yes } else { Self::No } + } +} +impl Continue for Descend { + fn descend(&self) -> bool { + matches!(self, Self::Yes) + } +} + +/// Calls the given function once for each expression contained. This does not enter any bodies or +/// nested items. +pub fn for_each_expr<'tcx, B, C: Continue>( + node: impl Visitable<'tcx>, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>, +) -> Option<B> { + struct V<B, F> { + f: F, + res: Option<B>, + } + impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<B, F> { + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + if self.res.is_some() { + return; + } + match (self.f)(e) { + ControlFlow::Continue(c) if c.descend() => walk_expr(self, e), + ControlFlow::Break(b) => self.res = Some(b), + ControlFlow::Continue(_) => (), + } + } + + // Avoid unnecessary `walk_*` calls. + fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {} + fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {} + fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {} + // Avoid monomorphising all `visit_*` functions. + fn visit_nested_item(&mut self, _: ItemId) {} + } + let mut v = V { f, res: None }; + node.visit(&mut v); + v.res +} + +/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested +/// bodies (i.e. closures) are visited. +/// If the callback returns `true`, the expr just provided to the callback is walked. +#[must_use] +pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> { + struct V<'tcx, F> { + hir: Map<'tcx>, + f: F, + } + impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.hir + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if (self.f)(expr) { + walk_expr(self, expr); + } + } + } + V { hir: cx.tcx.hir(), f } +} + +/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested +/// bodies (i.e. closures) are not visited. +/// If the callback returns `true`, the expr just provided to the callback is walked. +#[must_use] +pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> { + struct V<F>(F); + impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> { + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if (self.0)(e) { + walk_expr(self, e); + } + } + } + V(f) +} + +/// returns `true` if expr contains match expr desugared from try +fn contains_try(expr: &hir::Expr<'_>) -> bool { + let mut found = false; + expr_visitor_no_bodies(|e| { + if !found { + found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)); + } + !found + }) + .visit_expr(expr); + 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> { + 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 + } +} + +/// A type which can be visited. +pub trait Visitable<'tcx> { + /// Calls the corresponding `visit_*` function on the visitor. + fn visit<V: Visitor<'tcx>>(self, visitor: &mut V); +} +macro_rules! visitable_ref { + ($t:ident, $f:ident) => { + impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> { + fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) { + visitor.$f(self); + } + } + }; +} +visitable_ref!(Arm, visit_arm); +visitable_ref!(Block, visit_block); +visitable_ref!(Body, visit_body); +visitable_ref!(Expr, visit_expr); +visitable_ref!(Stmt, visit_stmt); + +// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I +// where +// I::Item: Visitable<'tcx>, +// { +// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) { +// for x in self { +// x.visit(visitor); +// } +// } +// } + +/// Checks if the given resolved path is used in the given body. +pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { + let mut found = false; + expr_visitor(cx, |e| { + if found { + return false; + } + + if let ExprKind::Path(p) = &e.kind { + if cx.qpath_res(p, e.hir_id) == res { + found = true; + } + } + !found + }) + .visit_expr(&cx.tcx.hir().body(body).value); + found +} + +/// Checks if the given local is used. +pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool { + let mut is_used = false; + let mut visitor = expr_visitor(cx, |expr| { + if !is_used { + is_used = path_to_local_id(expr, id); + } + !is_used + }); + visitable.visit(&mut visitor); + drop(visitor); + is_used +} + +/// Checks if the given expression is a constant. +pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { + struct V<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + is_const: bool, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if !self.is_const { + return; + } + match e.kind { + ExprKind::ConstBlock(_) => return, + ExprKind::Call( + &Expr { + kind: ExprKind::Path(ref p), + hir_id, + .. + }, + _, + ) if self + .cx + .qpath_res(p, hir_id) + .opt_def_id() + .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {}, + ExprKind::MethodCall(..) + if self + .cx + .typeck_results() + .type_dependent_def_id(e.hir_id) + .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {}, + ExprKind::Binary(_, lhs, rhs) + if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty() + && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {}, + ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (), + ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (), + ExprKind::Index(base, _) + if matches!( + self.cx.typeck_results().expr_ty(base).peel_refs().kind(), + ty::Slice(_) | ty::Array(..) + ) => {}, + ExprKind::Path(ref p) + if matches!( + self.cx.qpath_res(p, e.hir_id), + Res::Def( + DefKind::Const + | DefKind::AssocConst + | DefKind::AnonConst + | DefKind::ConstParam + | DefKind::Ctor(..) + | DefKind::Fn + | DefKind::AssocFn, + _ + ) | Res::SelfCtor(_) + ) => {}, + + ExprKind::AddrOf(..) + | ExprKind::Array(_) + | ExprKind::Block(..) + | ExprKind::Cast(..) + | ExprKind::DropTemps(_) + | ExprKind::Field(..) + | ExprKind::If(..) + | ExprKind::Let(..) + | ExprKind::Lit(_) + | ExprKind::Match(..) + | ExprKind::Repeat(..) + | ExprKind::Struct(..) + | ExprKind::Tup(_) + | ExprKind::Type(..) => (), + + _ => { + self.is_const = false; + return; + }, + } + walk_expr(self, e); + } + } + + let mut v = V { cx, is_const: true }; + v.visit_expr(e); + v.is_const +} + +/// Checks if the given expression performs an unsafe operation outside of an unsafe block. +pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { + struct V<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + is_unsafe: bool, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.is_unsafe { + return; + } + match e.kind { + ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => { + self.is_unsafe = true; + }, + ExprKind::MethodCall(..) + if self + .cx + .typeck_results() + .type_dependent_def_id(e.hir_id) + .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) => + { + self.is_unsafe = true; + }, + ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() { + ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true, + ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true, + _ => walk_expr(self, e), + }, + ExprKind::Path(ref p) + if self + .cx + .qpath_res(p, e.hir_id) + .opt_def_id() + .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) => + { + self.is_unsafe = true; + }, + _ => walk_expr(self, e), + } + } + fn visit_block(&mut self, b: &'tcx Block<'_>) { + if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) { + walk_block(self, b); + } + } + fn visit_nested_item(&mut self, id: ItemId) { + if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind { + self.is_unsafe = i.unsafety == Unsafety::Unsafe; + } + } + } + let mut v = V { cx, is_unsafe: false }; + v.visit_expr(e); + v.is_unsafe +} + +/// Checks if the given expression contains an unsafe block +pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + found_unsafe: bool, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_block(&mut self, b: &'tcx Block<'_>) { + if self.found_unsafe { + return; + } + if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { + self.found_unsafe = true; + return; + } + walk_block(self, b); + } + } + let mut v = V { + cx, + found_unsafe: false, + }; + v.visit_expr(e); + v.found_unsafe +} + +/// Runs the given function for each sub-expression producing the final value consumed by the parent +/// of the give expression. +/// +/// e.g. for the following expression +/// ```rust,ignore +/// if foo { +/// f(0) +/// } else { +/// 1 + 1 +/// } +/// ``` +/// this will pass both `f(0)` and `1+1` to the given function. +pub fn for_each_value_source<'tcx, B>( + e: &'tcx Expr<'tcx>, + f: &mut impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, +) -> ControlFlow<B> { + match e.kind { + ExprKind::Block(Block { expr: Some(e), .. }, _) => for_each_value_source(e, f), + ExprKind::Match(_, arms, _) => { + for arm in arms { + for_each_value_source(arm.body, f)?; + } + ControlFlow::Continue(()) + }, + ExprKind::If(_, if_expr, Some(else_expr)) => { + for_each_value_source(if_expr, f)?; + for_each_value_source(else_expr, f) + }, + ExprKind::DropTemps(e) => for_each_value_source(e, f), + _ => f(e), + } +} + +/// Runs the given function for each path expression referencing the given local which occur after +/// the given expression. +pub fn for_each_local_use_after_expr<'tcx, B>( + cx: &LateContext<'tcx>, + local_id: HirId, + expr_id: HirId, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, +) -> ControlFlow<B> { + struct V<'cx, 'tcx, F, B> { + cx: &'cx LateContext<'tcx>, + local_id: HirId, + expr_id: HirId, + found: bool, + res: ControlFlow<B>, + f: F, + } + impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + if !self.found { + if e.hir_id == self.expr_id { + self.found = true; + } else { + walk_expr(self, e); + } + return; + } + if self.res.is_break() { + return; + } + if path_to_local_id(e, self.local_id) { + self.res = (self.f)(e); + } else { + walk_expr(self, e); + } + } + } + + if let Some(b) = get_enclosing_block(cx, local_id) { + let mut v = V { + cx, + local_id, + expr_id, + found: false, + res: ControlFlow::Continue(()), + f, + }; + v.visit_block(b); + v.res + } else { + ControlFlow::Continue(()) + } +} + +// Calls the given function for every unconsumed temporary created by the expression. Note the +// function is only guaranteed to be called for types which need to be dropped, but it may be called +// for other types. +pub fn for_each_unconsumed_temporary<'tcx, B>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + mut f: impl FnMut(Ty<'tcx>) -> ControlFlow<B>, +) -> ControlFlow<B> { + // Todo: Handle partially consumed values. + fn helper<'tcx, B>( + typeck: &'tcx TypeckResults<'tcx>, + consume: bool, + e: &'tcx Expr<'tcx>, + f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow<B>, + ) -> ControlFlow<B> { + if !consume + || matches!( + typeck.expr_adjustments(e), + [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_)) + ) + { + match e.kind { + ExprKind::Path(QPath::Resolved(None, p)) + if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) => + { + f(typeck.expr_ty(e))?; + }, + ExprKind::Path(_) + | ExprKind::Unary(UnOp::Deref, _) + | ExprKind::Index(..) + | ExprKind::Field(..) + | ExprKind::AddrOf(..) => (), + _ => f(typeck.expr_ty(e))?, + } + } + match e.kind { + ExprKind::AddrOf(_, _, e) + | ExprKind::Field(e, _) + | ExprKind::Unary(UnOp::Deref, e) + | ExprKind::Match(e, ..) + | ExprKind::Let(&Let { init: e, .. }) => { + helper(typeck, false, e, f)?; + }, + ExprKind::Block(&Block { expr: Some(e), .. }, _) + | ExprKind::Box(e) + | ExprKind::Cast(e, _) + | ExprKind::Unary(_, e) => { + helper(typeck, true, e, f)?; + }, + ExprKind::Call(callee, args) => { + helper(typeck, true, callee, f)?; + for arg in args { + helper(typeck, true, arg, f)?; + } + }, + ExprKind::MethodCall(_, args, _) | ExprKind::Tup(args) | ExprKind::Array(args) => { + for arg in args { + helper(typeck, true, arg, f)?; + } + }, + ExprKind::Index(borrowed, consumed) + | ExprKind::Assign(borrowed, consumed, _) + | ExprKind::AssignOp(_, borrowed, consumed) => { + helper(typeck, false, borrowed, f)?; + helper(typeck, true, consumed, f)?; + }, + ExprKind::Binary(_, lhs, rhs) => { + helper(typeck, true, lhs, f)?; + helper(typeck, true, rhs, f)?; + }, + ExprKind::Struct(_, fields, default) => { + for field in fields { + helper(typeck, true, field.expr, f)?; + } + if let Some(default) = default { + helper(typeck, false, default, f)?; + } + }, + ExprKind::If(cond, then, else_expr) => { + helper(typeck, true, cond, f)?; + helper(typeck, true, then, f)?; + if let Some(else_expr) = else_expr { + helper(typeck, true, else_expr, f)?; + } + }, + ExprKind::Type(e, _) => { + helper(typeck, consume, e, f)?; + }, + + // Either drops temporaries, jumps out of the current expression, or has no sub expression. + ExprKind::DropTemps(_) + | ExprKind::Ret(_) + | ExprKind::Break(..) + | ExprKind::Yield(..) + | ExprKind::Block(..) + | ExprKind::Loop(..) + | ExprKind::Repeat(..) + | ExprKind::Lit(_) + | ExprKind::ConstBlock(_) + | ExprKind::Closure { .. } + | ExprKind::Path(_) + | ExprKind::Continue(_) + | ExprKind::InlineAsm(_) + | ExprKind::Err => (), + } + ControlFlow::Continue(()) + } + helper(cx.typeck_results(), true, e, &mut f) +} + +pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + for_each_unconsumed_temporary(cx, e, |ty| { + if needs_ordered_drop(cx, ty) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() +} + +/// Runs the given function for each path expression referencing the given local which occur after +/// the given expression. +pub fn for_each_local_assignment<'tcx, B>( + cx: &LateContext<'tcx>, + local_id: HirId, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, +) -> ControlFlow<B> { + struct V<'cx, 'tcx, F, B> { + cx: &'cx LateContext<'tcx>, + local_id: HirId, + res: ControlFlow<B>, + f: F, + } + impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + if let ExprKind::Assign(lhs, rhs, _) = e.kind + && self.res.is_continue() + && path_to_local_id(lhs, self.local_id) + { + self.res = (self.f)(rhs); + self.visit_expr(rhs); + } else { + walk_expr(self, e); + } + } + } + + if let Some(b) = get_enclosing_block(cx, local_id) { + let mut v = V { + cx, + local_id, + res: ControlFlow::Continue(()), + f, + }; + v.visit_block(b); + v.res + } else { + ControlFlow::Continue(()) + } +} |