From dc0db358abe19481e475e10c32149b53370f1a1c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:57:31 +0200 Subject: Merging upstream version 1.72.1+dfsg1. Signed-off-by: Daniel Baumann --- src/tools/clippy/clippy_utils/Cargo.toml | 2 +- .../clippy/clippy_utils/src/check_proc_macro.rs | 122 +++++++++++++++-- src/tools/clippy/clippy_utils/src/consts.rs | 150 ++++++++++++--------- src/tools/clippy/clippy_utils/src/diagnostics.rs | 22 +-- src/tools/clippy/clippy_utils/src/eager_or_lazy.rs | 28 +++- src/tools/clippy/clippy_utils/src/hir_utils.rs | 3 + src/tools/clippy/clippy_utils/src/lib.rs | 23 +++- src/tools/clippy/clippy_utils/src/macros.rs | 14 +- src/tools/clippy/clippy_utils/src/mir/mod.rs | 23 ++-- src/tools/clippy/clippy_utils/src/msrvs.rs | 8 +- .../clippy/clippy_utils/src/numeric_literal.rs | 2 +- src/tools/clippy/clippy_utils/src/paths.rs | 6 +- .../clippy_utils/src/qualify_min_const_fn.rs | 123 ++++++++++------- src/tools/clippy/clippy_utils/src/source.rs | 13 +- src/tools/clippy/clippy_utils/src/sugg.rs | 10 +- src/tools/clippy/clippy_utils/src/ty.rs | 82 ++++++++--- src/tools/clippy/clippy_utils/src/usage.rs | 2 +- src/tools/clippy/clippy_utils/src/visitors.rs | 1 + 18 files changed, 449 insertions(+), 185 deletions(-) (limited to 'src/tools/clippy/clippy_utils') diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml index 66a5079fa..cfe686eb9 100644 --- a/src/tools/clippy/clippy_utils/Cargo.toml +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.71" +version = "0.1.72" edition = "2021" publish = false 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 9edaae853..c6d0b654f 100644 --- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs +++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs @@ -12,25 +12,33 @@ //! 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::{IntTy, LitIntType, LitKind, StrStyle, UintTy}; +use rustc_ast::{ + ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, UintTy}, + token::CommentKind, + AttrStyle, +}; use rustc_hir::{ intravisit::FnKind, Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, HirId, - Impl, ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, Node, QPath, TraitItem, - TraitItemKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource, + 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::{Span, Symbol}; +use rustc_span::{symbol::Ident, Span, Symbol}; use rustc_target::spec::abi::Abi; /// The search pattern to look for. Used by `span_matches_pat` -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum Pat { /// A single string. Str(&'static str), + /// A single string. + OwnedStr(String), /// Any of the given strings. MultiStr(&'static [&'static str]), + /// Any of the given strings. + OwnedMultiStr(Vec), /// The string representation of the symbol. Sym(Symbol), /// Any decimal or hexadecimal digit depending on the location. @@ -51,12 +59,16 @@ fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ','); (match start_pat { Pat::Str(text) => start_str.starts_with(text), + Pat::OwnedStr(text) => start_str.starts_with(&text), Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)), + Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)), Pat::Sym(sym) => start_str.starts_with(sym.as_str()), Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit), } && match end_pat { Pat::Str(text) => end_str.ends_with(text), + Pat::OwnedStr(text) => end_str.starts_with(&text), Pat::MultiStr(texts) => texts.iter().any(|s| start_str.ends_with(s)), + Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)), Pat::Sym(sym) => end_str.ends_with(sym.as_str()), Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit), }) @@ -271,14 +283,79 @@ fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirI (start_pat, end_pat) } -pub trait WithSearchPat { +fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) { + match attr.kind { + AttrKind::Normal(..) => { + let mut pat = if matches!(attr.style, AttrStyle::Outer) { + (Pat::Str("#["), Pat::Str("]")) + } else { + (Pat::Str("#!["), Pat::Str("]")) + }; + + if let Some(ident) = attr.ident() && let Pat::Str(old_pat) = pat.0 { + // TODO: I feel like it's likely we can use `Cow` instead but this will require quite a bit of + // refactoring + // NOTE: This will likely have false positives, like `allow = 1` + pat.0 = Pat::OwnedMultiStr(vec![ident.to_string(), old_pat.to_owned()]); + pat.1 = Pat::Str(""); + } + + pat + }, + AttrKind::DocComment(_kind @ CommentKind::Line, ..) => { + if matches!(attr.style, AttrStyle::Outer) { + (Pat::Str("///"), Pat::Str("")) + } else { + (Pat::Str("//!"), Pat::Str("")) + } + }, + AttrKind::DocComment(_kind @ CommentKind::Block, ..) => { + if matches!(attr.style, AttrStyle::Outer) { + (Pat::Str("/**"), Pat::Str("*/")) + } else { + (Pat::Str("/*!"), Pat::Str("*/")) + } + }, + } +} + +fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { + match ty.kind { + TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")), + TyKind::Ptr(MutTy { mutbl, ty }) => ( + if mutbl.is_mut() { + Pat::Str("*const") + } else { + Pat::Str("*mut") + }, + ty_search_pat(ty).1, + ), + TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1), + TyKind::BareFn(bare_fn) => ( + Pat::OwnedStr(format!("{}{} fn", bare_fn.unsafety.prefix_str(), bare_fn.abi.name())), + ty_search_pat(ty).1, + ), + TyKind::Never => (Pat::Str("!"), Pat::Str("")), + 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. + _ => (Pat::Str(""), Pat::Str("")), + } +} + +fn ident_search_pat(ident: Ident) -> (Pat, Pat) { + (Pat::OwnedStr(ident.name.as_str().to_owned()), Pat::Str("")) +} + +pub trait WithSearchPat<'cx> { type Context: LintContext; fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat); fn span(&self) -> Span; } macro_rules! impl_with_search_pat { ($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => { - impl<'cx> WithSearchPat for $ty<'cx> { + impl<'cx> WithSearchPat<'cx> for $ty<'cx> { type Context = $cx<'cx>; #[allow(unused_variables)] fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) { @@ -297,8 +374,9 @@ impl_with_search_pat!(LateContext: TraitItem with trait_item_search_pat); impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat); impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat); impl_with_search_pat!(LateContext: Variant with variant_search_pat); +impl_with_search_pat!(LateContext: Ty with ty_search_pat); -impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { +impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { type Context = LateContext<'cx>; fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) { @@ -310,11 +388,37 @@ impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { } } +// `Attribute` does not have the `hir` associated lifetime, so we cannot use the macro +impl<'cx> WithSearchPat<'cx> for &'cx Attribute { + type Context = LateContext<'cx>; + + fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) { + attr_search_pat(self) + } + + fn span(&self) -> Span { + self.span + } +} + +// `Ident` does not have the `hir` associated lifetime, so we cannot use the macro +impl<'cx> WithSearchPat<'cx> for Ident { + type Context = LateContext<'cx>; + + fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) { + ident_search_pat(*self) + } + + fn span(&self) -> Span { + self.span + } +} + /// Checks if the item likely came from a proc-macro. /// /// This should be called after `in_external_macro` and the initial pattern matching of the ast as /// it is significantly slower than both of those. -pub fn is_from_proc_macro(cx: &T::Context, item: &T) -> bool { +pub fn is_from_proc_macro<'cx, T: WithSearchPat<'cx>>(cx: &T::Context, item: &T) -> bool { let (start_pat, end_pat) = item.search_pat(cx); !span_matches_pat(cx.sess(), item.span(), start_pat, end_pat) } diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index fb772644c..d1cfdc496 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -6,7 +6,7 @@ use if_chain::if_chain; use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_data_structures::sync::Lrc; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; +use rustc_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; @@ -14,7 +14,7 @@ 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_span::symbol::Symbol; +use rustc_span::symbol::{Ident, Symbol}; use rustc_span::SyntaxContext; use std::cmp::Ordering::{self, Equal}; use std::hash::{Hash, Hasher}; @@ -22,7 +22,8 @@ use std::iter; /// A `LitKind`-like enum to fold constant `Expr`s into. #[derive(Debug, Clone)] -pub enum Constant { +pub enum Constant<'tcx> { + Adt(rustc_middle::mir::ConstantKind<'tcx>), /// A `String` (e.g., "abc"). Str(String), /// A binary string (e.g., `b"abc"`). @@ -38,20 +39,20 @@ pub enum Constant { /// `true` or `false`. Bool(bool), /// An array of constants. - Vec(Vec), + Vec(Vec>), /// Also an array, but with only one constant, repeated N times. - Repeat(Box, u64), + Repeat(Box>, u64), /// A tuple of constants. - Tuple(Vec), + Tuple(Vec>), /// A raw pointer. RawPtr(u128), /// A reference - Ref(Box), + Ref(Box>), /// A literal with syntax error. Err, } -impl PartialEq for Constant { +impl<'tcx> PartialEq for Constant<'tcx> { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Str(ls), Self::Str(rs)) => ls == rs, @@ -80,13 +81,16 @@ impl PartialEq for Constant { } } -impl Hash for Constant { +impl<'tcx> Hash for Constant<'tcx> { fn hash(&self, state: &mut H) where H: Hasher, { std::mem::discriminant(self).hash(state); match *self { + Self::Adt(ref elem) => { + elem.hash(state); + }, Self::Str(ref s) => { s.hash(state); }, @@ -126,7 +130,7 @@ impl Hash for Constant { } } -impl Constant { +impl<'tcx> Constant<'tcx> { pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { match (left, right) { (Self::Str(ls), Self::Str(rs)) => Some(ls.cmp(rs)), @@ -209,7 +213,7 @@ impl Constant { } /// Parses a `LitKind` to a `Constant`. -pub fn lit_to_mir_constant(lit: &LitKind, ty: Option>) -> Constant { +pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option>) -> Constant<'tcx> { match *lit { LitKind::Str(ref is, _) => Constant::Str(is.to_string()), LitKind::Byte(b) => Constant::Int(u128::from(b)), @@ -248,7 +252,7 @@ pub fn constant<'tcx>( lcx: &LateContext<'tcx>, typeck_results: &ty::TypeckResults<'tcx>, e: &Expr<'_>, -) -> Option { +) -> Option> { ConstEvalLateContext::new(lcx, typeck_results).expr(e) } @@ -257,7 +261,7 @@ pub fn constant_with_source<'tcx>( lcx: &LateContext<'tcx>, typeck_results: &ty::TypeckResults<'tcx>, e: &Expr<'_>, -) -> Option<(Constant, ConstantSource)> { +) -> Option<(Constant<'tcx>, ConstantSource)> { let mut ctxt = ConstEvalLateContext::new(lcx, typeck_results); let res = ctxt.expr(e); res.map(|x| (x, ctxt.source)) @@ -268,7 +272,7 @@ pub fn constant_simple<'tcx>( lcx: &LateContext<'tcx>, typeck_results: &ty::TypeckResults<'tcx>, e: &Expr<'_>, -) -> Option { +) -> Option> { constant_with_source(lcx, typeck_results, e).and_then(|(c, s)| s.is_local().then_some(c)) } @@ -338,8 +342,10 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } /// Simple constant folding: Insert an expression, get a constant or none. - pub fn expr(&mut self, e: &Expr<'_>) -> Option { + pub fn expr(&mut self, e: &Expr<'_>) -> Option> { match e.kind { + ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), + ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), ExprKind::Block(block, _) => self.block(block), ExprKind::Lit(lit) => { @@ -392,13 +398,25 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { }, ExprKind::Index(arr, index) => self.index(arr, index), ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), - // TODO: add other expressions. + ExprKind::Field(local_expr, ref field) => { + let result = self.expr(local_expr); + if let Some(Constant::Adt(constant)) = &self.expr(local_expr) + && let ty::Adt(adt_def, _) = constant.ty().kind() + && adt_def.is_struct() + && let Some(desired_field) = field_of_struct(*adt_def, self.lcx, *constant, field) + { + miri_to_const(self.lcx, desired_field) + } + else { + result + } + }, _ => None, } } #[expect(clippy::cast_possible_wrap)] - fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { + fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option> { use self::Constant::{Bool, Int}; match *o { Bool(b) => Some(Bool(!b)), @@ -414,7 +432,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } } - fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { + fn constant_negate(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option> { use self::Constant::{Int, F32, F64}; match *o { Int(value) => { @@ -433,28 +451,25 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// Create `Some(Vec![..])` of all constants, unless there is any /// non-constant part. - fn multi(&mut self, vec: &[Expr<'_>]) -> Option> { + fn multi(&mut self, vec: &[Expr<'_>]) -> Option>> { vec.iter().map(|elem| self.expr(elem)).collect::>() } /// Lookup a possibly constant expression from an `ExprKind::Path`. - fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option { + fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option> { let res = self.typeck_results.qpath_res(qpath, id); match res { Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { // Check if this constant is based on `cfg!(..)`, // which is NOT constant for our purposes. - if let Some(node) = self.lcx.tcx.hir().get_if_local(def_id) && - let Node::Item(&Item { - kind: ItemKind::Const(_, body_id), - .. - }) = node && - let Node::Expr(&Expr { - kind: ExprKind::Lit(_), - span, - .. - }) = self.lcx.tcx.hir().get(body_id.hir_id) && - is_direct_expn_of(span, "cfg").is_some() { + if let Some(node) = self.lcx.tcx.hir().get_if_local(def_id) + && let Node::Item(Item { kind: ItemKind::Const(_, body_id), .. }) = node + && let Node::Expr(Expr { kind: ExprKind::Lit(_), span, .. }) = self.lcx + .tcx + .hir() + .get(body_id.hir_id) + && is_direct_expn_of(*span, "cfg").is_some() + { return None; } @@ -462,25 +477,23 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { let substs = if self.substs.is_empty() { substs } else { - EarlyBinder(substs).subst(self.lcx.tcx, self.substs) + EarlyBinder::bind(substs).subst(self.lcx.tcx, self.substs) }; - let result = self .lcx .tcx .const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, substs), None) .ok() .map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty))?; - let result = miri_to_const(self.lcx.tcx, result)?; + let result = miri_to_const(self.lcx, result)?; self.source = ConstantSource::Constant; Some(result) }, - // FIXME: cover all usable cases. _ => None, } } - fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { + fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option> { let lhs = self.expr(lhs); let index = self.expr(index); @@ -506,7 +519,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } /// A block can only yield a constant if it only has one constant expression. - fn block(&mut self, block: &Block<'_>) -> Option { + fn block(&mut self, block: &Block<'_>) -> Option> { if block.stmts.is_empty() && let Some(expr) = block.expr { @@ -539,7 +552,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } } - fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { + fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option> { if let Some(Constant::Bool(b)) = self.expr(cond) { if b { self.expr(then) @@ -551,7 +564,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } } - fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option { + fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option> { let l = self.expr(left)?; let r = self.expr(right); match (l, r) { @@ -644,23 +657,21 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } } -pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) -> Option { +pub fn miri_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::ConstantKind<'tcx>) -> Option> { use rustc_middle::mir::interpret::ConstValue; match result { - mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(int)), _) => { - match result.ty().kind() { - ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), - ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), - ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( - int.try_into().expect("invalid f32 bit representation"), - ))), - ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( - int.try_into().expect("invalid f64 bit representation"), - ))), - ty::RawPtr(_) => Some(Constant::RawPtr(int.assert_bits(int.size()))), - // FIXME: implement other conversions. - _ => None, - } + mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(int)), _) => match result.ty().kind() { + ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)), + ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), + ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), + ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( + int.try_into().expect("invalid f32 bit representation"), + ))), + ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( + int.try_into().expect("invalid f64 bit representation"), + ))), + ty::RawPtr(_) => Some(Constant::RawPtr(int.assert_bits(int.size()))), + _ => None, }, mir::ConstantKind::Val(ConstValue::Slice { data, start, end }, _) => match result.ty().kind() { ty::Ref(_, tam, _) => match tam.kind() { @@ -676,35 +687,54 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - _ => None, }, mir::ConstantKind::Val(ConstValue::ByRef { alloc, offset: _ }, _) => match result.ty().kind() { + ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)), ty::Array(sub_type, len) => match sub_type.kind() { - ty::Float(FloatTy::F32) => match len.kind().try_to_target_usize(tcx) { + ty::Float(FloatTy::F32) => match len.try_to_target_usize(lcx.tcx) { Some(len) => alloc .inner() .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap())) .to_owned() .array_chunks::<4>() .map(|&chunk| Some(Constant::F32(f32::from_le_bytes(chunk)))) - .collect::>>() + .collect::>>>() .map(Constant::Vec), _ => None, }, - ty::Float(FloatTy::F64) => match len.kind().try_to_target_usize(tcx) { + ty::Float(FloatTy::F64) => match len.try_to_target_usize(lcx.tcx) { Some(len) => alloc .inner() .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap())) .to_owned() .array_chunks::<8>() .map(|&chunk| Some(Constant::F64(f64::from_le_bytes(chunk)))) - .collect::>>() + .collect::>>>() .map(Constant::Vec), _ => None, }, - // FIXME: implement other array type conversions. _ => None, }, _ => None, }, - // FIXME: implement other conversions. _ => None, } } + +fn field_of_struct<'tcx>( + adt_def: ty::AdtDef<'tcx>, + lcx: &LateContext<'tcx>, + result: mir::ConstantKind<'tcx>, + field: &Ident, +) -> Option> { + if let mir::ConstantKind::Val(result, ty) = result + && let Some(dc) = lcx.tcx.try_destructure_mir_constant_for_diagnostics((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) + && let Some(&(val, ty)) = dc.fields.get(field_idx) + { + Some(mir::ConstantKind::Val(val, ty)) + } + else { + None + } +} diff --git a/src/tools/clippy/clippy_utils/src/diagnostics.rs b/src/tools/clippy/clippy_utils/src/diagnostics.rs index 812f6fe71..edd87546a 100644 --- a/src/tools/clippy/clippy_utils/src/diagnostics.rs +++ b/src/tools/clippy/clippy_utils/src/diagnostics.rs @@ -46,7 +46,7 @@ fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) { /// | ^^^^^^^^^^^^^^^^^^^^^^^ /// ``` pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into, msg: &str) { - cx.struct_span_lint(lint, sp, msg, |diag| { + cx.struct_span_lint(lint, sp, msg.to_string(), |diag| { docs_link(diag, lint); diag }); @@ -80,11 +80,12 @@ pub fn span_lint_and_help( help_span: Option, help: &str, ) { - cx.struct_span_lint(lint, span, msg, |diag| { + cx.struct_span_lint(lint, span, msg.to_string(), |diag| { + let help = help.to_string(); if let Some(help_span) = help_span { - diag.span_help(help_span, help); + diag.span_help(help_span, help.to_string()); } else { - diag.help(help); + diag.help(help.to_string()); } docs_link(diag, lint); diag @@ -122,7 +123,8 @@ pub fn span_lint_and_note( note_span: Option, note: &str, ) { - cx.struct_span_lint(lint, span, msg, |diag| { + cx.struct_span_lint(lint, span, msg.to_string(), |diag| { + let note = note.to_string(); if let Some(note_span) = note_span { diag.span_note(note_span, note); } else { @@ -143,7 +145,7 @@ where S: Into, F: FnOnce(&mut Diagnostic), { - cx.struct_span_lint(lint, sp, msg, |diag| { + cx.struct_span_lint(lint, sp, msg.to_string(), |diag| { f(diag); docs_link(diag, lint); diag @@ -151,7 +153,7 @@ where } pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) { - cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |diag| { + cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg.to_string(), |diag| { docs_link(diag, lint); diag }); @@ -165,7 +167,7 @@ pub fn span_lint_hir_and_then( msg: &str, f: impl FnOnce(&mut Diagnostic), ) { - cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |diag| { + cx.tcx.struct_span_lint_hir(lint, hir_id, sp, msg.to_string(), |diag| { f(diag); docs_link(diag, lint); diag @@ -202,7 +204,7 @@ pub fn span_lint_and_sugg( applicability: Applicability, ) { span_lint_and_then(cx, lint, sp, msg, |diag| { - diag.span_suggestion(sp, help, sugg, applicability); + diag.span_suggestion(sp, help.to_string(), sugg, applicability); }); } @@ -232,5 +234,5 @@ pub fn multispan_sugg_with_applicability( ) where I: IntoIterator, { - diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability); + diag.multipart_suggestion(help_msg.to_string(), sugg.into_iter().collect(), applicability); } diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs index 3df40942e..4a845ca63 100644 --- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs +++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs @@ -15,7 +15,8 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, PredicateKind}; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; use rustc_span::{sym, Symbol}; use std::cmp; use std::ops; @@ -73,7 +74,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: .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() { - PredicateKind::Clause(ty::Clause::Trait(pred)) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker, + 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(_))) @@ -114,6 +115,20 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS if self.eagerness == ForceNoChange { return; } + + // Autoderef through a user-defined `Deref` impl can have side-effects, + // so don't suggest changing it. + if self + .cx + .typeck_results() + .expr_adjustments(e) + .iter() + .any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) + { + self.eagerness |= NoChange; + return; + } + match e.kind { ExprKind::Call( &Expr { @@ -173,11 +188,15 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS self.eagerness = Lazy; } }, - + // Custom `Deref` impl might have side effects + ExprKind::Unary(UnOp::Deref, e) + if self.cx.typeck_results().expr_ty(e).builtin_deref(true).is_none() => + { + self.eagerness |= NoChange; + }, // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe. ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (), ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange, - ExprKind::Unary(_, e) if matches!( self.cx.typeck_results().expr_ty(e).kind(), @@ -191,6 +210,7 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS ExprKind::Break(..) | ExprKind::Continue(_) | ExprKind::Ret(_) + | ExprKind::Become(_) | ExprKind::InlineAsm(_) | ExprKind::Yield(..) | ExprKind::Err(_) => { diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index a49246a78..3e1d73564 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -845,6 +845,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(e); } }, + ExprKind::Become(f) => { + self.hash_expr(f); + }, ExprKind::Path(ref qpath) => { self.hash_qpath(qpath); }, diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 8c883445a..727b59f1f 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -20,6 +20,7 @@ extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_attr; +extern crate rustc_const_eval; extern crate rustc_data_structures; // The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate. #[allow(unused_extern_crates)] @@ -326,6 +327,18 @@ pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) .map_or(false, |did| is_diag_trait_item(cx, did, diag_item)) } +/// Checks if the `def_id` belongs to a function that is part of a trait impl. +pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { + if let Some(hir_id) = cx.tcx.opt_local_def_id_to_hir_id(def_id) + && let Node::Item(item) = cx.tcx.hir().get_parent(hir_id) + && let ItemKind::Impl(imp) = item.kind + { + imp.of_trait.is_some() + } else { + false + } +} + /// Checks if the given expression is a path referring an item on the trait /// that is marked with the given diagnostic item. /// @@ -1498,7 +1511,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti && let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx) && let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree())) && let min_const_kind = ConstantKind::from_value(const_val, bnd_ty) - && let Some(min_const) = miri_to_const(cx.tcx, min_const_kind) + && let Some(min_const) = miri_to_const(cx, min_const_kind) && let Some(start_const) = constant(cx, cx.typeck_results(), start) { start_const == min_const @@ -1514,7 +1527,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti && let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx) && let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree())) && let max_const_kind = ConstantKind::from_value(const_val, bnd_ty) - && let Some(max_const) = miri_to_const(cx.tcx, max_const_kind) + && let Some(max_const) = miri_to_const(cx, max_const_kind) && let Some(end_const) = constant(cx, cx.typeck_results(), end) { end_const == max_const @@ -2396,7 +2409,7 @@ fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalDefId, f: impl Fn(&[Symbol /// Checks if the function containing the given `HirId` is a `#[test]` function /// -/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function +/// Note: Add `//@compile-flags: --test` to UI tests with a `#[test]` function pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { with_test_item_names(tcx, tcx.parent_module(id), |names| { tcx.hir() @@ -2418,7 +2431,7 @@ pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { /// Checks if the item containing the given `HirId` has `#[cfg(test)]` attribute applied /// -/// Note: Add `// compile-flags: --test` to UI tests with a `#[cfg(test)]` function +/// Note: Add `//@compile-flags: --test` to UI tests with a `#[cfg(test)]` function pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { fn is_cfg_test(attr: &Attribute) -> bool { if attr.has_name(sym::cfg) @@ -2440,7 +2453,7 @@ pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { /// Checks whether item either has `test` attribute applied, or /// is a module with `test` in its name. /// -/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function +/// Note: Add `//@compile-flags: --test` to UI tests with a `#[test]` function pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { is_in_test_function(tcx, item.hir_id()) || matches!(item.kind, ItemKind::Mod(..)) diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index e4a4936ff..00f3abaec 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -9,7 +9,7 @@ use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; use rustc_lint::LateContext; use rustc_span::def_id::DefId; use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; -use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol}; +use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol}; use std::cell::RefCell; use std::ops::ControlFlow; use std::sync::atomic::{AtomicBool, Ordering}; @@ -415,8 +415,18 @@ pub fn find_format_arg_expr<'hir, 'ast>( start: &'hir Expr<'hir>, target: &'ast FormatArgument, ) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> { + let SpanData { + lo, + hi, + ctxt, + parent: _, + } = target.expr.span.data(); + for_each_expr(start, |expr| { - if expr.span == target.expr.span { + // When incremental compilation is enabled spans gain a parent during AST to HIR lowering, + // since we're comparing an AST span to a HIR one we need to ignore the parent field + let data = expr.span.data(); + if data.lo == lo && data.hi == hi && data.ctxt == ctxt { ControlFlow::Break(expr) } else { ControlFlow::Continue(()) diff --git a/src/tools/clippy/clippy_utils/src/mir/mod.rs b/src/tools/clippy/clippy_utils/src/mir/mod.rs index 26c0015e8..131f3c0aa 100644 --- a/src/tools/clippy/clippy_utils/src/mir/mod.rs +++ b/src/tools/clippy/clippy_utils/src/mir/mod.rs @@ -101,21 +101,26 @@ pub fn used_exactly_once(mir: &rustc_middle::mir::Body<'_>, local: rustc_middle: /// Returns the `mir::Body` containing the node associated with `hir_id`. #[allow(clippy::module_name_repetitions)] -pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> &Body<'_> { +pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<&Body<'_>> { let body_owner_local_def_id = tcx.hir().enclosing_body_owner(hir_id); - tcx.optimized_mir(body_owner_local_def_id.to_def_id()) + if tcx.hir().body_owner_kind(body_owner_local_def_id).is_fn_or_closure() { + Some(tcx.optimized_mir(body_owner_local_def_id.to_def_id())) + } else { + None + } } /// Tries to determine the `Local` corresponding to `expr`, if any. /// This function is expensive and should be used sparingly. pub fn expr_local(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option { - let mir = enclosing_mir(tcx, expr.hir_id); - mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| { - if local_decl.source_info.span == expr.span { - Some(local) - } else { - None - } + enclosing_mir(tcx, expr.hir_id).and_then(|mir| { + mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| { + if local_decl.source_info.span == expr.span { + Some(local) + } else { + None + } + }) }) } diff --git a/src/tools/clippy/clippy_utils/src/msrvs.rs b/src/tools/clippy/clippy_utils/src/msrvs.rs index e05de2dc9..3637476c4 100644 --- a/src/tools/clippy/clippy_utils/src/msrvs.rs +++ b/src/tools/clippy/clippy_utils/src/msrvs.rs @@ -19,8 +19,10 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { + 1,71,0 { TUPLE_ARRAY_CONVERSIONS } + 1,70,0 { OPTION_IS_SOME_AND } 1,68,0 { PATH_MAIN_SEPARATOR_STR } - 1,65,0 { LET_ELSE } + 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY } 1,55,0 { SEEK_REWIND } @@ -28,7 +30,7 @@ msrv_aliases! { 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS } 1,50,0 { BOOL_THEN, CLAMP } - 1,47,0 { TAU, IS_ASCII_DIGIT_CONST } + 1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN } 1,46,0 { CONST_IF_MATCH } 1,45,0 { STR_STRIP_PREFIX } 1,43,0 { LOG2_10, LOG10_2 } @@ -42,11 +44,13 @@ msrv_aliases! { 1,34,0 { TRY_FROM } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,28,0 { FROM_BOOL } + 1,27,0 { ITERATOR_TRY_FOLD } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } 1,24,0 { IS_ASCII_DIGIT } 1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN } 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR } 1,16,0 { STR_REPEAT } + 1,15,0 { MAYBE_BOUND_IN_WHERE } } fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { diff --git a/src/tools/clippy/clippy_utils/src/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs index c225398ad..bbe4149fe 100644 --- a/src/tools/clippy/clippy_utils/src/numeric_literal.rs +++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs @@ -161,7 +161,7 @@ impl<'a> NumericLiteral<'a> { } if let Some((separator, exponent)) = self.exponent { - if exponent != "0" { + if !exponent.is_empty() && exponent != "0" { output.push_str(separator); Self::group_digits(&mut output, exponent, group_size, true, false); } diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index 0f0792fda..0e6f01287 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -15,7 +15,6 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [ ]; #[cfg(feature = "internal")] pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"]; -pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"]; pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "BTreeSet", "iter"]; @@ -93,7 +92,6 @@ pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"]; pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"]; pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"]; pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"]; -pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"]; pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"]; pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"]; pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"]; @@ -125,8 +123,6 @@ pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"]; pub const STR_BYTES: [&str; 4] = ["core", "str", "", "bytes"]; pub const STR_CHARS: [&str; 4] = ["core", "str", "", "chars"]; pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "", "ends_with"]; -pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"]; -pub const STR_FROM_UTF8_UNCHECKED: [&str; 4] = ["core", "str", "converts", "from_utf8_unchecked"]; pub const STR_LEN: [&str; 4] = ["core", "str", "", "len"]; pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "", "starts_with"]; #[cfg(feature = "internal")] @@ -163,3 +159,5 @@ pub const VEC_IS_EMPTY: [&str; 4] = ["alloc", "vec", "Vec", "is_empty"]; pub const VEC_POP: [&str; 4] = ["alloc", "vec", "Vec", "pop"]; 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"]; 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 c0d2c835d..fbf4ab272 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 @@ -4,52 +4,30 @@ // differ from the time of `rustc` even if the name stays the same. use crate::msrvs::Msrv; +use hir::LangItem; +use rustc_const_eval::transform::check_consts::ConstCx; use rustc_hir as hir; use rustc_hir::def_id::DefId; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::Obligation; 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::PointerCast, Ty, TyCtxt}; +use rustc_middle::ty::{self, adjustment::PointerCoercion, Ty, TyCtxt}; +use rustc_middle::ty::{BoundConstness, TraitRef}; use rustc_semver::RustcVersion; use rustc_span::symbol::sym; use rustc_span::Span; +use rustc_trait_selection::traits::{ObligationCtxt, SelectionContext}; use std::borrow::Cow; type McfResult = Result<(), (Span, Cow<'static, str>)>; pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) -> McfResult { let def_id = body.source.def_id(); - let mut current = def_id; - loop { - let predicates = tcx.predicates_of(current); - for (predicate, _) in predicates.predicates { - match predicate.kind().skip_binder() { - ty::PredicateKind::Clause( - ty::Clause::RegionOutlives(_) - | ty::Clause::TypeOutlives(_) - | ty::Clause::Projection(_) - | ty::Clause::Trait(..) - | ty::Clause::ConstArgHasType(..), - ) - | ty::PredicateKind::WellFormed(_) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue, - ty::PredicateKind::AliasRelate(..) => panic!("alias relate predicate on function: {predicate:#?}"), - ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {predicate:#?}"), - ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {predicate:#?}"), - ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {predicate:#?}"), - ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {predicate:#?}"), - ty::PredicateKind::Ambiguous => panic!("ambiguous predicate on function: {predicate:#?}"), - } - } - match predicates.parent { - Some(parent) => current = parent, - None => break, - } - } for local in &body.local_decls { check_ty(tcx, local.ty, local.source_info.span)?; @@ -61,7 +39,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) body.local_decls.iter().next().unwrap().source_info.span, )?; - for bb in body.basic_blocks.iter() { + for bb in &*body.basic_blocks { check_terminator(tcx, body, bb.terminator(), msrv)?; for stmt in &bb.statements { check_statement(tcx, body, def_id, stmt)?; @@ -89,7 +67,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult { return Err((span, "function pointers in const fn are unstable".into())); }, ty::Dynamic(preds, _, _) => { - for pred in preds.iter() { + for pred in *preds { match pred.skip_binder() { ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => { return Err(( @@ -141,18 +119,18 @@ fn check_rvalue<'tcx>( | CastKind::FloatToFloat | CastKind::FnPtrToPtr | CastKind::PtrToPtr - | CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), + | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer), operand, _, ) => check_operand(tcx, operand, span, body), Rvalue::Cast( - CastKind::Pointer( - PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer, + CastKind::PointerCoercion( + PointerCoercion::UnsafeFnPointer | PointerCoercion::ClosureFnPointer(_) | PointerCoercion::ReifyFnPointer, ), _, _, ) => Err((span, "function pointer casts are not allowed in const fn".into())), - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => { + Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), op, cast_ty) => { let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) { deref_ty.ty } else { @@ -256,7 +234,19 @@ fn check_statement<'tcx>( fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult { match operand { - Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body), + Operand::Move(place) => { + if !place.projection.as_ref().is_empty() + && !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body) + { + return Err(( + span, + "cannot drop locals with a non constant destructor in const fn".into(), + )); + } + + check_place(tcx, *place, span, body) + }, + Operand::Copy(place) => check_place(tcx, *place, span, body), Operand::Constant(c) => match c.check_static_ptr(tcx) { Some(_) => Err((span, "cannot access `static` items in const fn".into())), None => Ok(()), @@ -265,12 +255,10 @@ fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, b } fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult { - let mut cursor = place.projection.as_ref(); - while let [ref proj_base @ .., elem] = *cursor { - cursor = proj_base; + for (base, elem) in place.as_ref().iter_projections() { match elem { ProjectionElem::Field(..) => { - let base_ty = Place::ty_from(place.local, proj_base, body, tcx).ty; + let base_ty = base.ty(body, tcx).ty; if let Some(def) = base_ty.ty_adt_def() { // No union field accesses in `const fn` if def.is_union() { @@ -305,19 +293,23 @@ fn check_terminator<'tcx>( | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Unreachable => Ok(()), - - TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body), - + TerminatorKind::Drop { place, .. } => { + if !is_ty_const_destruct(tcx, place.ty(&body.local_decls, tcx).ty, body) { + return Err(( + span, + "cannot drop locals with a non constant destructor in const fn".into(), + )); + } + Ok(()) + }, TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body), - TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => { Err((span, "const fn generators are unstable".into())) }, - TerminatorKind::Call { func, args, - from_hir_call: _, + call_source: _, destination: _, target: _, unwind: _, @@ -357,7 +349,6 @@ fn check_terminator<'tcx>( Err((span, "can only call other const fns within const fn".into())) } }, - TerminatorKind::Assert { cond, expected: _, @@ -365,7 +356,6 @@ fn check_terminator<'tcx>( target: _, unwind: _, } => check_operand(tcx, cond, span, body), - TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())), } } @@ -379,8 +369,7 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool { // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262. // HACK(nilstrieb): CURRENT_RUSTC_VERSION can return versions like 1.66.0-dev. `rustc-semver` - // doesn't accept the `-dev` version number so we have to strip it - // off. + // doesn't accept the `-dev` version number so we have to strip it off. let short_version = since .as_str() .split('-') @@ -398,3 +387,35 @@ 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; + } + + 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), + ); + + 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(_) | 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() +} diff --git a/src/tools/clippy/clippy_utils/src/source.rs b/src/tools/clippy/clippy_utils/src/source.rs index 0f6029064..582337b47 100644 --- a/src/tools/clippy/clippy_utils/src/source.rs +++ b/src/tools/clippy/clippy_utils/src/source.rs @@ -4,7 +4,7 @@ use rustc_data_structures::sync::Lrc; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource}; use rustc_lint::{LateContext, LintContext}; use rustc_session::Session; use rustc_span::source_map::{original_sp, SourceMap}; @@ -71,11 +71,16 @@ pub fn expr_block( app: &mut Applicability, ) -> String { let (code, from_macro) = snippet_block_with_context(cx, expr.span, outer, default, indent_relative_to, app); - if from_macro { - format!("{{ {code} }}") - } else if let ExprKind::Block(_, _) = expr.kind { + if !from_macro && + let ExprKind::Block(block, _) = expr.kind && + block.rules != BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + { format!("{code}") } else { + // FIXME: add extra indent for the unsafe blocks: + // original code: unsafe { ... } + // result code: { unsafe { ... } } + // desired code: {\n unsafe { ... }\n} format!("{{ {code} }}") } } diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 14f7f0301..cf781e18c 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -147,6 +147,7 @@ impl<'a> Sugg<'a> { | hir::ExprKind::Path(..) | hir::ExprKind::Repeat(..) | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Tup(..) | hir::ExprKind::Err(_) => Sugg::NonParen(get_snippet(expr.span)), @@ -211,6 +212,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Path(..) | ast::ExprKind::Repeat(..) | ast::ExprKind::Ret(..) + | ast::ExprKind::Become(..) | ast::ExprKind::Yeet(..) | ast::ExprKind::FormatArgs(..) | ast::ExprKind::Struct(..) @@ -741,7 +743,7 @@ impl DiagnosticExt for rustc_errors::Diagnostic { if let Some(indent) = indentation(cx, item) { let span = item.with_hi(item.lo()); - self.span_suggestion(span, msg, format!("{attr}\n{indent}"), applicability); + self.span_suggestion(span, msg.to_string(), format!("{attr}\n{indent}"), applicability); } } @@ -762,7 +764,7 @@ impl DiagnosticExt for rustc_errors::Diagnostic { }) .collect::(); - self.span_suggestion(span, msg, format!("{new_item}\n{indent}"), applicability); + self.span_suggestion(span, msg.to_string(), format!("{new_item}\n{indent}"), applicability); } } @@ -779,7 +781,7 @@ impl DiagnosticExt for rustc_errors::Diagnostic { } } - self.span_suggestion(remove_span, msg, "", applicability); + self.span_suggestion(remove_span, msg.to_string(), "", applicability); } } @@ -942,7 +944,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { }, // item is used in a call // i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)` - ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, _, [call_args @ ..], _) => { + ExprKind::Call(_, call_args) | ExprKind::MethodCall(_, _, call_args, _) => { let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id); let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind(); diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 7b4ed77e8..d650cbe0b 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -17,8 +17,8 @@ use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::{ self, layout::ValidityRequirement, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, IntTy, List, ParamEnv, - Predicate, PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, - TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, + Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, + UintTy, VariantDef, VariantDiscr, }; use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_span::symbol::Ident; @@ -94,7 +94,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' match predicate.kind().skip_binder() { // For `impl Trait`, it will register a predicate of `T: Trait`, so we go through // and check substitutions to find `U`. - ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => { + ty::ClauseKind::Trait(trait_predicate) => { if trait_predicate .trait_ref .substs @@ -107,7 +107,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' }, // For `impl Trait`, it will register a predicate of `::Assoc = U`, // so we check the term for `U`. - ty::PredicateKind::Clause(ty::Clause::Projection(projection_predicate)) => { + ty::ClauseKind::Projection(projection_predicate) => { if let ty::TermKind::Ty(ty) = projection_predicate.term.unpack() { if contains_ty_adt_constructor_opaque_inner(cx, ty, needle, seen) { return true; @@ -268,7 +268,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty::Tuple(substs) => substs.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::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) = predicate.kind().skip_binder() { + if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() { if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) { return true; } @@ -277,7 +277,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { false }, ty::Dynamic(binder, _, _) => { - for predicate in binder.iter() { + for predicate in *binder { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) { return true; @@ -563,7 +563,7 @@ fn is_uninit_value_valid_for_ty_fallback<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'t } /// Gets an iterator over all predicates which apply to the given item. -pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator, Span)> { +pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator, Span)> { let mut next_id = Some(id); iter::from_fn(move || { next_id.take().map(|id| { @@ -665,7 +665,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option sig_from_bounds( cx, ty, - cx.tcx.item_bounds(def_id).subst(cx.tcx, substs), + cx.tcx.item_bounds(def_id).subst_iter(cx.tcx, substs), cx.tcx.opt_parent(def_id), ), ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)), @@ -698,7 +698,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option( cx: &LateContext<'tcx>, ty: Ty<'tcx>, - predicates: &'tcx [Predicate<'tcx>], + predicates: impl IntoIterator>, predicates_id: Option, ) -> Option> { let mut inputs = None; @@ -707,7 +707,7 @@ fn sig_from_bounds<'tcx>( for pred in predicates { match pred.kind().skip_binder() { - PredicateKind::Clause(ty::Clause::Trait(p)) + ty::ClauseKind::Trait(p) if (lang_items.fn_trait() == Some(p.def_id()) || lang_items.fn_mut_trait() == Some(p.def_id()) || lang_items.fn_once_trait() == Some(p.def_id())) @@ -720,7 +720,7 @@ fn sig_from_bounds<'tcx>( } inputs = Some(i); }, - PredicateKind::Clause(ty::Clause::Projection(p)) + ty::ClauseKind::Projection(p) if Some(p.projection_ty.def_id) == lang_items.fn_once_output() && p.projection_ty.self_ty() == ty => { if output.is_some() { @@ -747,7 +747,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option .subst_iter_copied(cx.tcx, ty.substs) { match pred.kind().skip_binder() { - PredicateKind::Clause(ty::Clause::Trait(p)) + ty::ClauseKind::Trait(p) if (lang_items.fn_trait() == Some(p.def_id()) || lang_items.fn_mut_trait() == Some(p.def_id()) || lang_items.fn_once_trait() == Some(p.def_id())) => @@ -760,9 +760,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: AliasTy<'tcx>) -> Option } inputs = Some(i); }, - PredicateKind::Clause(ty::Clause::Projection(p)) - if Some(p.projection_ty.def_id) == lang_items.fn_once_output() => - { + ty::ClauseKind::Projection(p) if Some(p.projection_ty.def_id) == lang_items.fn_once_output() => { if output.is_some() { // Multiple different fn trait impls. Is this even allowed? return None; @@ -937,7 +935,7 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option< } /// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`. -pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [Predicate<'_>]) -> bool { +pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [ty::Clause<'_>]) -> bool { let ty::Param(ty) = *ty.kind() else { return false; }; @@ -950,7 +948,7 @@ pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tc predicates .iter() .try_fold(false, |found, p| { - if let PredicateKind::Clause(ty::Clause::Trait(p)) = p.kind().skip_binder() + if let ty::ClauseKind::Trait(p) = p.kind().skip_binder() && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() && ty.index == self_ty.index { @@ -1126,7 +1124,7 @@ pub fn make_normalized_projection<'tcx>( ); return None; } - match tcx.try_normalize_erasing_regions(param_env, tcx.mk_projection(ty.def_id, ty.substs)) { + match tcx.try_normalize_erasing_regions(param_env, Ty::new_projection(tcx,ty.def_id, ty.substs)) { Ok(ty) => Some(ty), Err(e) => { debug_assert!(false, "failed to normalize type `{ty}`: {e:#?}"); @@ -1180,3 +1178,51 @@ pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { _ => false, } } + +pub fn make_normalized_projection_with_regions<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + container_id: DefId, + assoc_ty: Symbol, + substs: impl IntoIterator>>, +) -> Option> { + fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option> { + #[cfg(debug_assertions)] + if let Some((i, subst)) = ty + .substs + .iter() + .enumerate() + .find(|(_, subst)| subst.has_late_bound_regions()) + { + debug_assert!( + false, + "substs contain late-bound region at index `{i}` which can't be normalized.\n\ + use `TyCtxt::erase_late_bound_regions`\n\ + note: subst is `{subst:#?}`", + ); + return None; + } + let cause = rustc_middle::traits::ObligationCause::dummy(); + match tcx + .infer_ctxt() + .build() + .at(&cause, param_env) + .query_normalize(Ty::new_projection(tcx,ty.def_id, ty.substs)) + { + Ok(ty) => Some(ty.value), + Err(e) => { + debug_assert!(false, "failed to normalize type `{ty}`: {e:#?}"); + None + }, + } + } + helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?) +} + +pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let cause = rustc_middle::traits::ObligationCause::dummy(); + match tcx.infer_ctxt().build().at(&cause, param_env).query_normalize(ty) { + Ok(ty) => ty.value, + Err(_) => ty, + } +} diff --git a/src/tools/clippy/clippy_utils/src/usage.rs b/src/tools/clippy/clippy_utils/src/usage.rs index ab3976a13..985508521 100644 --- a/src/tools/clippy/clippy_utils/src/usage.rs +++ b/src/tools/clippy/clippy_utils/src/usage.rs @@ -82,7 +82,7 @@ pub struct ParamBindingIdCollector { impl<'tcx> ParamBindingIdCollector { fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec { let mut hir_ids: Vec = Vec::new(); - for param in body.params.iter() { + for param in body.params { let mut finder = ParamBindingIdCollector { binding_hir_ids: Vec::new(), }; diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index 5dcd71cef..8dafa723a 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -651,6 +651,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( // Either drops temporaries, jumps out of the current expression, or has no sub expression. ExprKind::DropTemps(_) | ExprKind::Ret(_) + | ExprKind::Become(_) | ExprKind::Break(..) | ExprKind::Yield(..) | ExprKind::Block(..) -- cgit v1.2.3