summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_utils')
-rw-r--r--src/tools/clippy/clippy_utils/Cargo.toml5
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs27
-rw-r--r--src/tools/clippy/clippy_utils/src/check_proc_macro.rs6
-rw-r--r--src/tools/clippy/clippy_utils/src/consts.rs176
-rw-r--r--src/tools/clippy/clippy_utils/src/diagnostics.rs12
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs52
-rw-r--r--src/tools/clippy/clippy_utils/src/higher.rs77
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs118
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs450
-rw-r--r--src/tools/clippy/clippy_utils/src/paths.rs9
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs7
-rw-r--r--src/tools/clippy/clippy_utils/src/ty.rs74
-rw-r--r--src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs25
13 files changed, 779 insertions, 259 deletions
diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml
index c9b01a68f..5d23326ce 100644
--- a/src/tools/clippy/clippy_utils/Cargo.toml
+++ b/src/tools/clippy/clippy_utils/Cargo.toml
@@ -1,14 +1,13 @@
[package]
name = "clippy_utils"
-version = "0.1.75"
+version = "0.1.76"
edition = "2021"
publish = false
[dependencies]
clippy_config = { path = "../clippy_config" }
arrayvec = { version = "0.7", default-features = false }
-if_chain = "1.0"
-itertools = "0.10.1"
+itertools = "0.11"
rustc-semver = "1.1"
[features]
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index a2c61e07b..c271e4986 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -188,7 +188,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
Closure(box ast::Closure {
binder: lb,
capture_clause: lc,
- asyncness: la,
+ coroutine_kind: la,
movability: lm,
fn_decl: lf,
body: le,
@@ -197,7 +197,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
Closure(box ast::Closure {
binder: rb,
capture_clause: rc,
- asyncness: ra,
+ coroutine_kind: ra,
movability: rm,
fn_decl: rf,
body: re,
@@ -206,7 +206,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
) => {
eq_closure_binder(lb, rb)
&& lc == rc
- && la.is_async() == ra.is_async()
+ && la.map_or(false, CoroutineKind::is_async) == ra.map_or(false, CoroutineKind::is_async)
&& lm == rm
&& eq_fn_decl(lf, rf)
&& eq_expr(le, re)
@@ -236,7 +236,7 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
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.body, &r.body)
&& eq_expr_opt(&l.guard, &r.guard)
&& over(&l.attrs, &r.attrs, eq_attr)
}
@@ -546,7 +546,9 @@ 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),
+ (Struct { fields: l, .. }, Struct { fields: r, .. }) | (Tuple(l, _), Tuple(r, _)) => {
+ over(l, r, eq_struct_field)
+ },
_ => false,
}
}
@@ -563,9 +565,22 @@ pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
}
+fn eq_opt_coroutine_kind(l: Option<CoroutineKind>, r: Option<CoroutineKind>) -> bool {
+ matches!(
+ (l, r),
+ (Some(CoroutineKind::Async { .. }), Some(CoroutineKind::Async { .. }))
+ | (Some(CoroutineKind::Gen { .. }), Some(CoroutineKind::Gen { .. }))
+ | (
+ Some(CoroutineKind::AsyncGen { .. }),
+ Some(CoroutineKind::AsyncGen { .. })
+ )
+ | (None, None)
+ )
+}
+
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()
+ && eq_opt_coroutine_kind(l.coroutine_kind, r.coroutine_kind)
&& matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
&& eq_ext(&l.ext, &r.ext)
}
diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
index 2f619a306..d751aeaf9 100644
--- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
+++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
@@ -200,7 +200,7 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
ItemKind::TyAlias(..) | ItemKind::OpaqueTy(_) => (Pat::Str("type"), Pat::Str(";")),
ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
- ItemKind::Struct(VariantData::Struct(..), _) => (Pat::Str("struct"), Pat::Str("}")),
+ ItemKind::Struct(VariantData::Struct { .. }, _) => (Pat::Str("struct"), Pat::Str("}")),
ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
ItemKind::Trait(_, Unsafety::Unsafe, ..)
@@ -255,7 +255,7 @@ fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
match v.data {
- VariantData::Struct(..) => (Pat::Sym(v.ident.name), Pat::Str("}")),
+ VariantData::Struct { .. } => (Pat::Sym(v.ident.name), Pat::Str("}")),
VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
}
@@ -267,7 +267,7 @@ fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirI
FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, body.value).1),
};
- let start_pat = match tcx.hir().get(hir_id) {
+ let start_pat = match tcx.hir_node(hir_id) {
Node::Item(Item { vis_span, .. }) | Node::ImplItem(ImplItem { vis_span, .. }) => {
if vis_span.is_empty() {
start_pat
diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs
index b581a60de..727f93c83 100644
--- a/src/tools/clippy/clippy_utils/src/consts.rs
+++ b/src/tools/clippy/clippy_utils/src/consts.rs
@@ -2,7 +2,7 @@
use crate::source::{get_source_text, walk_span_to_context};
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};
@@ -10,7 +10,7 @@ use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item
use rustc_lexer::tokenize;
use rustc_lint::LateContext;
use rustc_middle::mir::interpret::{alloc_range, Scalar};
-use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, List, ScalarInt, Ty, TyCtxt};
+use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, IntTy, List, ScalarInt, Ty, TyCtxt, UintTy};
use rustc_middle::{bug, mir, span_bug};
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::SyntaxContext;
@@ -51,6 +51,63 @@ pub enum Constant<'tcx> {
Err,
}
+trait IntTypeBounds: Sized {
+ type Output: PartialOrd;
+
+ fn min_max(self) -> Option<(Self::Output, Self::Output)>;
+ fn bits(self) -> Self::Output;
+ fn ensure_fits(self, val: Self::Output) -> Option<Self::Output> {
+ let (min, max) = self.min_max()?;
+ (min <= val && val <= max).then_some(val)
+ }
+}
+impl IntTypeBounds for UintTy {
+ type Output = u128;
+ fn min_max(self) -> Option<(Self::Output, Self::Output)> {
+ Some(match self {
+ UintTy::U8 => (u8::MIN.into(), u8::MAX.into()),
+ UintTy::U16 => (u16::MIN.into(), u16::MAX.into()),
+ UintTy::U32 => (u32::MIN.into(), u32::MAX.into()),
+ UintTy::U64 => (u64::MIN.into(), u64::MAX.into()),
+ UintTy::U128 => (u128::MIN, u128::MAX),
+ UintTy::Usize => (usize::MIN.try_into().ok()?, usize::MAX.try_into().ok()?),
+ })
+ }
+ fn bits(self) -> Self::Output {
+ match self {
+ UintTy::U8 => 8,
+ UintTy::U16 => 16,
+ UintTy::U32 => 32,
+ UintTy::U64 => 64,
+ UintTy::U128 => 128,
+ UintTy::Usize => usize::BITS.into(),
+ }
+ }
+}
+impl IntTypeBounds for IntTy {
+ type Output = i128;
+ fn min_max(self) -> Option<(Self::Output, Self::Output)> {
+ Some(match self {
+ IntTy::I8 => (i8::MIN.into(), i8::MAX.into()),
+ IntTy::I16 => (i16::MIN.into(), i16::MAX.into()),
+ IntTy::I32 => (i32::MIN.into(), i32::MAX.into()),
+ IntTy::I64 => (i64::MIN.into(), i64::MAX.into()),
+ IntTy::I128 => (i128::MIN, i128::MAX),
+ IntTy::Isize => (isize::MIN.try_into().ok()?, isize::MAX.try_into().ok()?),
+ })
+ }
+ fn bits(self) -> Self::Output {
+ match self {
+ IntTy::I8 => 8,
+ IntTy::I16 => 16,
+ IntTy::I32 => 32,
+ IntTy::I64 => 64,
+ IntTy::I128 => 128,
+ IntTy::Isize => isize::BITS.into(),
+ }
+ }
+}
+
impl<'tcx> PartialEq for Constant<'tcx> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
@@ -372,27 +429,25 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
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
- }
+ if args.is_empty()
+ && let ExprKind::Path(qpath) = &callee.kind
+ && let res = self.typeck_results.qpath_res(qpath, callee.hir_id)
+ && let Some(def_id) = res.opt_def_id()
+ && let def_path = self.lcx.get_def_path(def_id)
+ && let def_path = def_path.iter().take(4).map(Symbol::as_str).collect::<Vec<_>>()
+ && let ["core", "num", int_impl, "max_value"] = *def_path
+ {
+ 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),
@@ -435,8 +490,15 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
match *o {
Int(value) => {
let ty::Int(ity) = *ty.kind() else { return None };
+ let (min, _) = ity.min_max()?;
// sign extend
let value = sext(self.lcx.tcx, value, ity);
+
+ // Applying unary - to the most negative value of any signed integer type panics.
+ if value == min {
+ return None;
+ }
+
let value = value.checked_neg()?;
// clear unused bits
Some(Int(unsext(self.lcx.tcx, value, ity)))
@@ -469,7 +531,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
kind: ExprKind::Lit(_),
span,
..
- }) = self.lcx.tcx.hir().get(body_id.hir_id)
+ }) = self.lcx.tcx.hir_node(body_id.hir_id)
&& is_direct_expn_of(*span, "cfg").is_some()
{
return None;
@@ -572,17 +634,33 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
match (l, r) {
(Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() {
ty::Int(ity) => {
+ let (ty_min_value, _) = ity.min_max()?;
+ let bits = ity.bits();
let l = sext(self.lcx.tcx, l, ity);
let r = sext(self.lcx.tcx, r, ity);
+
+ // Using / or %, where the left-hand argument is the smallest integer of a signed integer type and
+ // the right-hand argument is -1 always panics, even with overflow-checks disabled
+ if let BinOpKind::Div | BinOpKind::Rem = op.node
+ && l == ty_min_value
+ && r == -1
+ {
+ return None;
+ }
+
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),
+ // When +, * or binary - create a value greater than the maximum value, or less than
+ // the minimum value that can be stored, it panics.
+ BinOpKind::Add => l.checked_add(r).and_then(|n| ity.ensure_fits(n)).map(zext),
+ BinOpKind::Sub => l.checked_sub(r).and_then(|n| ity.ensure_fits(n)).map(zext),
+ BinOpKind::Mul => l.checked_mul(r).and_then(|n| ity.ensure_fits(n)).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().ok()?).map(zext),
- BinOpKind::Shl => l.checked_shl(r.try_into().ok()?).map(zext),
+ // Using << or >> where the right-hand argument is greater than or equal to the number of bits
+ // in the type of the left-hand argument, or is negative panics.
+ BinOpKind::Shr if r < bits && !r.is_negative() => l.checked_shr(r.try_into().ok()?).map(zext),
+ BinOpKind::Shl if r < bits && !r.is_negative() => l.checked_shl(r.try_into().ok()?).map(zext),
BinOpKind::BitXor => Some(zext(l ^ r)),
BinOpKind::BitOr => Some(zext(l | r)),
BinOpKind::BitAnd => Some(zext(l & r)),
@@ -595,24 +673,28 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
_ => 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().ok()?).map(Constant::Int),
- BinOpKind::Shl => l.checked_shl(r.try_into().ok()?).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,
+ ty::Uint(ity) => {
+ let bits = ity.bits();
+
+ match op.node {
+ BinOpKind::Add => l.checked_add(r).and_then(|n| ity.ensure_fits(n)).map(Constant::Int),
+ BinOpKind::Sub => l.checked_sub(r).and_then(|n| ity.ensure_fits(n)).map(Constant::Int),
+ BinOpKind::Mul => l.checked_mul(r).and_then(|n| ity.ensure_fits(n)).map(Constant::Int),
+ BinOpKind::Div => l.checked_div(r).map(Constant::Int),
+ BinOpKind::Rem => l.checked_rem(r).map(Constant::Int),
+ BinOpKind::Shr if r < bits => l.checked_shr(r.try_into().ok()?).map(Constant::Int),
+ BinOpKind::Shl if r < bits => l.checked_shl(r.try_into().ok()?).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,
},
diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs
index 45c7e3a6e..756296153 100644
--- a/src/tools/clippy/clippy_utils/src/diagnostics.rs
+++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs
@@ -46,9 +46,9 @@ fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) {
/// | ^^^^^^^^^^^^^^^^^^^^^^^
/// ```
pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
+ #[expect(clippy::disallowed_methods)]
cx.struct_span_lint(lint, sp, msg.to_string(), |diag| {
docs_link(diag, lint);
- diag
});
}
@@ -80,6 +80,7 @@ pub fn span_lint_and_help<T: LintContext>(
help_span: Option<Span>,
help: &str,
) {
+ #[expect(clippy::disallowed_methods)]
cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
let help = help.to_string();
if let Some(help_span) = help_span {
@@ -88,7 +89,6 @@ pub fn span_lint_and_help<T: LintContext>(
diag.help(help.to_string());
}
docs_link(diag, lint);
- diag
});
}
@@ -123,6 +123,7 @@ pub fn span_lint_and_note<T: LintContext>(
note_span: Option<Span>,
note: &str,
) {
+ #[expect(clippy::disallowed_methods)]
cx.struct_span_lint(lint, span, msg.to_string(), |diag| {
let note = note.to_string();
if let Some(note_span) = note_span {
@@ -131,7 +132,6 @@ pub fn span_lint_and_note<T: LintContext>(
diag.note(note);
}
docs_link(diag, lint);
- diag
});
}
@@ -145,17 +145,17 @@ where
S: Into<MultiSpan>,
F: FnOnce(&mut Diagnostic),
{
+ #[expect(clippy::disallowed_methods)]
cx.struct_span_lint(lint, sp, msg.to_string(), |diag| {
f(diag);
docs_link(diag, lint);
- diag
});
}
pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
+ #[expect(clippy::disallowed_methods)]
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg.to_string(), |diag| {
docs_link(diag, lint);
- diag
});
}
@@ -167,10 +167,10 @@ pub fn span_lint_hir_and_then(
msg: &str,
f: impl FnOnce(&mut Diagnostic),
) {
+ #[expect(clippy::disallowed_methods)]
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg.to_string(), |diag| {
f(diag);
docs_link(diag, lint);
- diag
});
}
diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
index 0bcefba75..4e71c6483 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -9,12 +9,13 @@
//! - or-fun-call
//! - option-if-let-else
+use crate::consts::{constant, FullInt};
use crate::ty::{all_predicates_of, is_copy};
use crate::visitors::is_const_evaluatable;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, Visitor};
-use rustc_hir::{Block, Expr, ExprKind, QPath, UnOp};
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::adjustment::Adjust;
@@ -193,6 +194,12 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
self.eagerness = Lazy;
}
},
+
+ // `-i32::MIN` panics with overflow checks
+ ExprKind::Unary(UnOp::Neg, right) if constant(self.cx, self.cx.typeck_results(), right).is_none() => {
+ self.eagerness |= NoChange;
+ },
+
// Custom `Deref` impl might have side effects
ExprKind::Unary(UnOp::Deref, e)
if self.cx.typeck_results().expr_ty(e).builtin_deref(true).is_none() =>
@@ -207,6 +214,49 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
self.cx.typeck_results().expr_ty(e).kind(),
ty::Bool | ty::Int(_) | ty::Uint(_),
) => {},
+
+ // `>>` and `<<` panic when the right-hand side is greater than or equal to the number of bits in the
+ // type of the left-hand side, or is negative.
+ // We intentionally only check if the right-hand isn't a constant, because even if the suggestion would
+ // overflow with constants, the compiler emits an error for it and the programmer will have to fix it.
+ // Thus, we would realistically only delay the lint.
+ ExprKind::Binary(op, _, right)
+ if matches!(op.node, BinOpKind::Shl | BinOpKind::Shr)
+ && constant(self.cx, self.cx.typeck_results(), right).is_none() =>
+ {
+ self.eagerness |= NoChange;
+ },
+
+ ExprKind::Binary(op, left, right)
+ if matches!(op.node, BinOpKind::Div | BinOpKind::Rem)
+ && let right_ty = self.cx.typeck_results().expr_ty(right)
+ && let left = constant(self.cx, self.cx.typeck_results(), left)
+ && let right = constant(self.cx, self.cx.typeck_results(), right)
+ .and_then(|c| c.int_value(self.cx, right_ty))
+ && matches!(
+ (left, right),
+ // `1 / x`: x might be zero
+ (_, None)
+ // `x / -1`: x might be T::MIN
+ | (None, Some(FullInt::S(-1)))
+ ) =>
+ {
+ self.eagerness |= NoChange;
+ },
+
+ // Similar to `>>` and `<<`, we only want to avoid linting entirely if either side is unknown and the
+ // compiler can't emit an error for an overflowing expression.
+ // Suggesting eagerness for `true.then(|| i32::MAX + 1)` is okay because the compiler will emit an
+ // error and it's good to have the eagerness warning up front when the user fixes the logic error.
+ ExprKind::Binary(op, left, right)
+ if matches!(op.node, BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul)
+ && !self.cx.typeck_results().expr_ty(e).is_floating_point()
+ && (constant(self.cx, self.cx.typeck_results(), left).is_none()
+ || constant(self.cx, self.cx.typeck_results(), right).is_none()) =>
+ {
+ self.eagerness |= NoChange;
+ },
+
ExprKind::Binary(_, lhs, rhs)
if self.cx.typeck_results().expr_ty(lhs).is_primitive()
&& self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs
index edea4b366..3135a0336 100644
--- a/src/tools/clippy/clippy_utils/src/higher.rs
+++ b/src/tools/clippy/clippy_utils/src/higher.rs
@@ -5,7 +5,7 @@
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};
@@ -30,24 +30,22 @@ pub struct ForLoop<'tcx> {
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,
- });
- }
+ if let hir::ExprKind::DropTemps(e) = expr.kind
+ && let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind
+ && let hir::ExprKind::Call(_, [arg]) = iterexpr.kind
+ && let hir::ExprKind::Loop(block, ..) = arm.body.kind
+ && let [stmt] = block.stmts
+ && let hir::StmtKind::Expr(e) = stmt.kind
+ && let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind
+ && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
+ {
+ 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
}
@@ -277,29 +275,28 @@ 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 let hir::ExprKind::Call(_, [arg]) = &args[0].kind
- && let hir::ExprKind::Array(args) = arg.kind {
- Some(VecArgs::Vec(args))
- } else {
- None
- }
- } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
- Some(VecArgs::Vec(&[]))
+ if let hir::ExprKind::Call(fun, args) = expr.kind
+ && let hir::ExprKind::Path(ref qpath) = fun.kind
+ && is_expn_of(fun.span, "vec").is_some()
+ && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
+ {
+ 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 let hir::ExprKind::Call(_, [arg]) = &args[0].kind
+ && let hir::ExprKind::Array(args) = arg.kind
+ {
+ Some(VecArgs::Vec(args))
} else {
None
- };
- }
+ }
+ } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
+ Some(VecArgs::Vec(&[]))
+ } else {
+ None
+ };
}
None
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index 2a8b2ebd5..e610ed930 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -247,7 +247,7 @@ impl HirEqInterExpr<'_, '_, '_> {
res
}
- #[expect(clippy::similar_names)]
+ #[expect(clippy::similar_names, clippy::too_many_lines)]
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
return false;
@@ -271,9 +271,7 @@ impl HirEqInterExpr<'_, '_, '_> {
(&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::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
(&ExprKind::Assign(ll, lr, _), &ExprKind::Assign(rl, rr, _)) => {
self.inner.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
},
@@ -294,9 +292,15 @@ impl HirEqInterExpr<'_, '_, '_> {
(&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)) => {
+ (&ExprKind::Cast(lx, lt), &ExprKind::Cast(rx, rt)) => {
self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
},
+ (&ExprKind::Closure(_l), &ExprKind::Closure(_r)) => false,
+ (&ExprKind::ConstBlock(lb), &ExprKind::ConstBlock(rb)) => self.eq_body(lb.body, rb.body),
+ (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
+ both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
+ },
+ (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re),
(&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)
},
@@ -329,24 +333,70 @@ impl HirEqInterExpr<'_, '_, '_> {
&& self.eq_expr(l_receiver, r_receiver)
&& self.eq_exprs(l_args, r_args)
},
+ (&ExprKind::OffsetOf(l_container, l_fields), &ExprKind::OffsetOf(r_container, r_fields)) => {
+ self.eq_ty(l_container, r_container) && over(l_fields, r_fields, |l, r| l.name == r.name)
+ },
+ (ExprKind::Path(l), ExprKind::Path(r)) => self.eq_qpath(l, r),
(&ExprKind::Repeat(le, ll), &ExprKind::Repeat(re, rl)) => {
self.eq_expr(le, re) && self.eq_array_length(ll, rl)
},
(ExprKind::Ret(l), ExprKind::Ret(r)) => both(l, r, |l, r| self.eq_expr(l, r)),
- (ExprKind::Path(l), ExprKind::Path(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::Type(le, lt), &ExprKind::Type(re, rt)) => self.eq_expr(le, re) && self.eq_ty(lt, rt),
(&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),
- (&ExprKind::OffsetOf(l_container, l_fields), &ExprKind::OffsetOf(r_container, r_fields)) => {
- self.eq_ty(l_container, r_container) && over(l_fields, r_fields, |l, r| l.name == r.name)
- },
- _ => false,
+ (&ExprKind::Yield(le, _), &ExprKind::Yield(re, _)) => return self.eq_expr(le, re),
+ (
+ // Else branches for branches above, grouped as per `match_same_arms`.
+ | &ExprKind::AddrOf(..)
+ | &ExprKind::Array(..)
+ | &ExprKind::Assign(..)
+ | &ExprKind::AssignOp(..)
+ | &ExprKind::Binary(..)
+ | &ExprKind::Become(..)
+ | &ExprKind::Block(..)
+ | &ExprKind::Break(..)
+ | &ExprKind::Call(..)
+ | &ExprKind::Cast(..)
+ | &ExprKind::ConstBlock(..)
+ | &ExprKind::Continue(..)
+ | &ExprKind::DropTemps(..)
+ | &ExprKind::Field(..)
+ | &ExprKind::Index(..)
+ | &ExprKind::If(..)
+ | &ExprKind::Let(..)
+ | &ExprKind::Lit(..)
+ | &ExprKind::Loop(..)
+ | &ExprKind::Match(..)
+ | &ExprKind::MethodCall(..)
+ | &ExprKind::OffsetOf(..)
+ | &ExprKind::Path(..)
+ | &ExprKind::Repeat(..)
+ | &ExprKind::Ret(..)
+ | &ExprKind::Struct(..)
+ | &ExprKind::Tup(..)
+ | &ExprKind::Type(..)
+ | &ExprKind::Unary(..)
+ | &ExprKind::Yield(..)
+
+ // --- Special cases that do not have a positive branch.
+
+ // `Err` represents an invalid expression, so let's never assume that
+ // an invalid expressions is equal to anything.
+ | &ExprKind::Err(..)
+
+ // For the time being, we always consider that two closures are unequal.
+ // This behavior may change in the future.
+ | &ExprKind::Closure(..)
+ // For the time being, we always consider that two instances of InlineAsm are different.
+ // This behavior may change in the future.
+ | &ExprKind::InlineAsm(_)
+ , _
+ ) => false,
};
(is_eq && (!self.should_ignore(left) || !self.should_ignore(right)))
|| self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right))
@@ -684,6 +734,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_name(i.ident.name);
}
},
+ ExprKind::Array(v) => {
+ self.hash_exprs(v);
+ },
ExprKind::Assign(l, r, _) => {
self.hash_expr(l);
self.hash_expr(r);
@@ -693,6 +746,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(l);
self.hash_expr(r);
},
+ ExprKind::Become(f) => {
+ self.hash_expr(f);
+ },
ExprKind::Block(b, _) => {
self.hash_block(b);
},
@@ -709,9 +765,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(j);
}
},
- ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => {
- self.hash_expr(e);
- },
ExprKind::Call(fun, args) => {
self.hash_expr(fun);
self.hash_exprs(args);
@@ -727,6 +780,12 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
// closures inherit TypeckResults
self.hash_expr(self.cx.tcx.hir().body(body).value);
},
+ ExprKind::ConstBlock(ref l_id) => {
+ self.hash_body(l_id.body);
+ },
+ ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => {
+ self.hash_expr(e);
+ },
ExprKind::Field(e, ref f) => {
self.hash_expr(e);
self.hash_name(f.name);
@@ -788,12 +847,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}
}
},
- ExprKind::OffsetOf(container, fields) => {
- self.hash_ty(container);
- for field in fields {
- self.hash_name(field.name);
- }
- },
ExprKind::Let(Let { pat, init, ty, .. }) => {
self.hash_expr(init);
if let Some(ty) = ty {
@@ -801,7 +854,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}
self.hash_pat(pat);
},
- ExprKind::Err(_) => {},
ExprKind::Lit(l) => {
l.node.hash(&mut self.s);
},
@@ -836,8 +888,14 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(receiver);
self.hash_exprs(args);
},
- ExprKind::ConstBlock(ref l_id) => {
- self.hash_body(l_id.body);
+ ExprKind::OffsetOf(container, fields) => {
+ self.hash_ty(container);
+ for field in fields {
+ self.hash_name(field.name);
+ }
+ },
+ ExprKind::Path(ref qpath) => {
+ self.hash_qpath(qpath);
},
ExprKind::Repeat(e, len) => {
self.hash_expr(e);
@@ -848,12 +906,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(e);
}
},
- ExprKind::Become(f) => {
- self.hash_expr(f);
- },
- ExprKind::Path(ref qpath) => {
- self.hash_qpath(qpath);
- },
ExprKind::Struct(path, fields, ref expr) => {
self.hash_qpath(path);
@@ -869,13 +921,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
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);
},
+ ExprKind::Err(_) => {},
}
}
@@ -967,7 +1017,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}
e.hash(&mut self.s);
},
- PatKind::Wild => {},
+ PatKind::Never | PatKind::Wild => {},
}
}
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 1181dfc0e..70a3c6f82 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -71,12 +71,12 @@ pub use self::hir_utils::{
both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
};
+use core::mem;
use core::ops::ControlFlow;
use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault;
use std::sync::{Mutex, MutexGuard, OnceLock};
-use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_data_structures::fx::FxHashMap;
@@ -176,14 +176,12 @@ pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr
/// 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::NONE, ..));
- let parent = hir.parent_id(hir_id);
- if let Some(Node::Local(local)) = hir.find(parent);
- then {
- return local.init;
- }
+ if let Some(Node::Pat(pat)) = cx.tcx.opt_hir_node(hir_id)
+ && matches!(pat.kind, PatKind::Binding(BindingAnnotation::NONE, ..))
+ && let parent = hir.parent_id(hir_id)
+ && let Some(Node::Local(local)) = cx.tcx.opt_hir_node(parent)
+ {
+ return local.init;
}
None
}
@@ -565,7 +563,7 @@ fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symb
let hir = tcx.hir();
let root_mod;
- let item_kind = match hir.find_by_def_id(local_id) {
+ let item_kind = match tcx.opt_hir_node_by_def_id(local_id) {
Some(Node::Crate(r#mod)) => {
root_mod = ItemKind::Mod(r#mod);
&root_mod
@@ -711,15 +709,13 @@ pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
/// ```
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 hir_id = cx.tcx.local_def_id_to_hir_id(def_id);
let parent_impl = cx.tcx.hir().get_parent_item(hir_id);
- if_chain! {
- if parent_impl != hir::CRATE_OWNER_ID;
- if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl.def_id);
- if let hir::ItemKind::Impl(impl_) = &item.kind;
- then {
- return impl_.of_trait.as_ref();
- }
+ if parent_impl != hir::CRATE_OWNER_ID
+ && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(parent_impl.def_id)
+ && let hir::ItemKind::Impl(impl_) = &item.kind
+ {
+ return impl_.of_trait.as_ref();
}
None
}
@@ -823,12 +819,14 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
/// Returns 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 }
+ if let hir::ExprKind::Path(ref repl_func_qpath) = repl_func.kind
+ && let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
+ && (is_diag_trait_item(cx, repl_def_id, sym::Default)
+ || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath))
+ {
+ true
+ } else {
+ false
}
}
@@ -843,14 +841,14 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
_ => 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(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 {
+ ExprKind::Repeat(x, ArrayLen::Body(len)) => {
+ if let ExprKind::Lit(const_lit) = cx.tcx.hir().body(len.body).value.kind
+ && let LitKind::Int(v, _) = const_lit.node
+ && v <= 32
+ && is_default_equivalent(cx, x)
+ {
true
- }
- else {
+ } else {
false
}
},
@@ -1244,7 +1242,7 @@ pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
/// 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).def_id;
- match cx.tcx.hir().find_by_def_id(parent_id) {
+ match cx.tcx.opt_hir_node_by_def_id(parent_id) {
Some(
Node::Item(Item { ident, .. })
| Node::TraitItem(TraitItem { ident, .. })
@@ -1321,7 +1319,7 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
let map = &cx.tcx.hir();
let enclosing_node = map
.get_enclosing_scope(hir_id)
- .and_then(|enclosing_id| map.find(enclosing_id));
+ .and_then(|enclosing_id| cx.tcx.opt_hir_node(enclosing_id));
enclosing_node.and_then(|node| match node {
Node::Block(block) => Some(block),
Node::Item(&Item {
@@ -1489,6 +1487,43 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
}
}
+/// Checks if the given expression is a part of `let else`
+/// returns `true` for both the `init` and the `else` part
+pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ let mut child_id = expr.hir_id;
+ for (parent_id, node) in tcx.hir().parent_iter(child_id) {
+ if let Node::Local(Local {
+ init: Some(init),
+ els: Some(els),
+ ..
+ }) = node
+ && (init.hir_id == child_id || els.hir_id == child_id)
+ {
+ return true;
+ }
+
+ child_id = parent_id;
+ }
+
+ false
+}
+
+/// Checks if the given expression is the else clause of a `let else` expression
+pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ let mut child_id = expr.hir_id;
+ for (parent_id, node) in tcx.hir().parent_iter(child_id) {
+ if let Node::Local(Local { els: Some(els), .. }) = node
+ && els.hir_id == child_id
+ {
+ return true;
+ }
+
+ child_id = parent_id;
+ }
+
+ false
+}
+
/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
/// For the lower bound, this means that:
/// - either there is none
@@ -1632,13 +1667,13 @@ pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
/// Convenience function to get the return type of a function.
pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: hir::OwnerId) -> Ty<'tcx> {
let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().output();
- cx.tcx.erase_late_bound_regions(ret_ty)
+ cx.tcx.instantiate_bound_regions_with_erased(ret_ty)
}
/// Convenience function to get the nth argument type of a function.
pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: hir::OwnerId, nth: usize) -> Ty<'tcx> {
let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().input(nth);
- cx.tcx.erase_late_bound_regions(arg)
+ cx.tcx.instantiate_bound_regions_with_erased(arg)
}
/// Checks if an expression is constructing a tuple-like enum variant or struct
@@ -1671,7 +1706,7 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
}
match pat.kind {
- PatKind::Wild => false,
+ PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable.
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,
@@ -1736,15 +1771,13 @@ pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl It
/// 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, ddpos) = arm.pat.kind;
- if ddpos.as_opt_usize().is_none();
- if is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultOk);
- if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
- if path_to_local_id(arm.body, hir_id);
- then {
- return true;
- }
+ if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind
+ && ddpos.as_opt_usize().is_none()
+ && is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultOk)
+ && let PatKind::Binding(_, hir_id, _, None) = pat[0].kind
+ && path_to_local_id(arm.body, hir_id)
+ {
+ return true;
}
false
}
@@ -1763,14 +1796,12 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
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);
- }
+ if arms.len() == 2
+ && arms[0].guard.is_none()
+ && arms[1].guard.is_none()
+ && ((is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])))
+ {
+ return Some(expr);
}
}
@@ -1887,14 +1918,12 @@ pub fn match_function_call<'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);
- }
+ if let ExprKind::Call(fun, args) = expr.kind
+ && let ExprKind::Path(ref qpath) = fun.kind
+ && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
+ && match_def_path(cx, fun_def_id, path)
+ {
+ return Some(args);
};
None
}
@@ -1904,13 +1933,11 @@ pub fn match_function_call_with_def_id<'tcx>(
expr: &'tcx Expr<'_>,
fun_def_id: DefId,
) -> Option<&'tcx [Expr<'tcx>]> {
- if_chain! {
- if let ExprKind::Call(fun, args) = expr.kind;
- if let ExprKind::Path(ref qpath) = fun.kind;
- if cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id);
- then {
- return Some(args);
- }
+ if let ExprKind::Call(fun, args) = expr.kind
+ && let ExprKind::Path(ref qpath) = fun.kind
+ && cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id)
+ {
+ return Some(args);
};
None
}
@@ -2008,10 +2035,10 @@ pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'t
// 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 {
+ ExprKind::Call(path, _) => {
+ if let ExprKind::Path(ref qpath) = path.kind
+ && let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id)
+ {
Some(did)
} else {
None
@@ -2034,6 +2061,18 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
fn check_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
+ if cx
+ .typeck_results()
+ .pat_binding_modes()
+ .get(pat.hir_id)
+ .is_some_and(|mode| matches!(mode, BindingMode::BindByReference(_)))
+ {
+ // If a tuple `(x, y)` is of type `&(i32, i32)`, then due to match ergonomics,
+ // the inner patterns become references. Don't consider this the identity function
+ // as that changes types.
+ return false;
+ }
+
match (pat.kind, expr.kind) {
(PatKind::Binding(_, id, _, _), _) => {
path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty()
@@ -2071,14 +2110,12 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
},
_,
) => {
- 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;
- }
+ if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind
+ && let ExprKind::Ret(Some(ret_val)) = e.kind
+ {
+ expr = ret_val;
+ } else {
+ return false;
}
},
_ => return check_pat(cx, param.pat, expr),
@@ -2530,7 +2567,7 @@ pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
tcx.has_attr(def_id, sym::cfg)
|| hir
- .parent_iter(hir.local_def_id_to_hir_id(def_id))
+ .parent_iter(tcx.local_def_id_to_hir_id(def_id))
.flat_map(|(parent_id, _)| hir.attrs(parent_id))
.any(|attr| attr.has_name(sym::cfg))
}
@@ -2650,11 +2687,11 @@ impl<'tcx> ExprUseNode<'tcx> {
.and(Binder::dummy(cx.tcx.type_of(id).instantiate_identity())),
)),
Self::Return(id) => {
- let hir_id = cx.tcx.hir().local_def_id_to_hir_id(id.def_id);
+ let hir_id = cx.tcx.local_def_id_to_hir_id(id.def_id);
if let Some(Node::Expr(Expr {
kind: ExprKind::Closure(c),
..
- })) = cx.tcx.hir().find(hir_id)
+ })) = cx.tcx.opt_hir_node(hir_id)
{
match c.fn_decl.output {
FnRetTy::DefaultReturn(_) => None,
@@ -2720,7 +2757,7 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Optio
walk_to_expr_usage(cx, e, &mut |parent, child_id| {
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
if adjustments.is_empty()
- && let Node::Expr(e) = cx.tcx.hir().get(child_id)
+ && let Node::Expr(e) = cx.tcx.hir_node(child_id)
{
adjustments = cx.typeck_results().expr_adjustments(e);
}
@@ -2974,3 +3011,248 @@ pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: i
_ => false,
}
}
+
+#[derive(Clone, Copy)]
+pub enum RequiresSemi {
+ Yes,
+ No,
+}
+impl RequiresSemi {
+ pub fn requires_semi(self) -> bool {
+ matches!(self, Self::Yes)
+ }
+}
+
+/// Check if the expression return `!`, a type coerced from `!`, or could return `!` if the final
+/// expression were turned into a statement.
+#[expect(clippy::too_many_lines)]
+pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> {
+ struct BreakTarget {
+ id: HirId,
+ unused: bool,
+ }
+
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ break_targets: Vec<BreakTarget>,
+ break_targets_for_result_ty: u32,
+ in_final_expr: bool,
+ requires_semi: bool,
+ is_never: bool,
+ }
+
+ impl<'tcx> V<'_, 'tcx> {
+ fn push_break_target(&mut self, id: HirId) {
+ self.break_targets.push(BreakTarget { id, unused: true });
+ self.break_targets_for_result_ty += u32::from(self.in_final_expr);
+ }
+ }
+
+ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ // Note: Part of the complexity here comes from the fact that
+ // coercions are applied to the innermost expression.
+ // e.g. In `let x: u32 = { break () };` the never-to-any coercion
+ // is applied to the break expression. This means we can't just
+ // check the block's type as it will be `u32` despite the fact
+ // that the block always diverges.
+
+ // The rest of the complexity comes from checking blocks which
+ // syntactically return a value, but will always diverge before
+ // reaching that point.
+ // e.g. In `let x = { foo(panic!()) };` the block's type will be the
+ // return type of `foo` even though it will never actually run. This
+ // can be trivially fixed by adding a semicolon after the call, but
+ // we must first detect that a semicolon is needed to make that
+ // suggestion.
+
+ if self.is_never && self.break_targets.is_empty() {
+ if self.in_final_expr && !self.requires_semi {
+ // This expression won't ever run, but we still need to check
+ // if it can affect the type of the final expression.
+ match e.kind {
+ ExprKind::DropTemps(e) => self.visit_expr(e),
+ ExprKind::If(_, then, Some(else_)) => {
+ self.visit_expr(then);
+ self.visit_expr(else_);
+ },
+ ExprKind::Match(_, arms, _) => {
+ for arm in arms {
+ self.visit_expr(arm.body);
+ }
+ },
+ ExprKind::Loop(b, ..) => {
+ self.push_break_target(e.hir_id);
+ self.in_final_expr = false;
+ self.visit_block(b);
+ self.break_targets.pop();
+ },
+ ExprKind::Block(b, _) => {
+ if b.targeted_by_break {
+ self.push_break_target(b.hir_id);
+ self.visit_block(b);
+ self.break_targets.pop();
+ } else {
+ self.visit_block(b);
+ }
+ },
+ _ => {
+ self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never();
+ },
+ }
+ }
+ return;
+ }
+ match e.kind {
+ ExprKind::DropTemps(e) => self.visit_expr(e),
+ ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true,
+ ExprKind::Ret(Some(e)) | ExprKind::Become(e) => {
+ self.in_final_expr = false;
+ self.visit_expr(e);
+ self.is_never = true;
+ },
+ ExprKind::Break(dest, e) => {
+ if let Some(e) = e {
+ self.in_final_expr = false;
+ self.visit_expr(e);
+ }
+ if let Ok(id) = dest.target_id
+ && let Some((i, target)) = self
+ .break_targets
+ .iter_mut()
+ .enumerate()
+ .find(|(_, target)| target.id == id)
+ {
+ target.unused &= self.is_never;
+ if i < self.break_targets_for_result_ty as usize {
+ self.requires_semi = true;
+ }
+ }
+ self.is_never = true;
+ },
+ ExprKind::If(cond, then, else_) => {
+ let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+ self.visit_expr(cond);
+ self.in_final_expr = in_final_expr;
+
+ if self.is_never {
+ self.visit_expr(then);
+ if let Some(else_) = else_ {
+ self.visit_expr(else_);
+ }
+ } else {
+ self.visit_expr(then);
+ let is_never = mem::replace(&mut self.is_never, false);
+ if let Some(else_) = else_ {
+ self.visit_expr(else_);
+ self.is_never &= is_never;
+ }
+ }
+ },
+ ExprKind::Match(scrutinee, arms, _) => {
+ let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+ self.visit_expr(scrutinee);
+ self.in_final_expr = in_final_expr;
+
+ if self.is_never {
+ for arm in arms {
+ self.visit_arm(arm);
+ }
+ } else {
+ let mut is_never = true;
+ for arm in arms {
+ self.is_never = false;
+ if let Some(guard) = arm.guard {
+ let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+ self.visit_expr(guard.body());
+ self.in_final_expr = in_final_expr;
+ // The compiler doesn't consider diverging guards as causing the arm to diverge.
+ self.is_never = false;
+ }
+ self.visit_expr(arm.body);
+ is_never &= self.is_never;
+ }
+ self.is_never = is_never;
+ }
+ },
+ ExprKind::Loop(b, _, _, _) => {
+ self.push_break_target(e.hir_id);
+ self.in_final_expr = false;
+ self.visit_block(b);
+ self.is_never = self.break_targets.pop().unwrap().unused;
+ },
+ ExprKind::Block(b, _) => {
+ if b.targeted_by_break {
+ self.push_break_target(b.hir_id);
+ self.visit_block(b);
+ self.is_never &= self.break_targets.pop().unwrap().unused;
+ } else {
+ self.visit_block(b);
+ }
+ },
+ _ => {
+ self.in_final_expr = false;
+ walk_expr(self, e);
+ self.is_never |= self.cx.typeck_results().expr_ty(e).is_never();
+ },
+ }
+ }
+
+ fn visit_block(&mut self, b: &'tcx Block<'_>) {
+ let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+ for s in b.stmts {
+ self.visit_stmt(s);
+ }
+ self.in_final_expr = in_final_expr;
+ if let Some(e) = b.expr {
+ self.visit_expr(e);
+ }
+ }
+
+ fn visit_local(&mut self, l: &'tcx Local<'_>) {
+ if let Some(e) = l.init {
+ self.visit_expr(e);
+ }
+ if let Some(else_) = l.els {
+ let is_never = self.is_never;
+ self.visit_block(else_);
+ self.is_never = is_never;
+ }
+ }
+
+ fn visit_arm(&mut self, arm: &Arm<'tcx>) {
+ if let Some(guard) = arm.guard {
+ let in_final_expr = mem::replace(&mut self.in_final_expr, false);
+ self.visit_expr(guard.body());
+ self.in_final_expr = in_final_expr;
+ }
+ self.visit_expr(arm.body);
+ }
+ }
+
+ if cx.typeck_results().expr_ty(e).is_never() {
+ Some(RequiresSemi::No)
+ } else if let ExprKind::Block(b, _) = e.kind
+ && !b.targeted_by_break
+ && b.expr.is_none()
+ {
+ // If a block diverges without a final expression then it's type is `!`.
+ None
+ } else {
+ let mut v = V {
+ cx,
+ break_targets: Vec::new(),
+ break_targets_for_result_ty: 0,
+ in_final_expr: true,
+ requires_semi: false,
+ is_never: false,
+ };
+ v.visit_expr(e);
+ v.is_never
+ .then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) {
+ RequiresSemi::Yes
+ } else {
+ RequiresSemi::No
+ })
+ }
+}
diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs
index 5bca55437..0a820a175 100644
--- a/src/tools/clippy/clippy_utils/src/paths.rs
+++ b/src/tools/clippy/clippy_utils/src/paths.rs
@@ -32,7 +32,15 @@ pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncRead
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_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
+pub const HASHMAP_ITER: [&str; 5] = ["std", "collections", "hash", "map", "Iter"];
+pub const HASHMAP_ITER_MUT: [&str; 5] = ["std", "collections", "hash", "map", "IterMut"];
+pub const HASHMAP_KEYS: [&str; 5] = ["std", "collections", "hash", "map", "Keys"];
+pub const HASHMAP_VALUES: [&str; 5] = ["std", "collections", "hash", "map", "Values"];
+pub const HASHMAP_DRAIN: [&str; 5] = ["std", "collections", "hash", "map", "Drain"];
+pub const HASHMAP_VALUES_MUT: [&str; 5] = ["std", "collections", "hash", "map", "ValuesMut"];
+pub const HASHSET_ITER_TY: [&str; 5] = ["std", "collections", "hash", "set", "Iter"];
pub const HASHSET_ITER: [&str; 6] = ["std", "collections", "hash", "set", "HashSet", "iter"];
+pub const HASHSET_DRAIN: [&str; 5] = ["std", "collections", "hash", "set", "Drain"];
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
@@ -99,3 +107,4 @@ pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"];
pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"];
#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so
pub const BOOL_THEN: [&str; 4] = ["core", "bool", "<impl bool>", "then"];
+pub const ALLOCATOR_GLOBAL: [&str; 3] = ["alloc", "alloc", "Global"];
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index db79dd788..9b2bc8df1 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -159,7 +159,7 @@ impl<'a> Sugg<'a> {
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()),
+ AssocOp::from_ast_binop(op.node),
get_snippet(lhs.span),
get_snippet(rhs.span),
),
@@ -380,10 +380,7 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
| AssocOp::NotEqual
| AssocOp::Greater
| AssocOp::GreaterEqual => {
- format!(
- "{lhs} {} {rhs}",
- op.to_ast_binop().expect("Those are AST ops").to_string()
- )
+ format!("{lhs} {} {rhs}", op.to_ast_binop().expect("Those are AST ops").as_str())
},
AssocOp::Assign => format!("{lhs} = {rhs}"),
AssocOp::AssignOp(op) => {
diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs
index 842a206f9..61d0663aa 100644
--- a/src/tools/clippy/clippy_utils/src/ty.rs
+++ b/src/tools/clippy/clippy_utils/src/ty.rs
@@ -214,7 +214,17 @@ pub fn implements_trait<'tcx>(
trait_id: DefId,
args: &[GenericArg<'tcx>],
) -> bool {
- implements_trait_with_env_from_iter(cx.tcx, cx.param_env, ty, trait_id, args.iter().map(|&x| Some(x)))
+ let callee_id = cx
+ .enclosing_body
+ .map(|body| cx.tcx.hir().body_owner(body).owner.to_def_id());
+ implements_trait_with_env_from_iter(
+ cx.tcx,
+ cx.param_env,
+ ty,
+ trait_id,
+ callee_id,
+ args.iter().map(|&x| Some(x)),
+ )
}
/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
@@ -223,9 +233,17 @@ pub fn implements_trait_with_env<'tcx>(
param_env: ParamEnv<'tcx>,
ty: Ty<'tcx>,
trait_id: DefId,
+ callee_id: DefId,
args: &[GenericArg<'tcx>],
) -> bool {
- implements_trait_with_env_from_iter(tcx, param_env, ty, trait_id, args.iter().map(|&x| Some(x)))
+ implements_trait_with_env_from_iter(
+ tcx,
+ param_env,
+ ty,
+ trait_id,
+ Some(callee_id),
+ args.iter().map(|&x| Some(x)),
+ )
}
/// Same as `implements_trait_from_env` but takes the arguments as an iterator.
@@ -234,6 +252,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>(
param_env: ParamEnv<'tcx>,
ty: Ty<'tcx>,
trait_id: DefId,
+ callee_id: Option<DefId>,
args: impl IntoIterator<Item = impl Into<Option<GenericArg<'tcx>>>>,
) -> bool {
// Clippy shouldn't have infer types
@@ -245,20 +264,36 @@ pub fn implements_trait_with_env_from_iter<'tcx>(
}
let infcx = tcx.infer_ctxt().build();
+ let args = args
+ .into_iter()
+ .map(|arg| {
+ arg.into().unwrap_or_else(|| {
+ let orig = TypeVariableOrigin {
+ kind: TypeVariableOriginKind::MiscVariable,
+ span: DUMMY_SP,
+ };
+ infcx.next_ty_var(orig).into()
+ })
+ })
+ .collect::<Vec<_>>();
+
+ // If an effect arg was not specified, we need to specify it.
+ let effect_arg = if tcx
+ .generics_of(trait_id)
+ .host_effect_index
+ .is_some_and(|x| args.get(x - 1).is_none())
+ {
+ Some(GenericArg::from(callee_id.map_or(tcx.consts.true_, |def_id| {
+ tcx.expected_host_effect_param_for_body(def_id)
+ })))
+ } else {
+ None
+ };
+
let trait_ref = TraitRef::new(
tcx,
trait_id,
- Some(GenericArg::from(ty))
- .into_iter()
- .chain(args.into_iter().map(|arg| {
- arg.into().unwrap_or_else(|| {
- let orig = TypeVariableOrigin {
- kind: TypeVariableOriginKind::MiscVariable,
- span: DUMMY_SP,
- };
- infcx.next_ty_var(orig).into()
- })
- })),
+ Some(GenericArg::from(ty)).into_iter().chain(args).chain(effect_arg),
);
debug_assert_matches!(
@@ -694,7 +729,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'t
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)));
+ .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.local_def_id_to_hir_id(id)));
Some(ExprFnSig::Closure(decl, subs.as_closure().sig()))
},
ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate(cx.tcx, subs), Some(id))),
@@ -890,7 +925,7 @@ pub fn for_each_top_level_late_bound_region<B>(
impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<TyCtxt<'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()
+ if let RegionKind::ReBound(idx, bound) = r.kind()
&& idx.as_u32() == self.index
{
(self.f)(bound)
@@ -1169,7 +1204,7 @@ pub fn make_normalized_projection<'tcx>(
debug_assert!(
false,
"args contain late-bound region at index `{i}` which can't be normalized.\n\
- use `TyCtxt::erase_late_bound_regions`\n\
+ use `TyCtxt::instantiate_bound_regions_with_erased`\n\
note: arg is `{arg:#?}`",
);
return None;
@@ -1247,7 +1282,7 @@ pub fn make_normalized_projection_with_regions<'tcx>(
debug_assert!(
false,
"args contain late-bound region at index `{i}` which can't be normalized.\n\
- use `TyCtxt::erase_late_bound_regions`\n\
+ use `TyCtxt::instantiate_bound_regions_with_erased`\n\
note: arg is `{arg:#?}`",
);
return None;
@@ -1276,3 +1311,8 @@ pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>
Err(_) => ty,
}
}
+
+/// Checks if the type is `core::mem::ManuallyDrop<_>`
+pub fn is_manually_drop(ty: Ty<'_>) -> bool {
+ ty.ty_adt_def().map_or(false, AdtDef::is_manually_drop)
+}
diff --git a/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs
index 76fa15e15..da71fc3aa 100644
--- a/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs
+++ b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs
@@ -170,19 +170,18 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo
path_segment_certainty(cx, type_certainty(cx, ty), path_segment, resolves_to_type)
},
- QPath::LangItem(lang_item, _, _) => {
- cx.tcx
- .lang_items()
- .get(*lang_item)
- .map_or(Certainty::Uncertain, |def_id| {
- let generics = cx.tcx.generics_of(def_id);
- if generics.parent_count == 0 && generics.params.is_empty() {
- Certainty::Certain(if resolves_to_type { Some(def_id) } else { None })
- } else {
- Certainty::Uncertain
- }
- })
- },
+ QPath::LangItem(lang_item, ..) => cx
+ .tcx
+ .lang_items()
+ .get(*lang_item)
+ .map_or(Certainty::Uncertain, |def_id| {
+ let generics = cx.tcx.generics_of(def_id);
+ if generics.parent_count == 0 && generics.params.is_empty() {
+ Certainty::Certain(if resolves_to_type { Some(def_id) } else { None })
+ } else {
+ Certainty::Uncertain
+ }
+ }),
};
debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
certainty