summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /src/tools/clippy/clippy_utils
parentInitial commit. (diff)
downloadrustc-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')
-rw-r--r--src/tools/clippy/clippy_utils/Cargo.toml18
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs710
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils/ident_iter.rs45
-rw-r--r--src/tools/clippy/clippy_utils/src/attrs.rs159
-rw-r--r--src/tools/clippy/clippy_utils/src/comparisons.rs36
-rw-r--r--src/tools/clippy/clippy_utils/src/consts.rs652
-rw-r--r--src/tools/clippy/clippy_utils/src/diagnostics.rs249
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs234
-rw-r--r--src/tools/clippy/clippy_utils/src/higher.rs469
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs1031
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs2304
-rw-r--r--src/tools/clippy/clippy_utils/src/macros.rs583
-rw-r--r--src/tools/clippy/clippy_utils/src/msrvs.rs39
-rw-r--r--src/tools/clippy/clippy_utils/src/numeric_literal.rs248
-rw-r--r--src/tools/clippy/clippy_utils/src/paths.rs196
-rw-r--r--src/tools/clippy/clippy_utils/src/ptr.rs57
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs371
-rw-r--r--src/tools/clippy/clippy_utils/src/source.rs508
-rw-r--r--src/tools/clippy/clippy_utils/src/str_utils.rs325
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs1099
-rw-r--r--src/tools/clippy/clippy_utils/src/sym_helper.rs7
-rw-r--r--src/tools/clippy/clippy_utils/src/ty.rs829
-rw-r--r--src/tools/clippy/clippy_utils/src/usage.rs216
-rw-r--r--src/tools/clippy/clippy_utils/src/visitors.rs733
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, _) = &current.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(())
+ }
+}