summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_utils/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
commitd1b2d29528b7794b41e66fc2136e395a02f8529b (patch)
treea4a17504b260206dec3cf55b2dca82929a348ac2 /src/tools/clippy/clippy_utils/src
parentReleasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz
rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_utils/src')
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs12
-rw-r--r--src/tools/clippy/clippy_utils/src/attrs.rs7
-rw-r--r--src/tools/clippy/clippy_utils/src/check_proc_macro.rs24
-rw-r--r--src/tools/clippy/clippy_utils/src/comparisons.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/consts.rs32
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs39
-rw-r--r--src/tools/clippy/clippy_utils/src/higher.rs9
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs32
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs420
-rw-r--r--src/tools/clippy/clippy_utils/src/macros.rs20
-rw-r--r--src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs12
-rw-r--r--src/tools/clippy/clippy_utils/src/mir/possible_origin.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/paths.rs16
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs66
-rw-r--r--src/tools/clippy/clippy_utils/src/source.rs3
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs8
-rw-r--r--src/tools/clippy/clippy_utils/src/ty.rs282
-rw-r--r--src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs122
-rw-r--r--src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs320
-rw-r--r--src/tools/clippy/clippy_utils/src/usage.rs55
-rw-r--r--src/tools/clippy/clippy_utils/src/visitors.rs16
21 files changed, 1169 insertions, 332 deletions
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 8cc01f1ef..140cfa219 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -178,7 +178,9 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
(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),
+ (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),
@@ -301,15 +303,17 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
(
Const(box ast::ConstItem {
defaultness: ld,
+ generics: lg,
ty: lt,
expr: le,
}),
Const(box ast::ConstItem {
defaultness: rd,
+ generics: rg,
ty: rt,
expr: re,
}),
- ) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ ) => eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Fn(box ast::Fn {
defaultness: ld,
@@ -476,15 +480,17 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
(
Const(box ast::ConstItem {
defaultness: ld,
+ generics: lg,
ty: lt,
expr: le,
}),
Const(box ast::ConstItem {
defaultness: rd,
+ generics: rg,
ty: rt,
expr: re,
}),
- ) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
+ ) => eq_defaultness(*ld, *rd) && eq_generics(lg, rg) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(
Fn(box ast::Fn {
defaultness: ld,
diff --git a/src/tools/clippy/clippy_utils/src/attrs.rs b/src/tools/clippy/clippy_utils/src/attrs.rs
index 49cb9718e..51771f78d 100644
--- a/src/tools/clippy/clippy_utils/src/attrs.rs
+++ b/src/tools/clippy/clippy_utils/src/attrs.rs
@@ -1,5 +1,4 @@
-use rustc_ast::ast;
-use rustc_ast::attr;
+use rustc_ast::{ast, attr};
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::sym;
@@ -143,13 +142,13 @@ pub fn get_unique_attr<'a>(
unique_attr
}
-/// Return true if the attributes contain any of `proc_macro`,
+/// Returns true if the attributes contain any of `proc_macro`,
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
pub fn is_proc_macro(attrs: &[ast::Attribute]) -> bool {
attrs.iter().any(rustc_ast::Attribute::is_proc_macro_attr)
}
-/// Return true if the attributes contain `#[doc(hidden)]`
+/// Returns true if the attributes contain `#[doc(hidden)]`
pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
attrs
.iter()
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 c6d0b654f..6be8b8bb9 100644
--- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
+++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
@@ -12,20 +12,20 @@
//! code was written, and check if the span contains that text. Note this will only work correctly
//! if the span is not from a `macro_rules` based macro.
-use rustc_ast::{
- ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, UintTy},
- token::CommentKind,
- AttrStyle,
-};
+use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, UintTy};
+use rustc_ast::token::CommentKind;
+use rustc_ast::AttrStyle;
+use rustc_hir::intravisit::FnKind;
use rustc_hir::{
- intravisit::FnKind, Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, HirId,
- Impl, ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, MutTy, Node, QPath, TraitItem,
- TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource,
+ Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, HirId, Impl, ImplItem,
+ ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, MutTy, Node, QPath, TraitItem, TraitItemKind, Ty,
+ TyKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource,
};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::TyCtxt;
use rustc_session::Session;
-use rustc_span::{symbol::Ident, Span, Symbol};
+use rustc_span::symbol::Ident;
+use rustc_span::{Span, Symbol};
use rustc_target::spec::abi::Abi;
/// The search pattern to look for. Used by `span_matches_pat`
@@ -149,7 +149,7 @@ fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
(Pat::Str("for"), Pat::Str("}"))
},
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
- ExprKind::Match(e, _, MatchSource::TryDesugar) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
+ ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
(expr_search_pat(tcx, e).0, Pat::Str("await"))
},
@@ -163,7 +163,7 @@ fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
) => (Pat::Str("unsafe"), Pat::Str("}")),
ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)),
- ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
+ ExprKind::Index(e, _, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
ExprKind::Path(ref path) => qpath_search_pat(path),
ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1),
ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
@@ -339,7 +339,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
TyKind::Tup(..) => (Pat::Str("("), Pat::Str(")")),
TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
TyKind::Path(qpath) => qpath_search_pat(&qpath),
- // NOTE: This is missing `TraitObject`. It always return true then.
+ // NOTE: This is missing `TraitObject`. It will always return true then.
_ => (Pat::Str(""), Pat::Str("")),
}
}
diff --git a/src/tools/clippy/clippy_utils/src/comparisons.rs b/src/tools/clippy/clippy_utils/src/comparisons.rs
index 7a18d5e81..5e6bf2278 100644
--- a/src/tools/clippy/clippy_utils/src/comparisons.rs
+++ b/src/tools/clippy/clippy_utils/src/comparisons.rs
@@ -1,11 +1,11 @@
-//! Utility functions about comparison operators.
+//! Utility functions for 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.
+/// Represents a normalized comparison operator.
pub enum Rel {
/// `<`
Lt,
diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs
index d1cfdc496..6d57af325 100644
--- a/src/tools/clippy/clippy_utils/src/consts.rs
+++ b/src/tools/clippy/clippy_utils/src/consts.rs
@@ -9,11 +9,9 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp};
use rustc_lexer::tokenize;
use rustc_lint::LateContext;
-use rustc_middle::mir;
use rustc_middle::mir::interpret::Scalar;
-use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt};
-use rustc_middle::ty::{List, SubstsRef};
-use rustc_middle::{bug, span_bug};
+use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, List, ScalarInt, Ty, TyCtxt};
+use rustc_middle::{bug, mir, span_bug};
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::SyntaxContext;
use std::cmp::Ordering::{self, Equal};
@@ -155,7 +153,7 @@ impl<'tcx> Constant<'tcx> {
},
(Self::Vec(l), Self::Vec(r)) => {
let (ty::Array(cmp_type, _) | ty::Slice(cmp_type)) = *cmp_type.kind() else {
- return None
+ return None;
};
iter::zip(l, r)
.map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri))
@@ -267,7 +265,7 @@ pub fn constant_with_source<'tcx>(
res.map(|x| (x, ctxt.source))
}
-/// Attempts to evaluate an expression only if it's value is not dependent on other items.
+/// Attempts to evaluate an expression only if its value is not dependent on other items.
pub fn constant_simple<'tcx>(
lcx: &LateContext<'tcx>,
typeck_results: &ty::TypeckResults<'tcx>,
@@ -327,17 +325,17 @@ pub struct ConstEvalLateContext<'a, 'tcx> {
typeck_results: &'a ty::TypeckResults<'tcx>,
param_env: ty::ParamEnv<'tcx>,
source: ConstantSource,
- substs: SubstsRef<'tcx>,
+ args: GenericArgsRef<'tcx>,
}
impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
- fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>) -> Self {
+ pub fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>) -> Self {
Self {
lcx,
typeck_results,
param_env: lcx.param_env,
source: ConstantSource::Local,
- substs: List::empty(),
+ args: List::empty(),
}
}
@@ -396,7 +394,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
}
}
},
- ExprKind::Index(arr, index) => self.index(arr, index),
+ ExprKind::Index(arr, index, _) => self.index(arr, index),
ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
ExprKind::Field(local_expr, ref field) => {
let result = self.expr(local_expr);
@@ -463,7 +461,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
// 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::Item(Item { kind: ItemKind::Const(.., body_id), .. }) = node
&& let Node::Expr(Expr { kind: ExprKind::Lit(_), span, .. }) = self.lcx
.tcx
.hir()
@@ -473,16 +471,16 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
return None;
}
- let substs = self.typeck_results.node_substs(id);
- let substs = if self.substs.is_empty() {
- substs
+ let args = self.typeck_results.node_args(id);
+ let args = if self.args.is_empty() {
+ args
} else {
- EarlyBinder::bind(substs).subst(self.lcx.tcx, self.substs)
+ EarlyBinder::bind(args).instantiate(self.lcx.tcx, self.args)
};
let result = self
.lcx
.tcx
- .const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, substs), None)
+ .const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, args), None)
.ok()
.map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty))?;
let result = miri_to_const(self.lcx, result)?;
@@ -726,7 +724,7 @@ fn field_of_struct<'tcx>(
field: &Ident,
) -> Option<mir::ConstantKind<'tcx>> {
if let mir::ConstantKind::Val(result, ty) = result
- && let Some(dc) = lcx.tcx.try_destructure_mir_constant_for_diagnostics((result, ty))
+ && let Some(dc) = rustc_const_eval::const_eval::try_destructure_mir_constant_for_diagnostics(lcx.tcx, result, ty)
&& let Some(dc_variant) = dc.variant
&& let Some(variant) = adt_def.variants().get(dc_variant)
&& let Some(field_idx) = variant.fields.iter().position(|el| el.name == field.name)
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 4a845ca63..0bcefba75 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -1,7 +1,7 @@
//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
//!
//! Things to consider:
-//! - has the expression side-effects?
+//! - does the expression have side-effects?
//! - is the expression computationally expensive?
//!
//! See lints:
@@ -12,14 +12,14 @@
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::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp};
+use rustc_hir::{Block, Expr, ExprKind, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::{sym, Symbol};
-use std::cmp;
-use std::ops;
+use std::{cmp, ops};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum EagernessSuggestion {
@@ -51,7 +51,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg:
let name = name.as_str();
let ty = match cx.tcx.impl_of_method(fn_id) {
- Some(id) => cx.tcx.type_of(id).subst_identity(),
+ Some(id) => cx.tcx.type_of(id).instantiate_identity(),
None => return Lazy,
};
@@ -68,19 +68,24 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg:
// 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).subst_identity().peel_refs().kind(), ty::Param(_)))
- && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
- ty::ClauseKind::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(_)))
+ if def.variants().iter().flat_map(|v| v.fields.iter()).any(|x| {
+ matches!(
+ cx.tcx.type_of(x.did).instantiate_identity().peel_refs().kind(),
+ ty::Param(_)
+ )
+ }) && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
+ ty::ClauseKind::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).subst_identity().skip_binder().inputs_and_output {
+ match &**cx
+ .tcx
+ .fn_sig(fn_id)
+ .instantiate_identity()
+ .skip_binder()
+ .inputs_and_output
+ {
[arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
_ => Lazy,
}
@@ -180,7 +185,7 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
.type_dependent_def_id(e.hir_id)
.map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, true));
},
- ExprKind::Index(_, e) => {
+ 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;
diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs
index a61e4c380..802adbd4d 100644
--- a/src/tools/clippy/clippy_utils/src/higher.rs
+++ b/src/tools/clippy/clippy_utils/src/higher.rs
@@ -13,7 +13,7 @@ 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)`.
+/// `for pat in arg { body }` becomes `(pat, arg, body)`. Returns `(pat, arg, body, span)`.
pub struct ForLoop<'tcx> {
/// `for` loop item
pub pat: &'tcx hir::Pat<'tcx>,
@@ -138,6 +138,7 @@ impl<'hir> IfLet<'hir> {
}
/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
+#[derive(Debug)]
pub enum IfLetOrMatch<'hir> {
/// Any `match` expression
Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
@@ -264,7 +265,7 @@ impl<'a> Range<'a> {
}
}
-/// Represent the pre-expansion arguments of a `vec!` invocation.
+/// Represents the pre-expansion arguments of a `vec!` invocation.
pub enum VecArgs<'a> {
/// `vec![elem; len]`
Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
@@ -398,7 +399,7 @@ impl<'hir> WhileLet<'hir> {
}
}
-/// Converts a hir binary operator to the corresponding `ast` type.
+/// Converts a `hir` binary operator to the corresponding `ast` type.
#[must_use]
pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
match op {
@@ -436,7 +437,7 @@ pub enum VecInitKind {
WithExprCapacity(HirId),
}
-/// Checks if given expression is an initialization of `Vec` and returns its kind.
+/// Checks if the 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 {
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index 3e1d73564..fdc35cd4d 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -5,10 +5,9 @@ use crate::tokenize_with_text;
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, BindingAnnotation, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg,
- GenericArgs, Guard, HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, Pat, PatField, PatKind, Path,
+ GenericArgs, Guard, HirId, HirIdMap, InlineAsmOperand, Let, Lifetime, LifetimeName, Pat, PatField, PatKind, Path,
PathSegment, PrimTy, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
};
use rustc_lexer::{tokenize, TokenKind};
@@ -253,15 +252,15 @@ impl HirEqInterExpr<'_, '_, '_> {
return false;
}
- if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results {
- if let (Some(l), Some(r)) = (
+ if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results
+ && typeck_lhs.expr_ty(left) == typeck_rhs.expr_ty(right)
+ && 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;
- }
- }
+ )
+ && l == r
+ {
+ return true;
}
let is_eq = match (
@@ -300,7 +299,7 @@ impl HirEqInterExpr<'_, '_, '_> {
(&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::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))
},
@@ -495,10 +494,13 @@ impl HirEqInterExpr<'_, '_, '_> {
loop {
use TokenKind::{BlockComment, LineComment, Whitespace};
if left_data.macro_def_id != right_data.macro_def_id
- || (matches!(left_data.kind, ExpnKind::Macro(MacroKind::Bang, name) if name == sym::cfg)
- && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| {
- !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. })
- }))
+ || (matches!(
+ left_data.kind,
+ ExpnKind::Macro(MacroKind::Bang, name)
+ if name == sym::cfg || name == sym::option_env
+ ) && !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| {
+ !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. })
+ }))
{
// Either a different chain of macro calls, or different arguments to the `cfg` macro.
return false;
@@ -728,7 +730,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(e);
self.hash_name(f.name);
},
- ExprKind::Index(a, i) => {
+ ExprKind::Index(a, i, _) => {
self.hash_expr(a);
self.hash_expr(i);
},
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 727b59f1f..6c4cec595 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -74,8 +74,7 @@ pub use self::hir_utils::{
use core::ops::ControlFlow;
use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault;
-use std::sync::OnceLock;
-use std::sync::{Mutex, MutexGuard};
+use std::sync::{Mutex, MutexGuard, OnceLock};
use if_chain::if_chain;
use itertools::Itertools;
@@ -84,41 +83,40 @@ use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unhash::UnhashMap;
use rustc_hir::def::{DefKind, Res};
-use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
+use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE};
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::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{
self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Destination, Expr,
- ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ItemKind, LangItem, Local,
- MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
- TraitItem, TraitItemRef, TraitRef, TyKind, UnOp,
+ ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item,
+ ItemKind, LangItem, Local, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, PrimTy,
+ QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp,
};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::mir::ConstantKind;
-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::SimplifiedType::{
- ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType,
- PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType,
-};
+use rustc_middle::ty::fast_reject::SimplifiedType;
+use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
- layout::IntegerExt, BorrowKind, ClosureKind, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UpvarCapture,
+ self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut,
+ TypeVisitableExt, UintTy, UpvarCapture,
};
-use rustc_middle::ty::{FloatTy, IntTy, UintTy};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
-use rustc_span::sym;
use rustc_span::symbol::{kw, Ident, Symbol};
-use rustc_span::Span;
+use rustc_span::{sym, Span};
use rustc_target::abi::Integer;
use crate::consts::{constant, miri_to_const, Constant};
use crate::higher::Range;
-use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
+use crate::ty::{
+ adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
+ ty_is_fn_once_param,
+};
use crate::visitors::for_each_expr;
use rustc_middle::hir::nested_filter;
@@ -288,7 +286,7 @@ pub fn is_wild(pat: &Pat<'_>) -> bool {
/// Checks if the given `QPath` belongs to a type alias.
pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
match *qpath {
- QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)),
+ QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias { .. } | DefKind::AssocTy, ..)),
QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => { is_ty_alias(&qpath) },
_ => false,
}
@@ -305,7 +303,7 @@ pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str])
/// 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).subst_identity().ty_adt_def() {
+ if let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() {
return cx.tcx.is_diagnostic_item(diag_item, adt.did());
}
}
@@ -514,30 +512,30 @@ pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>
fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
let ty = match name {
- "bool" => BoolSimplifiedType,
- "char" => CharSimplifiedType,
- "str" => StrSimplifiedType,
- "array" => ArraySimplifiedType,
- "slice" => SliceSimplifiedType,
+ "bool" => SimplifiedType::Bool,
+ "char" => SimplifiedType::Char,
+ "str" => SimplifiedType::Str,
+ "array" => SimplifiedType::Array,
+ "slice" => SimplifiedType::Slice,
// FIXME: rustdoc documents these two using just `pointer`.
//
// Maybe this is something we should do here too.
- "const_ptr" => PtrSimplifiedType(Mutability::Not),
- "mut_ptr" => PtrSimplifiedType(Mutability::Mut),
- "isize" => IntSimplifiedType(IntTy::Isize),
- "i8" => IntSimplifiedType(IntTy::I8),
- "i16" => IntSimplifiedType(IntTy::I16),
- "i32" => IntSimplifiedType(IntTy::I32),
- "i64" => IntSimplifiedType(IntTy::I64),
- "i128" => IntSimplifiedType(IntTy::I128),
- "usize" => UintSimplifiedType(UintTy::Usize),
- "u8" => UintSimplifiedType(UintTy::U8),
- "u16" => UintSimplifiedType(UintTy::U16),
- "u32" => UintSimplifiedType(UintTy::U32),
- "u64" => UintSimplifiedType(UintTy::U64),
- "u128" => UintSimplifiedType(UintTy::U128),
- "f32" => FloatSimplifiedType(FloatTy::F32),
- "f64" => FloatSimplifiedType(FloatTy::F64),
+ "const_ptr" => SimplifiedType::Ptr(Mutability::Not),
+ "mut_ptr" => SimplifiedType::Ptr(Mutability::Mut),
+ "isize" => SimplifiedType::Int(IntTy::Isize),
+ "i8" => SimplifiedType::Int(IntTy::I8),
+ "i16" => SimplifiedType::Int(IntTy::I16),
+ "i32" => SimplifiedType::Int(IntTy::I32),
+ "i64" => SimplifiedType::Int(IntTy::I64),
+ "i128" => SimplifiedType::Int(IntTy::I128),
+ "usize" => SimplifiedType::Uint(UintTy::Usize),
+ "u8" => SimplifiedType::Uint(UintTy::U8),
+ "u16" => SimplifiedType::Uint(UintTy::U16),
+ "u32" => SimplifiedType::Uint(UintTy::U32),
+ "u64" => SimplifiedType::Uint(UintTy::U64),
+ "u128" => SimplifiedType::Uint(UintTy::U128),
+ "f32" => SimplifiedType::Float(FloatTy::F32),
+ "f64" => SimplifiedType::Float(FloatTy::F64),
_ => return [].iter().copied(),
};
@@ -737,7 +735,7 @@ fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'
let mut result = vec![];
let root = loop {
match e.kind {
- ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => {
+ ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => {
result.push(e);
e = ep;
},
@@ -784,7 +782,7 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
return true;
}
},
- (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => {
+ (ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => {
if !eq_expr_value(cx, i1, i2) {
return false;
}
@@ -812,7 +810,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
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).subst_identity().ty_adt_def() {
+ if let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() {
return std_types_symbols.iter().any(|&symbol| {
cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string()
});
@@ -823,7 +821,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
false
}
-/// Return true if the expr is equal to `Default::default` when evaluated.
+/// 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;
@@ -1377,7 +1375,7 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
.chain(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).subst_identity().skip_binder().inputs()[i];
+ let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_binder().inputs()[i];
ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
},
_ => None,
@@ -1639,13 +1637,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).subst_identity().output();
+ let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().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_def_id: hir::OwnerId, nth: usize) -> Ty<'tcx> {
- let arg = cx.tcx.fn_sig(fn_def_id).subst_identity().input(nth);
+ let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().input(nth);
cx.tcx.erase_late_bound_regions(arg)
}
@@ -1767,7 +1765,7 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
if let ExprKind::Match(_, arms, ref source) = expr.kind {
// desugared from a `?` operator
- if *source == MatchSource::TryDesugar {
+ if let MatchSource::TryDesugar(_) = *source {
return Some(expr);
}
@@ -2372,11 +2370,11 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
false
}
-static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalDefId, Vec<Symbol>>>> = OnceLock::new();
+static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalModDefId, Vec<Symbol>>>> = OnceLock::new();
-fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalDefId, f: impl Fn(&[Symbol]) -> bool) -> bool {
+fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalModDefId, 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 mut map: MutexGuard<'_, FxHashMap<LocalModDefId, Vec<Symbol>>> = cache.lock().unwrap();
let value = map.entry(module);
match value {
Entry::Occupied(entry) => f(entry.get()),
@@ -2385,7 +2383,7 @@ fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalDefId, f: impl Fn(&[Symbol
for id in tcx.hir().module_items(module) {
if matches!(tcx.def_kind(id.owner_id), DefKind::Const)
&& let item = tcx.hir().item(id)
- && let ItemKind::Const(ty, _body) = item.kind {
+ && let ItemKind::Const(ty, _generics, _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 {
@@ -2450,6 +2448,17 @@ pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
.any(is_cfg_test)
}
+/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied.
+pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
+ let hir = tcx.hir();
+
+ tcx.has_attr(def_id, sym::cfg)
+ || hir
+ .parent_iter(hir.local_def_id_to_hir_id(def_id))
+ .flat_map(|(parent_id, _)| hir.attrs(parent_id))
+ .any(|attr| attr.has_name(sym::cfg))
+}
+
/// Checks whether item either has `test` attribute applied, or
/// is a module with `test` in its name.
///
@@ -2504,6 +2513,262 @@ pub fn walk_to_expr_usage<'tcx, T>(
None
}
+/// A type definition as it would be viewed from within a function.
+#[derive(Clone, Copy)]
+pub enum DefinedTy<'tcx> {
+ // Used for locals and closures defined within the function.
+ Hir(&'tcx hir::Ty<'tcx>),
+ /// Used for function signatures, and constant and static values. This includes the `ParamEnv`
+ /// from the definition site.
+ Mir(ParamEnvAnd<'tcx, Binder<'tcx, Ty<'tcx>>>),
+}
+
+/// The context an expressions value is used in.
+pub struct ExprUseCtxt<'tcx> {
+ /// The parent node which consumes the value.
+ pub node: ExprUseNode<'tcx>,
+ /// Any adjustments applied to the type.
+ pub adjustments: &'tcx [Adjustment<'tcx>],
+ /// Whether or not the type must unify with another code path.
+ pub is_ty_unified: bool,
+ /// Whether or not the value will be moved before it's used.
+ pub moved_before_use: bool,
+}
+
+/// The node which consumes a value.
+pub enum ExprUseNode<'tcx> {
+ /// Assignment to, or initializer for, a local
+ Local(&'tcx Local<'tcx>),
+ /// Initializer for a const or static item.
+ ConstStatic(OwnerId),
+ /// Implicit or explicit return from a function.
+ Return(OwnerId),
+ /// Initialization of a struct field.
+ Field(&'tcx ExprField<'tcx>),
+ /// An argument to a function.
+ FnArg(&'tcx Expr<'tcx>, usize),
+ /// An argument to a method.
+ MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize),
+ /// The callee of a function call.
+ Callee,
+ /// Access of a field.
+ FieldAccess(Ident),
+}
+impl<'tcx> ExprUseNode<'tcx> {
+ /// Checks if the value is returned from the function.
+ pub fn is_return(&self) -> bool {
+ matches!(self, Self::Return(_))
+ }
+
+ /// Checks if the value is used as a method call receiver.
+ pub fn is_recv(&self) -> bool {
+ matches!(self, Self::MethodArg(_, _, 0))
+ }
+
+ /// Gets the needed type as it's defined without any type inference.
+ pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
+ match *self {
+ Self::Local(Local { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)),
+ Self::ConstStatic(id) => Some(DefinedTy::Mir(
+ cx.param_env
+ .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);
+ if let Some(Node::Expr(Expr {
+ kind: ExprKind::Closure(c),
+ ..
+ })) = cx.tcx.hir().find(hir_id)
+ {
+ match c.fn_decl.output {
+ FnRetTy::DefaultReturn(_) => None,
+ FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)),
+ }
+ } else {
+ Some(DefinedTy::Mir(
+ cx.param_env.and(cx.tcx.fn_sig(id).instantiate_identity().output()),
+ ))
+ }
+ },
+ Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) {
+ Some(Expr {
+ hir_id,
+ kind: ExprKind::Struct(path, ..),
+ ..
+ }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id))
+ .and_then(|(adt, variant)| {
+ variant
+ .fields
+ .iter()
+ .find(|f| f.name == field.ident.name)
+ .map(|f| (adt, f))
+ })
+ .map(|(adt, field_def)| {
+ DefinedTy::Mir(
+ cx.tcx
+ .param_env(adt.did())
+ .and(Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity())),
+ )
+ }),
+ _ => None,
+ },
+ Self::FnArg(callee, i) => {
+ let sig = expr_sig(cx, callee)?;
+ let (hir_ty, ty) = sig.input_with_hir(i)?;
+ Some(match hir_ty {
+ Some(hir_ty) => DefinedTy::Hir(hir_ty),
+ None => DefinedTy::Mir(
+ sig.predicates_id()
+ .map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id))
+ .and(ty),
+ ),
+ })
+ },
+ Self::MethodArg(id, _, i) => {
+ let id = cx.typeck_results().type_dependent_def_id(id)?;
+ let sig = cx.tcx.fn_sig(id).skip_binder();
+ Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
+ },
+ Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None,
+ }
+ }
+}
+
+/// Gets the context an expression's value is used in.
+#[expect(clippy::too_many_lines)]
+pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> {
+ let mut adjustments = [].as_slice();
+ let mut is_ty_unified = false;
+ let mut moved_before_use = false;
+ let ctxt = e.span.ctxt();
+ 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) {
+ adjustments = cx.typeck_results().expr_adjustments(e);
+ }
+ match parent {
+ Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt {
+ node: ExprUseNode::Local(l),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+ Node::Item(&Item {
+ kind: ItemKind::Static(..) | ItemKind::Const(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Const(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Const(..),
+ owner_id,
+ span,
+ ..
+ }) if span.ctxt() == ctxt => Some(ExprUseCtxt {
+ node: ExprUseNode::ConstStatic(owner_id),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+
+ Node::Item(&Item {
+ kind: ItemKind::Fn(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::TraitItem(&TraitItem {
+ kind: TraitItemKind::Fn(..),
+ owner_id,
+ span,
+ ..
+ })
+ | Node::ImplItem(&ImplItem {
+ kind: ImplItemKind::Fn(..),
+ owner_id,
+ span,
+ ..
+ }) if span.ctxt() == ctxt => Some(ExprUseCtxt {
+ node: ExprUseNode::Return(owner_id),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+
+ Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt {
+ node: ExprUseNode::Field(field),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+
+ Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
+ ExprKind::Ret(_) => Some(ExprUseCtxt {
+ node: ExprUseNode::Return(OwnerId {
+ def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
+ }),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+ ExprKind::Closure(closure) => Some(ExprUseCtxt {
+ node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+ ExprKind::Call(func, args) => Some(ExprUseCtxt {
+ node: match args.iter().position(|arg| arg.hir_id == child_id) {
+ Some(i) => ExprUseNode::FnArg(func, i),
+ None => ExprUseNode::Callee,
+ },
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+ ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt {
+ node: ExprUseNode::MethodArg(
+ parent.hir_id,
+ name.args,
+ args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
+ ),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+ ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt {
+ node: ExprUseNode::FieldAccess(name),
+ adjustments,
+ is_ty_unified,
+ moved_before_use,
+ }),
+ ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
+ is_ty_unified = true;
+ moved_before_use = true;
+ None
+ },
+ ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
+ is_ty_unified = true;
+ moved_before_use = true;
+ None
+ },
+ ExprKind::Block(..) => {
+ moved_before_use = true;
+ None
+ },
+ _ => None,
+ },
+ _ => None,
+ }
+ })
+}
+
/// Tokenizes the input while keeping the text associated with each token.
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
let mut pos = 0;
@@ -2518,7 +2783,9 @@ pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
/// Checks whether a given span has any comment token
/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
- let Ok(snippet) = sm.span_to_snippet(span) else { return false };
+ let Ok(snippet) = sm.span_to_snippet(span) else {
+ return false;
+ };
return tokenize(&snippet).any(|token| {
matches!(
token.kind,
@@ -2527,7 +2794,8 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
});
}
-/// Return all the comments a given span contains
+/// Returns all the comments a given span contains
+///
/// Comments are returned wrapped with their relevant delimiters
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
let snippet = sm.span_to_snippet(span).unwrap_or_default();
@@ -2542,6 +2810,50 @@ pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
sm.span_take_while(span, |&ch| ch == ' ' || ch == ';')
}
+/// Returns whether the given let pattern and else body can be turned into a question mark
+///
+/// For this example:
+/// ```ignore
+/// let FooBar { a, b } = if let Some(a) = ex { a } else { return None };
+/// ```
+/// We get as parameters:
+/// ```ignore
+/// pat: Some(a)
+/// else_body: return None
+/// ```
+
+/// And for this example:
+/// ```ignore
+/// let Some(FooBar { a, b }) = ex else { return None };
+/// ```
+/// We get as parameters:
+/// ```ignore
+/// pat: Some(FooBar { a, b })
+/// else_body: return None
+/// ```
+
+/// We output `Some(a)` in the first instance, and `Some(FooBar { a, b })` in the second, because
+/// the question mark operator is applicable here. Callers have to check whether we are in a
+/// constant or not.
+pub fn pat_and_expr_can_be_question_mark<'a, 'hir>(
+ cx: &LateContext<'_>,
+ pat: &'a Pat<'hir>,
+ else_body: &Expr<'_>,
+) -> Option<&'a Pat<'hir>> {
+ if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind &&
+ is_res_lang_ctor(cx, cx.qpath_res(&pat_path, pat.hir_id), OptionSome) &&
+ !is_refutable(cx, inner_pat) &&
+ let else_body = peel_blocks(else_body) &&
+ let ExprKind::Ret(Some(ret_val)) = else_body.kind &&
+ let ExprKind::Path(ret_path) = ret_val.kind &&
+ is_res_lang_ctor(cx, cx.qpath_res(&ret_path, ret_val.hir_id), OptionNone)
+ {
+ Some(inner_pat)
+ } else {
+ None
+ }
+}
+
macro_rules! op_utils {
($($name:ident $assign:ident)*) => {
/// Binary operation traits like `LangItem::Add`
diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs
index 00f3abaec..173f9841d 100644
--- a/src/tools/clippy/clippy_utils/src/macros.rs
+++ b/src/tools/clippy/clippy_utils/src/macros.rs
@@ -192,7 +192,9 @@ pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<
/// 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 };
+ let Some(name) = cx.tcx.get_diagnostic_name(def_id) else {
+ return false;
+ };
matches!(
name,
sym::core_panic_macro
@@ -205,7 +207,9 @@ pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
/// Is `def_id` of `assert!` or `debug_assert!`
pub fn is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool {
- let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
+ let Some(name) = cx.tcx.get_diagnostic_name(def_id) else {
+ return false;
+ };
matches!(name, sym::assert_macro | sym::debug_assert_macro)
}
@@ -223,13 +227,19 @@ pub enum PanicExpn<'a> {
impl<'a> PanicExpn<'a> {
pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
- let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None };
- let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
+ let ExprKind::Call(callee, [arg, rest @ ..]) = &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 };
+ let ExprKind::AddrOf(_, _, e) = &arg.kind else {
+ return None;
+ };
Self::Display(e)
},
"panic_fmt" => Self::Format(arg),
diff --git a/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs b/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs
index 920ce8e65..703985b9d 100644
--- a/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs
+++ b/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs
@@ -1,11 +1,15 @@
-use super::{possible_origin::PossibleOriginVisitor, transitive_relation::TransitiveRelation};
+use super::possible_origin::PossibleOriginVisitor;
+use super::transitive_relation::TransitiveRelation;
use crate::ty::is_copy;
use rustc_data_structures::fx::FxHashMap;
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_lint::LateContext;
-use rustc_middle::mir::{self, visit::Visitor as _, Mutability};
-use rustc_middle::ty::{self, visit::TypeVisitor, TyCtxt};
-use rustc_mir_dataflow::{impls::MaybeStorageLive, Analysis, ResultsCursor};
+use rustc_middle::mir::visit::Visitor as _;
+use rustc_middle::mir::{self, Mutability};
+use rustc_middle::ty::visit::TypeVisitor;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_mir_dataflow::impls::MaybeStorageLive;
+use rustc_mir_dataflow::{Analysis, ResultsCursor};
use std::borrow::Cow;
use std::ops::ControlFlow;
diff --git a/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs b/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs
index 8e7513d74..da0426686 100644
--- a/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs
+++ b/src/tools/clippy/clippy_utils/src/mir/possible_origin.rs
@@ -44,7 +44,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
let lhs = place.local;
match rvalue {
// Only consider `&mut`, which can modify origin place
- mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
+ mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, borrowed) |
// _2: &mut _;
// _3 = move _2
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs
index 0e6f01287..914ea85ac 100644
--- a/src/tools/clippy/clippy_utils/src/paths.rs
+++ b/src/tools/clippy/clippy_utils/src/paths.rs
@@ -94,12 +94,12 @@ pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
-pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
-pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
-pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
-pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
-pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
-pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+pub const REGEX_BUILDER_NEW: [&str; 3] = ["regex", "RegexBuilder", "new"];
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 4] = ["regex", "bytes", "RegexBuilder", "new"];
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "bytes", "Regex", "new"];
+pub const REGEX_BYTES_SET_NEW: [&str; 4] = ["regex", "bytes", "RegexSet", "new"];
+pub const REGEX_NEW: [&str; 3] = ["regex", "Regex", "new"];
+pub const REGEX_SET_NEW: [&str; 3] = ["regex", "RegexSet", "new"];
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"];
@@ -149,6 +149,7 @@ 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_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"];
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"];
@@ -161,3 +162,6 @@ pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"];
pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"];
pub const FORMATTER: [&str; 3] = ["core", "fmt", "Formatter"];
pub const DEBUG_STRUCT: [&str; 4] = ["core", "fmt", "builders", "DebugStruct"];
+pub const ORD_CMP: [&str; 4] = ["core", "cmp", "Ord", "cmp"];
+#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so
+pub const BOOL_THEN: [&str; 4] = ["core", "bool", "<impl bool>", "then"];
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
index fbf4ab272..139e31bc5 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -14,10 +14,9 @@ use rustc_middle::mir::{
Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
Terminator, TerminatorKind,
};
-use rustc_middle::traits::{ImplSource, ObligationCause};
-use rustc_middle::ty::subst::GenericArgKind;
-use rustc_middle::ty::{self, adjustment::PointerCoercion, Ty, TyCtxt};
-use rustc_middle::ty::{BoundConstness, TraitRef};
+use rustc_middle::traits::{BuiltinImplSource, ImplSource, ObligationCause};
+use rustc_middle::ty::adjustment::PointerCoercion;
+use rustc_middle::ty::{self, GenericArgKind, TraitRef, Ty, TyCtxt};
use rustc_semver::RustcVersion;
use rustc_span::symbol::sym;
use rustc_span::Span;
@@ -35,7 +34,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
// impl trait is gone in MIR, so check the return type manually
check_ty(
tcx,
- tcx.fn_sig(def_id).subst_identity().output().skip_binder(),
+ tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(),
body.local_decls.iter().next().unwrap().source_info.span,
)?;
@@ -125,7 +124,9 @@ fn check_rvalue<'tcx>(
) => check_operand(tcx, operand, span, body),
Rvalue::Cast(
CastKind::PointerCoercion(
- PointerCoercion::UnsafeFnPointer | PointerCoercion::ClosureFnPointer(_) | PointerCoercion::ReifyFnPointer,
+ PointerCoercion::UnsafeFnPointer
+ | PointerCoercion::ClosureFnPointer(_)
+ | PointerCoercion::ReifyFnPointer,
),
_,
_,
@@ -390,32 +391,39 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
#[expect(clippy::similar_names)] // bit too pedantic
fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
- // Avoid selecting for simple cases, such as builtin types.
- if ty::util::is_trivially_const_drop(ty) {
- return true;
- }
+ // FIXME(effects, fee1-dead) revert to const destruct once it works again
+ #[expect(unused)]
+ fn is_ty_const_destruct_unused<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
+ // Avoid selecting for simple cases, such as builtin types.
+ if ty::util::is_trivially_const_drop(ty) {
+ return true;
+ }
- let obligation = Obligation::new(
- tcx,
- ObligationCause::dummy_with_span(body.span),
- ConstCx::new(tcx, body).param_env.with_const(),
- TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]).with_constness(BoundConstness::ConstIfConst),
- );
+ // FIXME(effects) constness
+ let obligation = Obligation::new(
+ tcx,
+ ObligationCause::dummy_with_span(body.span),
+ ConstCx::new(tcx, body).param_env,
+ TraitRef::from_lang_item(tcx, LangItem::Destruct, body.span, [ty]),
+ );
- let infcx = tcx.infer_ctxt().build();
- let mut selcx = SelectionContext::new(&infcx);
- let Some(impl_src) = selcx.select(&obligation).ok().flatten() else {
- return false;
- };
+ let infcx = tcx.infer_ctxt().build();
+ let mut selcx = SelectionContext::new(&infcx);
+ let Some(impl_src) = selcx.select(&obligation).ok().flatten() else {
+ return false;
+ };
+
+ if !matches!(
+ impl_src,
+ ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(_)
+ ) {
+ return false;
+ }
- if !matches!(
- impl_src,
- ImplSource::Builtin(_) | ImplSource::Param(_, ty::BoundConstness::ConstIfConst)
- ) {
- return false;
+ let ocx = ObligationCtxt::new(&infcx);
+ ocx.register_obligations(impl_src.nested_obligations());
+ ocx.select_all_or_error().is_empty()
}
- let ocx = ObligationCtxt::new(&infcx);
- ocx.register_obligations(impl_src.nested_obligations());
- ocx.select_all_or_error().is_empty()
+ !ty.needs_drop(tcx, ConstCx::new(tcx, body).param_env)
}
diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs
index 582337b47..dc4ee7256 100644
--- a/src/tools/clippy/clippy_utils/src/source.rs
+++ b/src/tools/clippy/clippy_utils/src/source.rs
@@ -8,8 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
use rustc_lint::{LateContext, LintContext};
use rustc_session::Session;
use rustc_span::source_map::{original_sp, SourceMap};
-use rustc_span::{hygiene, SourceFile};
-use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
+use rustc_span::{hygiene, BytePos, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP};
use std::borrow::Cow;
use std::ops::Range;
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index cf781e18c..ee5a49a20 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -395,7 +395,7 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
}
}
-/// Return `true` if `sugg` is enclosed in parenthesis.
+/// Returns `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('(') {
@@ -877,7 +877,7 @@ impl<'tcx> DerefDelegate<'_, 'tcx> {
.cx
.typeck_results()
.type_dependent_def_id(parent_expr.hir_id)
- .map(|did| self.cx.tcx.fn_sig(did).subst_identity().skip_binder())
+ .map(|did| self.cx.tcx.fn_sig(did).instantiate_identity().skip_binder())
{
std::iter::once(receiver)
.chain(call_args.iter())
@@ -1010,7 +1010,9 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
projections_handled = true;
},
// note: unable to trigger `Subslice` kind in tests
- ProjectionKind::Subslice => (),
+ ProjectionKind::Subslice |
+ // Doesn't have surface syntax. Only occurs in patterns.
+ ProjectionKind::OpaqueCast => (),
ProjectionKind::Deref => {
// Explicit derefs are typically handled later on, but
// some items do not need explicit deref, such as array accesses,
diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs
index d650cbe0b..a05f682aa 100644
--- a/src/tools/clippy/clippy_utils/src/ty.rs
+++ b/src/tools/clippy/clippy_utils/src/ty.rs
@@ -3,33 +3,37 @@
#![allow(clippy::module_name_repetitions)]
use core::ops::ControlFlow;
+use itertools::Itertools;
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::{
- type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
- TyCtxtInferExt,
-};
+use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
+use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::mir::interpret::{ConstValue, Scalar};
+use rustc_middle::traits::EvaluationResult;
+use rustc_middle::ty::layout::ValidityRequirement;
use rustc_middle::ty::{
- self, layout::ValidityRequirement, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, IntTy, List, ParamEnv,
- Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
- UintTy, VariantDef, VariantDiscr,
+ self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
+ GenericParamDefKind, IntTy, List, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt,
+ TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr,
};
-use rustc_middle::ty::{GenericArg, GenericArgKind};
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::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
+use rustc_trait_selection::traits::{Obligation, ObligationCause};
use std::iter;
use crate::{match_def_path, path_res, paths};
+mod type_certainty;
+pub use type_certainty::expr_type_is_certain;
+
/// 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, cx.param_env)
@@ -90,14 +94,14 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'
return false;
}
- for (predicate, _span) in cx.tcx.explicit_item_bounds(def_id).subst_identity_iter_copied() {
+ for (predicate, _span) in cx.tcx.explicit_item_bounds(def_id).instantiate_identity_iter_copied() {
match predicate.kind().skip_binder() {
// For `impl Trait<U>`, it will register a predicate of `T: Trait<U>`, so we go through
// and check substitutions to find `U`.
ty::ClauseKind::Trait(trait_predicate) => {
if trait_predicate
.trait_ref
- .substs
+ .args
.types()
.skip(1) // Skip the implicit `Self` generic parameter
.any(|ty| contains_ty_adt_constructor_opaque_inner(cx, ty, needle, seen))
@@ -206,15 +210,9 @@ pub fn implements_trait<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
trait_id: DefId,
- ty_params: &[GenericArg<'tcx>],
+ args: &[GenericArg<'tcx>],
) -> bool {
- implements_trait_with_env(
- cx.tcx,
- cx.param_env,
- ty,
- trait_id,
- ty_params.iter().map(|&arg| Some(arg)),
- )
+ implements_trait_with_env_from_iter(cx.tcx, cx.param_env, ty, trait_id, args.iter().map(|&x| Some(x)))
}
/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
@@ -223,7 +221,18 @@ pub fn implements_trait_with_env<'tcx>(
param_env: ParamEnv<'tcx>,
ty: Ty<'tcx>,
trait_id: DefId,
- ty_params: impl IntoIterator<Item = Option<GenericArg<'tcx>>>,
+ args: &[GenericArg<'tcx>],
+) -> bool {
+ implements_trait_with_env_from_iter(tcx, param_env, ty, trait_id, args.iter().map(|&x| Some(x)))
+}
+
+/// Same as `implements_trait_from_env` but takes the arguments as an iterator.
+pub fn implements_trait_with_env_from_iter<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ param_env: ParamEnv<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ args: impl IntoIterator<Item = impl Into<Option<GenericArg<'tcx>>>>,
) -> bool {
// Clippy shouldn't have infer types
assert!(!ty.has_infer());
@@ -232,19 +241,37 @@ pub fn implements_trait_with_env<'tcx>(
if ty.has_escaping_bound_vars() {
return false;
}
+
let infcx = tcx.infer_ctxt().build();
- let orig = TypeVariableOrigin {
- kind: TypeVariableOriginKind::MiscVariable,
- span: DUMMY_SP,
- };
- let ty_params = tcx.mk_substs_from_iter(
- ty_params
+ let trait_ref = TraitRef::new(
+ tcx,
+ trait_id,
+ Some(GenericArg::from(ty))
.into_iter()
- .map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())),
+ .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()
+ })
+ })),
);
+
+ debug_assert_eq!(tcx.def_kind(trait_id), DefKind::Trait);
+ #[cfg(debug_assertions)]
+ assert_generic_args_match(tcx, trait_id, trait_ref.args);
+
+ let obligation = Obligation {
+ cause: ObligationCause::dummy(),
+ param_env,
+ recursion_depth: 0,
+ predicate: ty::Binder::dummy(trait_ref).to_predicate(tcx),
+ };
infcx
- .type_implements_trait(trait_id, [ty.into()].into_iter().chain(ty_params), param_env)
- .must_apply_modulo_regions()
+ .evaluate_obligation(&obligation)
+ .is_ok_and(EvaluationResult::must_apply_modulo_regions)
}
/// Checks whether this type implements `Drop`.
@@ -265,7 +292,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
// 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::Tuple(args) => args.iter().any(|ty| is_must_use_ty(cx, ty)),
ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) => {
for (predicate, _) in cx.tcx.explicit_item_bounds(def_id).skip_binder() {
if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() {
@@ -314,11 +341,11 @@ fn is_normalizable_helper<'tcx>(
let cause = rustc_middle::traits::ObligationCause::dummy();
let result = if infcx.at(&cause, param_env).query_normalize(ty).is_ok() {
match ty.kind() {
- ty::Adt(def, substs) => def.variants().iter().all(|variant| {
+ ty::Adt(def, args) => def.variants().iter().all(|variant| {
variant
.fields
.iter()
- .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, substs), cache))
+ .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, args), cache))
}),
_ => ty.walk().all(|generic_arg| match generic_arg.unpack() {
GenericArgKind::Type(inner_ty) if inner_ty != ty => {
@@ -392,6 +419,11 @@ pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangI
}
}
+/// Gets the diagnostic name of the type, if it has one
+pub fn type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Symbol> {
+ ty.ty_adt_def().and_then(|adt| cx.tcx.get_diagnostic_name(adt.did()))
+}
+
/// 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))
@@ -517,14 +549,14 @@ pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) {
/// 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)) => {
+ (&ty::Adt(did_a, args_a), &ty::Adt(did_b, args_b)) => {
if did_a != did_b {
return false;
}
- substs_a
+ args_a
.iter()
- .zip(substs_b.iter())
+ .zip(args_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)) => {
@@ -643,7 +675,7 @@ impl<'tcx> ExprFnSig<'tcx> {
/// 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).subst_identity(), Some(id)))
+ Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate_identity(), Some(id)))
} else {
ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs())
}
@@ -661,11 +693,11 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'t
.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.fn_sig(id).subst(cx.tcx, subs), Some(id))),
- ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => sig_from_bounds(
+ ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).instantiate(cx.tcx, subs), Some(id))),
+ ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => sig_from_bounds(
cx,
ty,
- cx.tcx.item_bounds(def_id).subst_iter(cx.tcx, substs),
+ cx.tcx.item_bounds(def_id).iter_instantiated(cx.tcx, args),
cx.tcx.opt_parent(def_id),
),
ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)),
@@ -681,7 +713,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'t
.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))
+ Some(ExprFnSig::Trait(bound.map_bound(|b| b.args.type_at(0)), output, None))
},
_ => None,
}
@@ -713,7 +745,7 @@ fn sig_from_bounds<'tcx>(
|| lang_items.fn_once_trait() == Some(p.def_id()))
&& p.self_ty() == ty =>
{
- let i = pred.kind().rebind(p.trait_ref.substs.type_at(1));
+ let i = pred.kind().rebind(p.trait_ref.args.type_at(1));
if inputs.map_or(false, |inputs| i != inputs) {
// Multiple different fn trait impls. Is this even allowed?
return None;
@@ -744,7 +776,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option
for (pred, _) in cx
.tcx
.explicit_item_bounds(ty.def_id)
- .subst_iter_copied(cx.tcx, ty.substs)
+ .iter_instantiated_copied(cx.tcx, ty.args)
{
match pred.kind().skip_binder() {
ty::ClauseKind::Trait(p)
@@ -752,7 +784,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option
|| lang_items.fn_mut_trait() == Some(p.def_id())
|| lang_items.fn_once_trait() == Some(p.def_id())) =>
{
- let i = pred.kind().rebind(p.trait_ref.substs.type_at(1));
+ let i = pred.kind().rebind(p.trait_ref.args.type_at(1));
if inputs.map_or(false, |inputs| inputs != i) {
// Multiple different fn trait impls. Is this even allowed?
@@ -793,7 +825,7 @@ impl core::ops::Add<u32> for EnumValue {
#[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).subst_identity().kind() {
+ match tcx.type_of(id).instantiate_identity().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),
@@ -927,7 +959,7 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<
Some((adt, adt.variant_with_id(var_id)))
},
Res::SelfCtor(id) => {
- let adt = cx.tcx.type_of(id).subst_identity().ty_adt_def().unwrap();
+ let adt = cx.tcx.type_of(id).instantiate_identity().ty_adt_def().unwrap();
Some((adt, adt.non_enum_variant()))
},
_ => None,
@@ -940,8 +972,7 @@ pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tc
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())
+ 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;
};
@@ -1014,91 +1045,104 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
}
}
+/// Asserts that the given arguments match the generic parameters of the given item.
+#[allow(dead_code)]
+fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[GenericArg<'tcx>]) {
+ let g = tcx.generics_of(did);
+ let parent = g.parent.map(|did| tcx.generics_of(did));
+ let count = g.parent_count + g.params.len();
+ let params = parent
+ .map_or([].as_slice(), |p| p.params.as_slice())
+ .iter()
+ .chain(&g.params)
+ .map(|x| &x.kind);
+
+ assert!(
+ count == args.len(),
+ "wrong number of arguments for `{did:?}`: expected `{count}`, found {}\n\
+ note: the expected arguments are: `[{}]`\n\
+ the given arguments are: `{args:#?}`",
+ args.len(),
+ params.clone().map(GenericParamDefKind::descr).format(", "),
+ );
+
+ if let Some((idx, (param, arg))) =
+ params
+ .clone()
+ .zip(args.iter().map(|&x| x.unpack()))
+ .enumerate()
+ .find(|(_, (param, arg))| match (param, arg) {
+ (GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_))
+ | (GenericParamDefKind::Type { .. }, GenericArgKind::Type(_))
+ | (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) => false,
+ (
+ GenericParamDefKind::Lifetime
+ | GenericParamDefKind::Type { .. }
+ | GenericParamDefKind::Const { .. },
+ _,
+ ) => true,
+ })
+ {
+ panic!(
+ "incorrect argument for `{did:?}` at index `{idx}`: expected a {}, found `{arg:?}`\n\
+ note: the expected arguments are `[{}]`\n\
+ the given arguments are `{args:#?}`",
+ param.descr(),
+ params.clone().map(GenericParamDefKind::descr).format(", "),
+ );
+ }
+}
+
+/// Returns whether `ty` is never-like; i.e., `!` (never) or an enum with zero variants.
+pub fn is_never_like(ty: Ty<'_>) -> bool {
+ ty.is_never() || (ty.is_enum() && ty.ty_adt_def().is_some_and(|def| def.variants().is_empty()))
+}
+
/// Makes the projection type for the named associated type in the given impl or trait impl.
///
/// This function is for associated types which are "known" to exist, and as such, will only return
/// `None` when debug assertions are disabled in order to prevent ICE's. With debug assertions
/// enabled this will check that the named associated type exists, the correct number of
-/// substitutions are given, and that the correct kinds of substitutions are given (lifetime,
+/// arguments are given, and that the correct kinds of arguments are given (lifetime,
/// constant or type). This will not check if type normalization would succeed.
pub fn make_projection<'tcx>(
tcx: TyCtxt<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
- substs: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
+ args: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
) -> Option<AliasTy<'tcx>> {
fn helper<'tcx>(
tcx: TyCtxt<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
- substs: SubstsRef<'tcx>,
+ args: GenericArgsRef<'tcx>,
) -> Option<AliasTy<'tcx>> {
- let Some(assoc_item) = tcx
- .associated_items(container_id)
- .find_by_name_and_kind(tcx, Ident::with_dummy_span(assoc_ty), AssocKind::Type, container_id)
- else {
+ let Some(assoc_item) = tcx.associated_items(container_id).find_by_name_and_kind(
+ tcx,
+ Ident::with_dummy_span(assoc_ty),
+ AssocKind::Type,
+ container_id,
+ ) else {
debug_assert!(false, "type `{assoc_ty}` not found in `{container_id:?}`");
return None;
};
#[cfg(debug_assertions)]
- {
- let generics = tcx.generics_of(assoc_item.def_id);
- let generic_count = generics.parent_count + generics.params.len();
- let params = generics
- .parent
- .map_or([].as_slice(), |id| &*tcx.generics_of(id).params)
- .iter()
- .chain(&generics.params)
- .map(|x| &x.kind);
-
- debug_assert!(
- generic_count == substs.len(),
- "wrong number of substs for `{:?}`: found `{}` expected `{generic_count}`.\n\
- note: the expected parameters are: {:#?}\n\
- the given arguments are: `{substs:#?}`",
- assoc_item.def_id,
- substs.len(),
- params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>(),
- );
-
- if let Some((idx, (param, arg))) = params
- .clone()
- .zip(substs.iter().map(GenericArg::unpack))
- .enumerate()
- .find(|(_, (param, arg))| {
- !matches!(
- (param, arg),
- (ty::GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_))
- | (ty::GenericParamDefKind::Type { .. }, GenericArgKind::Type(_))
- | (ty::GenericParamDefKind::Const { .. }, GenericArgKind::Const(_))
- )
- })
- {
- debug_assert!(
- false,
- "mismatched subst type at index {idx}: expected a {}, found `{arg:?}`\n\
- note: the expected parameters are {:#?}\n\
- the given arguments are {substs:#?}",
- param.descr(),
- params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>()
- );
- }
- }
+ assert_generic_args_match(tcx, assoc_item.def_id, args);
- Some(tcx.mk_alias_ty(assoc_item.def_id, substs))
+ Some(tcx.mk_alias_ty(assoc_item.def_id, args))
}
helper(
tcx,
container_id,
assoc_ty,
- tcx.mk_substs_from_iter(substs.into_iter().map(Into::into)),
+ tcx.mk_args_from_iter(args.into_iter().map(Into::into)),
)
}
/// Normalizes the named associated type in the given impl or trait impl.
///
/// This function is for associated types which are "known" to be valid with the given
-/// substitutions, and as such, will only return `None` when debug assertions are disabled in order
+/// arguments, and as such, will only return `None` when debug assertions are disabled in order
/// to prevent ICE's. With debug assertions enabled this will check that type normalization
/// succeeds as well as everything checked by `make_projection`.
pub fn make_normalized_projection<'tcx>(
@@ -1106,25 +1150,20 @@ pub fn make_normalized_projection<'tcx>(
param_env: ParamEnv<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
- substs: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
+ args: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
) -> Option<Ty<'tcx>> {
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
#[cfg(debug_assertions)]
- if let Some((i, subst)) = ty
- .substs
- .iter()
- .enumerate()
- .find(|(_, subst)| subst.has_late_bound_regions())
- {
+ if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) {
debug_assert!(
false,
- "substs contain late-bound region at index `{i}` which can't be normalized.\n\
+ "args contain late-bound region at index `{i}` which can't be normalized.\n\
use `TyCtxt::erase_late_bound_regions`\n\
- note: subst is `{subst:#?}`",
+ note: arg is `{arg:#?}`",
);
return None;
}
- match tcx.try_normalize_erasing_regions(param_env, Ty::new_projection(tcx,ty.def_id, ty.substs)) {
+ match tcx.try_normalize_erasing_regions(param_env, Ty::new_projection(tcx, ty.def_id, ty.args)) {
Ok(ty) => Some(ty),
Err(e) => {
debug_assert!(false, "failed to normalize type `{ty}`: {e:#?}");
@@ -1132,7 +1171,7 @@ pub fn make_normalized_projection<'tcx>(
},
}
}
- helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?)
+ helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, args)?)
}
/// Check if given type has inner mutability such as [`std::cell::Cell`] or [`std::cell::RefCell`]
@@ -1147,7 +1186,7 @@ pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
&& is_interior_mut_ty(cx, inner_ty)
},
ty::Tuple(fields) => fields.iter().any(|ty| is_interior_mut_ty(cx, ty)),
- ty::Adt(def, substs) => {
+ ty::Adt(def, args) => {
// Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
// that of their type parameters. Note: we don't include `HashSet` and `HashMap`
// because they have no impl for `Hash` or `Ord`.
@@ -1168,7 +1207,7 @@ pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
if is_std_collection || is_box {
// The type is mutable if any of its type parameters are
- substs.types().any(|ty| is_interior_mut_ty(cx, ty))
+ args.types().any(|ty| is_interior_mut_ty(cx, ty))
} else {
!ty.has_escaping_bound_vars()
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
@@ -1184,21 +1223,16 @@ pub fn make_normalized_projection_with_regions<'tcx>(
param_env: ParamEnv<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
- substs: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
+ args: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
) -> Option<Ty<'tcx>> {
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
#[cfg(debug_assertions)]
- if let Some((i, subst)) = ty
- .substs
- .iter()
- .enumerate()
- .find(|(_, subst)| subst.has_late_bound_regions())
- {
+ if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) {
debug_assert!(
false,
- "substs contain late-bound region at index `{i}` which can't be normalized.\n\
+ "args contain late-bound region at index `{i}` which can't be normalized.\n\
use `TyCtxt::erase_late_bound_regions`\n\
- note: subst is `{subst:#?}`",
+ note: arg is `{arg:#?}`",
);
return None;
}
@@ -1207,7 +1241,7 @@ pub fn make_normalized_projection_with_regions<'tcx>(
.infer_ctxt()
.build()
.at(&cause, param_env)
- .query_normalize(Ty::new_projection(tcx,ty.def_id, ty.substs))
+ .query_normalize(Ty::new_projection(tcx, ty.def_id, ty.args))
{
Ok(ty) => Some(ty.value),
Err(e) => {
@@ -1216,7 +1250,7 @@ pub fn make_normalized_projection_with_regions<'tcx>(
},
}
}
- helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?)
+ helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, args)?)
}
pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
diff --git a/src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs b/src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs
new file mode 100644
index 000000000..0e69ffa22
--- /dev/null
+++ b/src/tools/clippy/clippy_utils/src/ty/type_certainty/certainty.rs
@@ -0,0 +1,122 @@
+use rustc_hir::def_id::DefId;
+use std::fmt::Debug;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Certainty {
+ /// Determining the type requires contextual information.
+ Uncertain,
+
+ /// The type can be determined purely from subexpressions. If the argument is `Some(..)`, the
+ /// specific `DefId` is known. Such arguments are needed to handle path segments whose `res` is
+ /// `Res::Err`.
+ Certain(Option<DefId>),
+
+ /// The heuristic believes that more than one `DefId` applies to a type---this is a bug.
+ Contradiction,
+}
+
+pub trait Meet {
+ fn meet(self, other: Self) -> Self;
+}
+
+pub trait TryJoin: Sized {
+ fn try_join(self, other: Self) -> Option<Self>;
+}
+
+impl Meet for Option<DefId> {
+ fn meet(self, other: Self) -> Self {
+ match (self, other) {
+ (None, _) | (_, None) => None,
+ (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(lhs),
+ }
+ }
+}
+
+impl TryJoin for Option<DefId> {
+ fn try_join(self, other: Self) -> Option<Self> {
+ match (self, other) {
+ (Some(lhs), Some(rhs)) => (lhs == rhs).then_some(Some(lhs)),
+ (Some(def_id), _) | (_, Some(def_id)) => Some(Some(def_id)),
+ (None, None) => Some(None),
+ }
+ }
+}
+
+impl Meet for Certainty {
+ fn meet(self, other: Self) -> Self {
+ match (self, other) {
+ (Certainty::Uncertain, _) | (_, Certainty::Uncertain) => Certainty::Uncertain,
+ (Certainty::Certain(lhs), Certainty::Certain(rhs)) => Certainty::Certain(lhs.meet(rhs)),
+ (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner),
+ (Certainty::Contradiction, Certainty::Contradiction) => Certainty::Contradiction,
+ }
+ }
+}
+
+impl Certainty {
+ /// Join two `Certainty`s preserving their `DefId`s (if any). Generally speaking, this method
+ /// should be used only when `self` and `other` refer directly to types. Otherwise,
+ /// `join_clearing_def_ids` should be used.
+ pub fn join(self, other: Self) -> Self {
+ match (self, other) {
+ (Certainty::Contradiction, _) | (_, Certainty::Contradiction) => Certainty::Contradiction,
+
+ (Certainty::Certain(lhs), Certainty::Certain(rhs)) => {
+ if let Some(inner) = lhs.try_join(rhs) {
+ Certainty::Certain(inner)
+ } else {
+ debug_assert!(false, "Contradiction with {lhs:?} and {rhs:?}");
+ Certainty::Contradiction
+ }
+ },
+
+ (Certainty::Certain(inner), _) | (_, Certainty::Certain(inner)) => Certainty::Certain(inner),
+
+ (Certainty::Uncertain, Certainty::Uncertain) => Certainty::Uncertain,
+ }
+ }
+
+ /// Join two `Certainty`s after clearing their `DefId`s. This method should be used when `self`
+ /// or `other` do not necessarily refer to types, e.g., when they are aggregations of other
+ /// `Certainty`s.
+ pub fn join_clearing_def_ids(self, other: Self) -> Self {
+ self.clear_def_id().join(other.clear_def_id())
+ }
+
+ pub fn clear_def_id(self) -> Certainty {
+ if matches!(self, Certainty::Certain(_)) {
+ Certainty::Certain(None)
+ } else {
+ self
+ }
+ }
+
+ pub fn with_def_id(self, def_id: DefId) -> Certainty {
+ if matches!(self, Certainty::Certain(_)) {
+ Certainty::Certain(Some(def_id))
+ } else {
+ self
+ }
+ }
+
+ pub fn to_def_id(self) -> Option<DefId> {
+ match self {
+ Certainty::Certain(inner) => inner,
+ _ => None,
+ }
+ }
+
+ pub fn is_certain(self) -> bool {
+ matches!(self, Self::Certain(_))
+ }
+}
+
+/// Think: `iter.all(/* is certain */)`
+pub fn meet(iter: impl Iterator<Item = Certainty>) -> Certainty {
+ iter.fold(Certainty::Certain(None), Certainty::meet)
+}
+
+/// Think: `iter.any(/* is certain */)`
+pub fn join(iter: impl Iterator<Item = Certainty>) -> Certainty {
+ iter.fold(Certainty::Uncertain, Certainty::join)
+}
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
new file mode 100644
index 000000000..06fd95290
--- /dev/null
+++ b/src/tools/clippy/clippy_utils/src/ty/type_certainty/mod.rs
@@ -0,0 +1,320 @@
+//! A heuristic to tell whether an expression's type can be determined purely from its
+//! subexpressions, and the arguments and locals they use. Put another way, `expr_type_is_certain`
+//! tries to tell whether an expression's type can be determined without appeal to the surrounding
+//! context.
+//!
+//! This is, in some sense, a counterpart to `let_unit_value`'s `expr_needs_inferred_result`.
+//! Intuitively, that function determines whether an expression's type is needed for type inference,
+//! whereas `expr_type_is_certain` determines whether type inference is needed for an expression's
+//! type.
+//!
+//! As a heuristic, `expr_type_is_certain` may produce false negatives, but a false positive should
+//! be considered a bug.
+
+use crate::def_path_res;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::intravisit::{walk_qpath, walk_ty, Visitor};
+use rustc_hir::{self as hir, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
+use rustc_span::{Span, Symbol};
+
+mod certainty;
+use certainty::{join, meet, Certainty, Meet};
+
+pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ expr_type_certainty(cx, expr).is_certain()
+}
+
+fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
+ let certainty = match &expr.kind {
+ ExprKind::Unary(_, expr)
+ | ExprKind::Field(expr, _)
+ | ExprKind::Index(expr, _, _)
+ | ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr),
+
+ ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
+
+ ExprKind::Call(callee, args) => {
+ let lhs = expr_type_certainty(cx, callee);
+ let rhs = if type_is_inferrable_from_arguments(cx, expr) {
+ meet(args.iter().map(|arg| expr_type_certainty(cx, arg)))
+ } else {
+ Certainty::Uncertain
+ };
+ lhs.join_clearing_def_ids(rhs)
+ },
+
+ ExprKind::MethodCall(method, receiver, args, _) => {
+ let mut receiver_type_certainty = expr_type_certainty(cx, receiver);
+ // Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method
+ // identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`,
+ // for example. So update the `DefId` in `receiver_type_certainty` (if any).
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
+ && let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id))
+ {
+ receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id);
+ };
+ let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
+ let rhs = if type_is_inferrable_from_arguments(cx, expr) {
+ meet(
+ std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))),
+ )
+ } else {
+ Certainty::Uncertain
+ };
+ lhs.join(rhs)
+ },
+
+ ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
+
+ ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)),
+
+ ExprKind::Lit(_) => Certainty::Certain(None),
+
+ ExprKind::Cast(_, ty) => type_certainty(cx, ty),
+
+ ExprKind::If(_, if_expr, Some(else_expr)) => {
+ expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr))
+ },
+
+ ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false),
+
+ ExprKind::Struct(qpath, _, _) => qpath_certainty(cx, qpath, true),
+
+ _ => Certainty::Uncertain,
+ };
+
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ if let Some(def_id) = adt_def_id(expr_ty) {
+ certainty.with_def_id(def_id)
+ } else {
+ certainty
+ }
+}
+
+struct CertaintyVisitor<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ certainty: Certainty,
+}
+
+impl<'cx, 'tcx> CertaintyVisitor<'cx, 'tcx> {
+ fn new(cx: &'cx LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ certainty: Certainty::Certain(None),
+ }
+ }
+}
+
+impl<'cx, 'tcx> Visitor<'cx> for CertaintyVisitor<'cx, 'tcx> {
+ fn visit_qpath(&mut self, qpath: &'cx QPath<'_>, hir_id: HirId, _: Span) {
+ self.certainty = self.certainty.meet(qpath_certainty(self.cx, qpath, true));
+ if self.certainty != Certainty::Uncertain {
+ walk_qpath(self, qpath, hir_id);
+ }
+ }
+
+ fn visit_ty(&mut self, ty: &'cx hir::Ty<'_>) {
+ if matches!(ty.kind, TyKind::Infer) {
+ self.certainty = Certainty::Uncertain;
+ }
+ if self.certainty != Certainty::Uncertain {
+ walk_ty(self, ty);
+ }
+ }
+}
+
+fn type_certainty(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Certainty {
+ // Handle `TyKind::Path` specially so that its `DefId` can be preserved.
+ //
+ // Note that `CertaintyVisitor::new` initializes the visitor's internal certainty to
+ // `Certainty::Certain(None)`. Furthermore, if a `TyKind::Path` is encountered while traversing
+ // `ty`, the result of the call to `qpath_certainty` is combined with the visitor's internal
+ // certainty using `Certainty::meet`. Thus, if the `TyKind::Path` were not treated specially here,
+ // the resulting certainty would be `Certainty::Certain(None)`.
+ if let TyKind::Path(qpath) = &ty.kind {
+ return qpath_certainty(cx, qpath, true);
+ }
+
+ let mut visitor = CertaintyVisitor::new(cx);
+ visitor.visit_ty(ty);
+ visitor.certainty
+}
+
+fn generic_args_certainty(cx: &LateContext<'_>, args: &GenericArgs<'_>) -> Certainty {
+ let mut visitor = CertaintyVisitor::new(cx);
+ visitor.visit_generic_args(args);
+ visitor.certainty
+}
+
+/// Tries to tell whether a `QPath` resolves to something certain, e.g., whether all of its path
+/// segments generic arguments are are instantiated.
+///
+/// `qpath` could refer to either a type or a value. The heuristic never needs the `DefId` of a
+/// value. So `DefId`s are retained only when `resolves_to_type` is true.
+fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bool) -> Certainty {
+ let certainty = match qpath {
+ QPath::Resolved(ty, path) => {
+ let len = path.segments.len();
+ path.segments.iter().enumerate().fold(
+ ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty)),
+ |parent_certainty, (i, path_segment)| {
+ path_segment_certainty(cx, parent_certainty, path_segment, i != len - 1 || resolves_to_type)
+ },
+ )
+ },
+
+ QPath::TypeRelative(ty, path_segment) => {
+ 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
+ }
+ })
+ },
+ };
+ debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
+ certainty
+}
+
+fn path_segment_certainty(
+ cx: &LateContext<'_>,
+ parent_certainty: Certainty,
+ path_segment: &PathSegment<'_>,
+ resolves_to_type: bool,
+) -> Certainty {
+ let certainty = match update_res(cx, parent_certainty, path_segment).unwrap_or(path_segment.res) {
+ // A definition's type is certain if it refers to something without generics (e.g., a crate or module, or
+ // an unparameterized type), or the generics are instantiated with arguments that are certain.
+ //
+ // If the parent is uncertain, then the current path segment must account for the parent's generic arguments.
+ // Consider the following examples, where the current path segment is `None`:
+ // - `Option::None` // uncertain; parent (i.e., `Option`) is uncertain
+ // - `Option::<Vec<u64>>::None` // certain; parent (i.e., `Option::<..>`) is certain
+ // - `Option::None::<Vec<u64>>` // certain; parent (i.e., `Option`) is uncertain
+ Res::Def(_, def_id) => {
+ // Checking `res_generics_def_id(..)` before calling `generics_of` avoids an ICE.
+ if cx.tcx.res_generics_def_id(path_segment.res).is_some() {
+ let generics = cx.tcx.generics_of(def_id);
+ let lhs = if (parent_certainty.is_certain() || generics.parent_count == 0) && generics.params.is_empty()
+ {
+ Certainty::Certain(None)
+ } else {
+ Certainty::Uncertain
+ };
+ let rhs = path_segment
+ .args
+ .map_or(Certainty::Uncertain, |args| generic_args_certainty(cx, args));
+ // See the comment preceding `qpath_certainty`. `def_id` could refer to a type or a value.
+ let certainty = lhs.join_clearing_def_ids(rhs);
+ if resolves_to_type {
+ if let DefKind::TyAlias { .. } = cx.tcx.def_kind(def_id) {
+ adt_def_id(cx.tcx.type_of(def_id).instantiate_identity())
+ .map_or(certainty, |def_id| certainty.with_def_id(def_id))
+ } else {
+ certainty.with_def_id(def_id)
+ }
+ } else {
+ certainty
+ }
+ } else {
+ Certainty::Certain(None)
+ }
+ },
+
+ Res::PrimTy(_) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } | Res::SelfCtor(_) => {
+ Certainty::Certain(None)
+ },
+
+ // `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`.
+ Res::Local(hir_id) => match cx.tcx.hir().get_parent(hir_id) {
+ // An argument's type is always certain.
+ Node::Param(..) => Certainty::Certain(None),
+ // A local's type is certain if its type annotation is certain or it has an initializer whose
+ // type is certain.
+ Node::Local(local) => {
+ let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty));
+ let rhs = local
+ .init
+ .map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init));
+ let certainty = lhs.join(rhs);
+ if resolves_to_type {
+ certainty
+ } else {
+ certainty.clear_def_id()
+ }
+ },
+ _ => Certainty::Uncertain,
+ },
+
+ _ => Certainty::Uncertain,
+ };
+ debug_assert!(resolves_to_type || certainty.to_def_id().is_none());
+ certainty
+}
+
+/// For at least some `QPath::TypeRelative`, the path segment's `res` can be `Res::Err`.
+/// `update_res` tries to fix the resolution when `parent_certainty` is `Certain(Some(..))`.
+fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &PathSegment<'_>) -> Option<Res> {
+ if path_segment.res == Res::Err && let Some(def_id) = parent_certainty.to_def_id() {
+ let mut def_path = cx.get_def_path(def_id);
+ def_path.push(path_segment.ident.name);
+ let reses = def_path_res(cx, &def_path.iter().map(Symbol::as_str).collect::<Vec<_>>());
+ if let [res] = reses.as_slice() { Some(*res) } else { None }
+ } else {
+ None
+ }
+}
+
+#[allow(clippy::cast_possible_truncation)]
+fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let Some(callee_def_id) = (match expr.kind {
+ ExprKind::Call(callee, _) => {
+ let callee_ty = cx.typeck_results().expr_ty(callee);
+ if let ty::FnDef(callee_def_id, _) = callee_ty.kind() {
+ Some(*callee_def_id)
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ }) else {
+ return false;
+ };
+
+ let generics = cx.tcx.generics_of(callee_def_id);
+ let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
+
+ // Check that all type parameters appear in the functions input types.
+ (0..(generics.parent_count + generics.params.len()) as u32).all(|index| {
+ fn_sig
+ .inputs()
+ .iter()
+ .any(|input_ty| contains_param(*input_ty.skip_binder(), index))
+ })
+}
+
+fn self_ty<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId) -> Ty<'tcx> {
+ cx.tcx.fn_sig(method_def_id).skip_binder().inputs().skip_binder()[0]
+}
+
+fn adt_def_id(ty: Ty<'_>) -> Option<DefId> {
+ ty.peel_refs().ty_adt_def().map(AdtDef::did)
+}
+
+fn contains_param(ty: Ty<'_>, index: u32) -> bool {
+ ty.walk()
+ .any(|arg| matches!(arg.unpack(), GenericArgKind::Type(ty) if ty.is_param(index)))
+}
diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs
index 985508521..39ef76348 100644
--- a/src/tools/clippy/clippy_utils/src/usage.rs
+++ b/src/tools/clippy/clippy_utils/src/usage.rs
@@ -1,10 +1,9 @@
-use crate as utils;
-use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
+use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend, Visitable};
+use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
use core::ops::ControlFlow;
-use rustc_hir as hir;
+use hir::def::Res;
use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::HirIdSet;
-use rustc_hir::{Expr, ExprKind, HirId, Node};
+use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
@@ -129,7 +128,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
}
fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) {
- if let hir::def::Res::Local(id) = path.res {
+ if let Res::Local(id) = path.res {
if self.binding_ids.contains(&id) {
self.usage_found = true;
}
@@ -155,8 +154,21 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
.is_some()
}
+pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
+ for_each_expr_with_closures(cx, v, |e| {
+ if utils::path_to_local_id(e, local_id) {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(())
+ }
+ })
+ .is_some()
+}
+
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 };
+ let Some(block) = utils::get_enclosing_block(cx, local_id) else {
+ return false;
+ };
// for _ in 1..3 {
// local
@@ -165,32 +177,21 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr
// 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 loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
let mut past_expr = false;
for_each_expr_with_closures(cx, block, |e| {
- if e.hir_id == after.hir_id {
+ if past_expr {
+ if utils::path_to_local_id(e, local_id) {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(Descend::Yes)
+ }
+ } else if e.hir_id == after.hir_id {
past_expr = true;
ControlFlow::Continue(Descend::No)
- } else if past_expr && utils::path_to_local_id(e, local_id) {
- ControlFlow::Break(())
} else {
+ past_expr = Some(e.hir_id) == loop_start;
ControlFlow::Continue(Descend::Yes)
}
})
diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs
index 8dafa723a..3b47a4513 100644
--- a/src/tools/clippy/clippy_utils/src/visitors.rs
+++ b/src/tools/clippy/clippy_utils/src/visitors.rs
@@ -52,6 +52,16 @@ pub trait Visitable<'tcx> {
/// Calls the corresponding `visit_*` function on the visitor.
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
}
+impl<'tcx, T> Visitable<'tcx> for &'tcx [T]
+where
+ &'tcx T: Visitable<'tcx>,
+{
+ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+ for x in self {
+ x.visit(visitor);
+ }
+ }
+}
macro_rules! visitable_ref {
($t:ident, $f:ident) => {
impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
@@ -151,7 +161,7 @@ pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
/// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool {
for_each_expr(expr, |e| {
- if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)) {
+ if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar(_))) {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
@@ -319,7 +329,7 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) ->
&& 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, _)
+ ExprKind::Index(base, _, _)
if matches!(
self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
ty::Slice(_) | ty::Array(..)
@@ -619,7 +629,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
helper(typeck, true, arg, f)?;
}
},
- ExprKind::Index(borrowed, consumed)
+ ExprKind::Index(borrowed, consumed, _)
| ExprKind::Assign(borrowed, consumed, _)
| ExprKind::AssignOp(_, borrowed, consumed) => {
helper(typeck, false, borrowed, f)?;