diff options
Diffstat (limited to 'clippy_lints/src')
24 files changed, 622 insertions, 76 deletions
diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 2a5be275615..a88f2b51c82 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -6,7 +6,11 @@ use clippy_utils::macros::{is_panic, macro_backtrace}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments}; use if_chain::if_chain; -use rustc_ast::{AttrKind, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem}; +use rustc_ast::token::{Token, TokenKind}; +use rustc_ast::tokenstream::TokenTree; +use rustc_ast::{ + AttrArgs, AttrArgsEq, AttrKind, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem, +}; use rustc_errors::Applicability; use rustc_hir::{ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, @@ -341,6 +345,41 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does + /// Checks for `#[should_panic]` attributes without specifying the expected panic message. + /// + /// ### Why is this bad? + /// The expected panic message should be specified to ensure that the test is actually + /// panicking with the expected message, and not another unrelated panic. + /// + /// ### Example + /// ```rust + /// fn random() -> i32 { 0 } + /// + /// #[should_panic] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// fn random() -> i32 { 0 } + /// + /// #[should_panic = "attempt to divide by zero"] + /// #[test] + /// fn my_test() { + /// let _ = 1 / random(); + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub SHOULD_PANIC_WITHOUT_EXPECT, + pedantic, + "ensures that all `should_panic` attributes specify its expected panic message" +} + +declare_clippy_lint! { + /// ### What it does /// Checks for `any` and `all` combinators in `cfg` with only one condition. /// /// ### Why is this bad? @@ -395,6 +434,7 @@ declare_lint_pass!(Attributes => [ DEPRECATED_SEMVER, USELESS_ATTRIBUTE, BLANKET_CLIPPY_RESTRICTION_LINTS, + SHOULD_PANIC_WITHOUT_EXPECT, ]); impl<'tcx> LateLintPass<'tcx> for Attributes { @@ -442,6 +482,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { } } } + if attr.has_name(sym::should_panic) { + check_should_panic_reason(cx, attr); + } } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { @@ -550,6 +593,35 @@ fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> { None } +fn check_should_panic_reason(cx: &LateContext<'_>, attr: &Attribute) { + if let AttrKind::Normal(normal_attr) = &attr.kind { + if let AttrArgs::Eq(_, AttrArgsEq::Hir(_)) = &normal_attr.item.args { + // `#[should_panic = ".."]` found, good + return; + } + + if let AttrArgs::Delimited(args) = &normal_attr.item.args + && let mut tt_iter = args.tokens.trees() + && let Some(TokenTree::Token(Token { kind: TokenKind::Ident(sym::expected, _), .. }, _)) = tt_iter.next() + && let Some(TokenTree::Token(Token { kind: TokenKind::Eq, .. }, _)) = tt_iter.next() + && let Some(TokenTree::Token(Token { kind: TokenKind::Literal(_), .. }, _)) = tt_iter.next() + { + // `#[should_panic(expected = "..")]` found, good + return; + } + + span_lint_and_sugg( + cx, + SHOULD_PANIC_WITHOUT_EXPECT, + attr.span, + "#[should_panic] attribute without a reason", + "consider specifying the expected panic", + r#"#[should_panic(expected = /* panic message */)]"#.into(), + Applicability::HasPlaceholders, + ); + } +} + fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) { for lint in items { if let Some(lint_name) = extract_clippy_lint(lint) { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index d34de305f5d..88ffbb55486 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -418,7 +418,7 @@ declare_clippy_lint! { /// let mut_ptr = ptr.cast_mut(); /// let ptr = mut_ptr.cast_const(); /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub PTR_CAST_CONSTNESS, pedantic, "casting using `as` from and to raw pointers to change constness when specialized methods apply" diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index db114abfc86..f73f1ed18f8 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -58,6 +58,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::attrs::MAYBE_MISUSED_CFG_INFO, crate::attrs::MISMATCHED_TARGET_OS_INFO, crate::attrs::NON_MINIMAL_CFG_INFO, + crate::attrs::SHOULD_PANIC_WITHOUT_EXPECT_INFO, crate::attrs::USELESS_ATTRIBUTE_INFO, crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO, crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO, @@ -208,6 +209,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::implicit_return::IMPLICIT_RETURN_INFO, crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO, crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO, + crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO, crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO, crate::incorrect_impls::INCORRECT_CLONE_IMPL_ON_COPY_TYPE_INFO, crate::incorrect_impls::INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE_INFO, @@ -578,6 +580,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::reference::DEREF_ADDROF_INFO, crate::regex::INVALID_REGEX_INFO, crate::regex::TRIVIAL_REGEX_INFO, + crate::reserve_after_initialization::RESERVE_AFTER_INITIALIZATION_INFO, crate::return_self_not_must_use::RETURN_SELF_NOT_MUST_USE_INFO, crate::returns::LET_AND_RETURN_INFO, crate::returns::NEEDLESS_RETURN_INFO, diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index dda14b4df53..affd0822120 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// let _x = 2i32.to_ne_bytes(); /// let _y = 2i64.to_ne_bytes(); /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub HOST_ENDIAN_BYTES, restriction, "disallows usage of the `to_ne_bytes` method" @@ -40,7 +40,7 @@ declare_clippy_lint! { /// let _x = 2i32.to_le_bytes(); /// let _y = 2i64.to_le_bytes(); /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub LITTLE_ENDIAN_BYTES, restriction, "disallows usage of the `to_le_bytes` method" @@ -59,7 +59,7 @@ declare_clippy_lint! { /// let _x = 2i32.to_be_bytes(); /// let _y = 2i64.to_be_bytes(); /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub BIG_ENDIAN_BYTES, restriction, "disallows usage of the `to_be_bytes` method" diff --git a/clippy_lints/src/excessive_nesting.rs b/clippy_lints/src/excessive_nesting.rs index 8911f1872c2..83480fc5eeb 100644 --- a/clippy_lints/src/excessive_nesting.rs +++ b/clippy_lints/src/excessive_nesting.rs @@ -56,7 +56,7 @@ declare_clippy_lint! { /// // lib.rs /// pub mod a; /// ``` - #[clippy::version = "1.70.0"] + #[clippy::version = "1.72.0"] pub EXCESSIVE_NESTING, complexity, "checks for blocks nested beyond a certain threshold" diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs new file mode 100644 index 00000000000..c6d1acad3a0 --- /dev/null +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -0,0 +1,202 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use rustc_errors::{Applicability, SuggestionStyle}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{ + Body, FnDecl, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, ItemKind, TraitBoundModifier, TraitItem, + TraitItemKind, TyKind, +}; +use rustc_hir_analysis::hir_ty_to_ty; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, ClauseKind, TyCtxt}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Looks for bounds in `impl Trait` in return position that are implied by other bounds. + /// This can happen when a trait is specified that another trait already has as a supertrait + /// (e.g. `fn() -> impl Deref + DerefMut<Target = i32>` has an unnecessary `Deref` bound, + /// because `Deref` is a supertrait of `DerefMut`) + /// + /// ### Why is this bad? + /// Specifying more bounds than necessary adds needless complexity for the reader. + /// + /// ### Limitations + /// This lint does not check for implied bounds transitively. Meaning that + /// it does't check for implied bounds from supertraits of supertraits + /// (e.g. `trait A {} trait B: A {} trait C: B {}`, then having an `fn() -> impl A + C`) + /// + /// ### Example + /// ```rust + /// # use std::ops::{Deref,DerefMut}; + /// fn f() -> impl Deref<Target = i32> + DerefMut<Target = i32> { + /// // ^^^^^^^^^^^^^^^^^^^ unnecessary bound, already implied by the `DerefMut` trait bound + /// Box::new(123) + /// } + /// ``` + /// Use instead: + /// ```rust + /// # use std::ops::{Deref,DerefMut}; + /// fn f() -> impl DerefMut<Target = i32> { + /// Box::new(123) + /// } + /// ``` + #[clippy::version = "1.73.0"] + pub IMPLIED_BOUNDS_IN_IMPLS, + complexity, + "specifying bounds that are implied by other bounds in `impl Trait` type" +} +declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]); + +/// This function tries to, for all type parameters in a supertype predicate `GenericTrait<U>`, +/// check if the substituted type in the implied-by bound matches with what's subtituted in the +/// implied type. +/// +/// Consider this example. +/// ```rust,ignore +/// trait GenericTrait<T> {} +/// trait GenericSubTrait<T, U, V>: GenericTrait<U> {} +/// ^ trait_predicate_args: [Self#0, U#2] +/// impl GenericTrait<i32> for () {} +/// impl GenericSubTrait<(), i32, ()> for () {} +/// impl GenericSubTrait<(), [u8; 8], ()> for () {} +/// +/// fn f() -> impl GenericTrait<i32> + GenericSubTrait<(), [u8; 8], ()> { +/// ^^^ implied_args ^^^^^^^^^^^^^^^ implied_by_args +/// (we are interested in `[u8; 8]` specifically, as that +/// is what `U` in `GenericTrait<U>` is substituted with) +/// () +/// } +/// ``` +/// Here i32 != [u8; 8], so this will return false. +fn is_same_generics( + tcx: TyCtxt<'_>, + trait_predicate_args: &[ty::GenericArg<'_>], + implied_by_args: &[GenericArg<'_>], + implied_args: &[GenericArg<'_>], +) -> bool { + trait_predicate_args + .iter() + .enumerate() + .skip(1) // skip `Self` implicit arg + .all(|(arg_index, arg)| { + if let Some(ty) = arg.as_type() + && let &ty::Param(ty::ParamTy{ index, .. }) = ty.kind() + // Since `trait_predicate_args` and type params in traits start with `Self=0` + // and generic argument lists `GenericTrait<i32>` don't have `Self`, + // we need to subtract 1 from the index. + && let GenericArg::Type(ty_a) = implied_by_args[index as usize - 1] + && let GenericArg::Type(ty_b) = implied_args[arg_index - 1] + { + hir_ty_to_ty(tcx, ty_a) == hir_ty_to_ty(tcx, ty_b) + } else { + false + } + }) +} + +fn check(cx: &LateContext<'_>, decl: &FnDecl<'_>) { + if let FnRetTy::Return(ty) = decl.output + &&let TyKind::OpaqueDef(item_id, ..) = ty.kind + && let item = cx.tcx.hir().item(item_id) + && let ItemKind::OpaqueTy(opaque_ty) = item.kind + // Very often there is only a single bound, e.g. `impl Deref<..>`, in which case + // we can avoid doing a bunch of stuff unnecessarily. + && opaque_ty.bounds.len() > 1 + { + // Get all the (implied) trait predicates in the bounds. + // For `impl Deref + DerefMut` this will contain [`Deref`]. + // The implied `Deref` comes from `DerefMut` because `trait DerefMut: Deref {}`. + // N.B. (G)ATs are fine to disregard, because they must be the same for all of its supertraits. + // Example: + // `impl Deref<Target = i32> + DerefMut<Target = u32>` is not allowed. + // `DerefMut::Target` needs to match `Deref::Target`. + let implied_bounds: Vec<_> = opaque_ty.bounds.iter().filter_map(|bound| { + if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound + && let [.., path] = poly_trait.trait_ref.path.segments + && poly_trait.bound_generic_params.is_empty() + && let Some(trait_def_id) = path.res.opt_def_id() + && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates + && !predicates.is_empty() // If the trait has no supertrait, there is nothing to add. + { + Some((bound.span(), path.args.map_or([].as_slice(), |a| a.args), predicates)) + } else { + None + } + }).collect(); + + // Lint all bounds in the `impl Trait` type that are also in the `implied_bounds` vec. + // This involves some extra logic when generic arguments are present, since + // simply comparing trait `DefId`s won't be enough. We also need to compare the generics. + for (index, bound) in opaque_ty.bounds.iter().enumerate() { + if let GenericBound::Trait(poly_trait, TraitBoundModifier::None) = bound + && let [.., path] = poly_trait.trait_ref.path.segments + && let implied_args = path.args.map_or([].as_slice(), |a| a.args) + && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id() + && let Some(implied_by_span) = implied_bounds.iter().find_map(|&(span, implied_by_args, preds)| { + preds.iter().find_map(|(clause, _)| { + if let ClauseKind::Trait(tr) = clause.kind().skip_binder() + && tr.def_id() == def_id + && is_same_generics(cx.tcx, tr.trait_ref.args, implied_by_args, implied_args) + { + Some(span) + } else { + None + } + }) + }) + { + let implied_by = snippet(cx, implied_by_span, ".."); + span_lint_and_then( + cx, IMPLIED_BOUNDS_IN_IMPLS, + poly_trait.span, + &format!("this bound is already specified as the supertrait of `{implied_by}`"), + |diag| { + // If we suggest removing a bound, we may also need extend the span + // to include the `+` token, so we don't end up with something like `impl + B` + + let implied_span_extended = if let Some(next_bound) = opaque_ty.bounds.get(index + 1) { + poly_trait.span.to(next_bound.span().shrink_to_lo()) + } else { + poly_trait.span + }; + + diag.span_suggestion_with_style( + implied_span_extended, + "try removing this bound", + "", + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways + ); + } + ); + } + } + } +} + +impl LateLintPass<'_> for ImpliedBoundsInImpls { + fn check_fn( + &mut self, + cx: &LateContext<'_>, + _: FnKind<'_>, + decl: &FnDecl<'_>, + _: &Body<'_>, + _: Span, + _: LocalDefId, + ) { + check(cx, decl); + } + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { + if let TraitItemKind::Fn(sig, ..) = &item.kind { + check(cx, sig.decl); + } + } + fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) { + if let ImplItemKind::Fn(sig, ..) = &item.kind { + check(cx, sig.decl); + } + } +} diff --git a/clippy_lints/src/incorrect_impls.rs b/clippy_lints/src/incorrect_impls.rs index 3c59b839a39..c19a46f4e97 100644 --- a/clippy_lints/src/incorrect_impls.rs +++ b/clippy_lints/src/incorrect_impls.rs @@ -5,6 +5,7 @@ use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_d use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp}; +use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::EarlyBinder; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -125,7 +126,7 @@ impl LateLintPass<'_> for IncorrectImpls { if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) { return; } - let ItemKind::Impl(_) = item.kind else { + let ItemKind::Impl(imp) = item.kind else { return; }; let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else { @@ -188,12 +189,7 @@ impl LateLintPass<'_> for IncorrectImpls { .diagnostic_items(trait_impl.def_id.krate) .name_to_id .get(&sym::Ord) - && implements_trait( - cx, - trait_impl.self_ty(), - *ord_def_id, - &[], - ) + && implements_trait(cx, hir_ty_to_ty(cx.tcx, imp.self_ty), *ord_def_id, &[]) { // If the `cmp` call likely needs to be fully qualified in the suggestion // (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't diff --git a/clippy_lints/src/large_stack_frames.rs b/clippy_lints/src/large_stack_frames.rs index 7aa1446d53d..1d28d7dd0e7 100644 --- a/clippy_lints/src/large_stack_frames.rs +++ b/clippy_lints/src/large_stack_frames.rs @@ -72,7 +72,7 @@ declare_clippy_lint! { /// // ... /// } /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub LARGE_STACK_FRAMES, nursery, "checks for functions that allocate a lot of stack space" diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 358004cf460..ab71bfbdc58 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -152,6 +152,7 @@ mod implicit_hasher; mod implicit_return; mod implicit_saturating_add; mod implicit_saturating_sub; +mod implied_bounds_in_impls; mod inconsistent_struct_constructor; mod incorrect_impls; mod index_refutable_slice; @@ -285,6 +286,7 @@ mod ref_option_ref; mod ref_patterns; mod reference; mod regex; +mod reserve_after_initialization; mod return_self_not_must_use; mod returns; mod same_name_method; @@ -1095,6 +1097,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: }); store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals)); store.register_late_pass(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns)); + store.register_late_pass(|_| Box::<reserve_after_initialization::ReserveAfterInitialization>::default()); + store.register_late_pass(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index 9b158f18f62..70c5182472a 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -36,7 +36,8 @@ struct PathAndSpan { span: Span, } -/// `MacroRefData` includes the name of the macro. +/// `MacroRefData` includes the name of the macro +/// and the path from `SourceMap::span_to_filename`. #[derive(Debug, Clone)] pub struct MacroRefData { name: String, diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 5259066eb71..1a69a48c582 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -12,13 +12,15 @@ use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::sym; -const ACCEPTABLE_METHODS: [&[&str]; 4] = [ +const ACCEPTABLE_METHODS: [&[&str]; 5] = [ + &paths::BINARYHEAP_ITER, &paths::HASHSET_ITER, &paths::BTREESET_ITER, &paths::SLICE_INTO, &paths::VEC_DEQUE_ITER, ]; -const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 6] = [ +const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [ + (sym::BinaryHeap, Some(msrvs::BINARY_HEAP_RETAIN)), (sym::BTreeSet, Some(msrvs::BTREE_SET_RETAIN)), (sym::BTreeMap, Some(msrvs::BTREE_MAP_RETAIN)), (sym::HashSet, Some(msrvs::HASH_SET_RETAIN)), diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 043425300d8..e91ce64d8c8 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -17,6 +17,7 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv let return_type = cx.typeck_results().expr_ty(expr); let input_type = cx.typeck_results().expr_ty(recv); let (input_type, ref_count) = peel_mid_ty_refs(input_type); + if !(ref_count > 0 && is_diag_trait_item(cx, method_def_id, sym::ToOwned)); if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did())); if return_type == input_type; if let Some(clone_trait) = cx.tcx.lang_items().clone_trait(); diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index 9f7ec19aa59..ee405a3e30c 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -1,21 +1,45 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{implements_trait, is_copy}; +use rustc_ast::BindingAnnotation; use rustc_errors::Applicability; -use rustc_hir::Expr; +use rustc_hir::{Body, Expr, ExprKind, HirId, HirIdSet, PatKind}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::mir::{FakeReadCause, Mutability}; +use rustc_middle::ty::{self, BorrowKind}; use rustc_span::sym; use super::ITER_OVEREAGER_CLONED; use crate::redundant_clone::REDUNDANT_CLONE; +use crate::rustc_trait_selection::infer::TyCtxtInferExt; + +#[derive(Clone, Copy)] +pub(super) enum Op<'a> { + // rm `.cloned()` + // e.g. `count` + RmCloned, + + // rm `.cloned()` + // e.g. `map` `for_each` + NeedlessMove(&'a str, &'a Expr<'a>), + + // later `.cloned()` + // and add `&` to the parameter of closure parameter + // e.g. `find` `filter` + FixClosure(&'a str, &'a Expr<'a>), + + // later `.cloned()` + // e.g. `skip` `take` + LaterCloned, +} pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, cloned_call: &'tcx Expr<'_>, cloned_recv: &'tcx Expr<'_>, - is_count: bool, + op: Op<'tcx>, needs_into_iter: bool, ) { let typeck = cx.typeck_results(); @@ -35,10 +59,47 @@ pub(super) fn check<'tcx>( return; } - let (lint, msg, trailing_clone) = if is_count { - (REDUNDANT_CLONE, "unneeded cloning of iterator items", "") - } else { - (ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()") + if let Op::NeedlessMove(_, expr) = op { + let rustc_hir::ExprKind::Closure(closure) = expr.kind else { return } ; + let body @ Body { params: [p], .. } = cx.tcx.hir().body(closure.body) else { return }; + let mut delegate = MoveDelegate {used_move : HirIdSet::default()}; + let infcx = cx.tcx.infer_ctxt().build(); + + ExprUseVisitor::new( + &mut delegate, + &infcx, + closure.body.hir_id.owner.def_id, + cx.param_env, + cx.typeck_results(), + ) + .consume_body(body); + + let mut to_be_discarded = false; + + p.pat.walk(|it| { + if delegate.used_move.contains(&it.hir_id){ + to_be_discarded = true; + return false; + } + + match it.kind { + PatKind::Binding(BindingAnnotation(_, Mutability::Mut), _, _, _) + | PatKind::Ref(_, Mutability::Mut) => { + to_be_discarded = true; + false + } + _ => { true } + } + }); + + if to_be_discarded { + return; + } + } + + let (lint, msg, trailing_clone) = match op { + Op::RmCloned | Op::NeedlessMove(_, _) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""), + Op::LaterCloned | Op::FixClosure(_, _) => (ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()"), }; span_lint_and_then( @@ -47,13 +108,54 @@ pub(super) fn check<'tcx>( expr.span, msg, |diag| { - let method_span = expr.span.with_lo(cloned_call.span.hi()); - if let Some(mut snip) = snippet_opt(cx, method_span) { - snip.push_str(trailing_clone); - let replace_span = expr.span.with_lo(cloned_recv.span.hi()); - diag.span_suggestion(replace_span, "try", snip, Applicability::MachineApplicable); + match op { + Op::RmCloned | Op::LaterCloned => { + let method_span = expr.span.with_lo(cloned_call.span.hi()); + if let Some(mut snip) = snippet_opt(cx, method_span) { + snip.push_str(trailing_clone); + let replace_span = expr.span.with_lo(cloned_recv.span.hi()); + diag.span_suggestion(replace_span, "try", snip, Applicability::MachineApplicable); + } + } + Op::FixClosure(name, predicate_expr) => { + if let Some(predicate) = snippet_opt(cx, predicate_expr.span) { + let new_closure = if let ExprKind::Closure(_) = predicate_expr.kind { + predicate.replacen('|', "|&", 1) + } else { + format!("|&x| {predicate}(x)") + }; + let snip = format!(".{name}({new_closure}).cloned()" ); + let replace_span = expr.span.with_lo(cloned_recv.span.hi()); + diag.span_suggestion(replace_span, "try", snip, Applicability::MachineApplicable); + } + } + Op::NeedlessMove(_, _) => { + let method_span = expr.span.with_lo(cloned_call.span.hi()); + if let Some(snip) = snippet_opt(cx, method_span) { + let replace_span = expr.span.with_lo(cloned_recv.span.hi()); + diag.span_suggestion(replace_span, "try", snip, Applicability::MaybeIncorrect); + } + } } } ); } } + +struct MoveDelegate { + used_move: HirIdSet, +} + +impl<'tcx> Delegate<'tcx> for MoveDelegate { + fn consume(&mut self, place_with_id: &PlaceWithHirId<'tcx>, _: HirId) { + if let PlaceBase::Local(l) = place_with_id.place.base { + self.used_move.insert(l); + } + } + + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: BorrowKind) {} + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 42756b27d01..5075137caa0 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -301,7 +301,7 @@ declare_clippy_lint! { /// let val2 = 1; /// let val3 = 1; /// ``` - #[clippy::version = "1.69.0"] + #[clippy::version = "1.72.0"] pub UNNECESSARY_LITERAL_UNWRAP, complexity, "using `unwrap()` related calls on `Result` and `Option` constructors" @@ -3328,7 +3328,7 @@ declare_clippy_lint! { /// mem::take(v) /// } /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub DRAIN_COLLECT, perf, "calling `.drain(..).collect()` to move all elements into a new collection" @@ -3919,7 +3919,7 @@ impl Methods { } }, ("count", []) if is_trait_method(cx, expr, sym::Iterator) => match method_call(recv) { - Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false), + Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::RmCloned , false), Some((name2 @ ("into_iter" | "iter" | "iter_mut"), recv2, [], _, _)) => { iter_count::check(cx, expr, recv2, name2); }, @@ -3973,6 +3973,13 @@ impl Methods { string_extend_chars::check(cx, expr, recv, arg); extend_with_drain::check(cx, expr, recv, arg); }, + (name @ ( "filter" | "find" ) , [arg]) => { + if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { + // if `arg` has side-effect, the semantic will change + iter_overeager_cloned::check(cx, expr, recv, recv2, + iter_overeager_cloned::Op::FixClosure(name, arg), false); + } + } ("filter_map", [arg]) => { unnecessary_filter_map::check(cx, expr, arg, name); filter_map_bool_then::check(cx, expr, arg, call_span); @@ -3987,16 +3994,18 @@ impl Methods { }, ("flatten", []) => match method_call(recv) { Some(("map", recv, [map_arg], map_span, _)) => map_flatten::check(cx, expr, recv, map_arg, map_span), - Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true), + Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::LaterCloned , true), _ => {}, }, ("fold", [init, acc]) => { manual_try_fold::check(cx, expr, init, acc, call_span, &self.msrv); unnecessary_fold::check(cx, expr, init, acc, span); }, - ("for_each", [_]) => { - if let Some(("inspect", _, [_], span2, _)) = method_call(recv) { - inspect_for_each::check(cx, expr, span2); + ("for_each", [arg]) => { + match method_call(recv) { + Some(("inspect", _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::NeedlessMove(name, arg), false), + _ => {} } }, ("get", [arg]) => { @@ -4021,7 +4030,8 @@ impl Methods { }, ("last", []) => { if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + iter_overeager_cloned::check(cx, expr, recv, recv2, + iter_overeager_cloned::Op::LaterCloned , false); } }, ("lock", []) => { @@ -4030,8 +4040,10 @@ impl Methods { (name @ ("map" | "map_err"), [m_arg]) => { if name == "map" { map_clone::check(cx, expr, recv, m_arg, &self.msrv); - if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) = method_call(recv) { - iter_kv_map::check(cx, map_name, expr, recv2, m_arg); + match method_call(recv) { + Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => iter_kv_map::check(cx, map_name, expr, recv2, m_arg), + Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::NeedlessMove(name, m_arg), false), + _ => {} } } else { map_err_ignore::check(cx, expr, m_arg); @@ -4058,7 +4070,7 @@ impl Methods { ("next", []) => { if let Some((name2, recv2, args2, _, _)) = method_call(recv) { match (name2, args2) { - ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), + ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::LaterCloned, false), ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg), ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, &self.msrv), ("iter", []) => iter_next_slice::check(cx, expr, recv2), @@ -4071,7 +4083,7 @@ impl Methods { }, ("nth", [n_arg]) => match method_call(recv) { Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg), - Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), + Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::LaterCloned , false), Some(("iter", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false), Some(("iter_mut", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true), _ => iter_nth_zero::check(cx, expr, recv, n_arg), @@ -4126,7 +4138,8 @@ impl Methods { iter_skip_zero::check(cx, expr, arg); if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + iter_overeager_cloned::check(cx, expr, recv, recv2, + iter_overeager_cloned::Op::LaterCloned , false); } } ("sort", []) => { @@ -4152,7 +4165,8 @@ impl Methods { ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), ("take", [_arg]) => { if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { - iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); + iter_overeager_cloned::check(cx, expr, recv, recv2, + iter_overeager_cloned::Op::LaterCloned, false); } }, ("take", []) => needless_option_take::check(cx, expr, recv), diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 8b2f57160af..942f3bd79a6 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -65,11 +65,26 @@ pub(super) fn check<'tcx>( }; let sugg = match (name, call_expr.is_some()) { - ("unwrap_or", true) | ("unwrap_or_else", false) => "unwrap_or_default", - ("or_insert", true) | ("or_insert_with", false) => "or_default", + ("unwrap_or", true) | ("unwrap_or_else", false) => sym!(unwrap_or_default), + ("or_insert", true) | ("or_insert_with", false) => sym!(or_default), _ => return false, }; + let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); + let has_suggested_method = receiver_ty.ty_adt_def().is_some_and(|adt_def| { + cx.tcx + .inherent_impls(adt_def.did()) + .iter() + .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) + .any(|assoc| { + assoc.fn_has_self_parameter + && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 + }) + }); + if !has_suggested_method { + return false; + } + // needs to target Default::default in particular or be *::new and have a Default impl // available if (is_new(fun) && output_type_implements_default(fun)) diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index e62a65a2712..79bff39379c 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -6,7 +6,8 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, GenericArgKind}; +use rustc_middle::ty; +use rustc_middle::ty::GenericArgKind; use rustc_span::sym; use rustc_span::symbol::Ident; use std::iter; diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index dea432fdbd5..9256119e6a4 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> { self.found = true; return; }, - ExprKind::If(..) => { - self.found = true; - return; - }, ExprKind::Path(_) => { if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) { if adj diff --git a/clippy_lints/src/needless_else.rs b/clippy_lints/src/needless_else.rs index 03bab86c6d7..0c1fe881fc1 100644 --- a/clippy_lints/src/needless_else.rs +++ b/clippy_lints/src/needless_else.rs @@ -27,7 +27,7 @@ declare_clippy_lint! { /// println!("Check successful!"); /// } /// ``` - #[clippy::version = "1.71.0"] + #[clippy::version = "1.72.0"] pub NEEDLESS_ELSE, style, "empty else branch" diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index cf7cd671dca..f7f9dccfbce 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -130,6 +130,11 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { } let generics_sugg = snippet(cx, generics.span, ""); + let where_clause_sugg = if generics.has_where_clause_predicates { + format!("\n{}\n", snippet(cx, generics.where_clause_span, "")) + } else { + String::new() + }; let self_ty_fmt = self_ty.to_string(); let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt); span_lint_hir_and_then( @@ -145,8 +150,12 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { cx, item.span, "try adding this", - &create_new_without_default_suggest_msg(&self_type_snip, &generics_sugg), - Applicability::MaybeIncorrect, + &create_new_without_default_suggest_msg( + &self_type_snip, + &generics_sugg, + &where_clause_sugg + ), + Applicability::MachineApplicable, ); }, ); @@ -159,10 +168,14 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { } } -fn create_new_without_default_suggest_msg(self_type_snip: &str, generics_sugg: &str) -> String { +fn create_new_without_default_suggest_msg( + self_type_snip: &str, + generics_sugg: &str, + where_clause_sugg: &str, +) -> String { #[rustfmt::skip] format!( -"impl{generics_sugg} Default for {self_type_snip} {{ +"impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{ fn default() -> Self {{ Self::new() }} diff --git a/clippy_lints/src/redundant_locals.rs b/clippy_lints/src/redundant_locals.rs index 0c89c7ee47d..a1ebba6a6a8 100644 --- a/clippy_lints/src/redundant_locals.rs +++ b/clippy_lints/src/redundant_locals.rs @@ -3,11 +3,9 @@ use clippy_utils::is_from_proc_macro; use clippy_utils::ty::needs_ordered_drop; use rustc_ast::Mutability; use rustc_hir::def::Res; -use rustc_hir::{ - BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath, -}; +use rustc_hir::{BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::{in_external_macro, is_from_async_await}; +use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Ident; use rustc_span::DesugaringKind; @@ -72,9 +70,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals { // the local is user-controlled if !in_external_macro(cx.sess(), local.span); if !is_from_proc_macro(cx, expr); - // Async function parameters are lowered into the closure body, so we can't lint them. - // see `lower_maybe_async_body` in `rust_ast_lowering` - if !is_from_async_await(local.span); then { span_lint_and_help( cx, @@ -111,12 +106,7 @@ fn affects_assignments(cx: &LateContext<'_>, mutability: Mutability, bind: HirId } /// Check if a rebinding of a local affects the code's drop behavior. -fn affects_drop_behavior<'tcx>( - cx: &LateContext<'tcx>, - bind: HirId, - rebind: HirId, - rebind_expr: &Expr<'tcx>, -) -> bool { +fn affects_drop_behavior<'tcx>(cx: &LateContext<'tcx>, bind: HirId, rebind: HirId, rebind_expr: &Expr<'tcx>) -> bool { let hir = cx.tcx.hir(); // the rebinding is in a different scope than the original binding diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 3e963d79892..1d4fdb43a0b 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// ```rust /// let foo = String::new(); /// ``` - #[clippy::version = "1.70.0"] + #[clippy::version = "1.72.0"] pub REDUNDANT_TYPE_ANNOTATIONS, restriction, "warns about needless / redundant type annotations." diff --git a/clippy_lints/src/reserve_after_initialization.rs b/clippy_lints/src/reserve_after_initialization.rs new file mode 100644 index 00000000000..0c8c904e374 --- /dev/null +++ b/clippy_lints/src/reserve_after_initialization.rs @@ -0,0 +1,134 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::higher::{get_vec_init_kind, VecInitKind}; +use clippy_utils::source::snippet; +use clippy_utils::{is_from_proc_macro, path_to_local_id}; +use rustc_errors::Applicability; +use rustc_hir::def::Res; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Informs the user about a more concise way to create a vector with a known capacity. + /// + /// ### Why is this bad? + /// The `Vec::with_capacity` constructor is less complex. + /// + /// ### Example + /// ```rust + /// let mut v: Vec<usize> = vec![]; + /// v.reserve(10); + /// ``` + /// Use instead: + /// ```rust + /// let mut v: Vec<usize> = Vec::with_capacity(10); + /// ``` + #[clippy::version = "1.73.0"] + pub RESERVE_AFTER_INITIALIZATION, + complexity, + "`reserve` called immediatly after `Vec` creation" +} +impl_lint_pass!(ReserveAfterInitialization => [RESERVE_AFTER_INITIALIZATION]); + +#[derive(Default)] +pub struct ReserveAfterInitialization { + searcher: Option<VecReserveSearcher>, +} + +struct VecReserveSearcher { + local_id: HirId, + err_span: Span, + init_part: String, + space_hint: String, +} +impl VecReserveSearcher { + fn display_err(&self, cx: &LateContext<'_>) { + if self.space_hint.is_empty() { + return; + } + + let s = format!("{}Vec::with_capacity({});", self.init_part, self.space_hint); + + span_lint_and_sugg( + cx, + RESERVE_AFTER_INITIALIZATION, + self.err_span, + "call to `reserve` immediately after creation", + "consider using `Vec::with_capacity(/* Space hint */)`", + s, + Applicability::HasPlaceholders, + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for ReserveAfterInitialization { + fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { + self.searcher = None; + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + if let Some(init_expr) = local.init + && let PatKind::Binding(BindingAnnotation::MUT, id, _, None) = local.pat.kind + && !in_external_macro(cx.sess(), local.span) + && let Some(init) = get_vec_init_kind(cx, init_expr) + && !matches!(init, VecInitKind::WithExprCapacity(_) | VecInitKind::WithConstCapacity(_)) + { + self.searcher = Some(VecReserveSearcher { + local_id: id, + err_span: local.span, + init_part: snippet(cx, local.span.shrink_to_lo() + .to(init_expr.span.source_callsite().shrink_to_lo()), "..") + .into_owned(), + space_hint: String::new() + }); + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if self.searcher.is_none() + && let ExprKind::Assign(left, right, _) = expr.kind + && let ExprKind::Path(QPath::Resolved(None, path)) = left.kind + && let Res::Local(id) = path.res + && !in_external_macro(cx.sess(), expr.span) + && let Some(init) = get_vec_init_kind(cx, right) + && !matches!(init, VecInitKind::WithExprCapacity(_) | VecInitKind::WithConstCapacity(_)) + { + self.searcher = Some(VecReserveSearcher { + local_id: id, + err_span: expr.span, + init_part: snippet(cx, left.span.shrink_to_lo() + .to(right.span.source_callsite().shrink_to_lo()), "..") + .into_owned(), // see `assign_expression` test + space_hint: String::new() + }); + } + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { + if let Some(searcher) = self.searcher.take() { + if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind + && let ExprKind::MethodCall(name, self_arg, [space_hint], _) = expr.kind + && path_to_local_id(self_arg, searcher.local_id) + && name.ident.as_str() == "reserve" + && !is_from_proc_macro(cx, expr) + { + self.searcher = Some(VecReserveSearcher { + err_span: searcher.err_span.to(stmt.span), + space_hint: snippet(cx, space_hint.span, "..").into_owned(), + .. searcher + }); + } else { + searcher.display_err(cx); + } + } + } + + fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) { + if let Some(searcher) = self.searcher.take() { + searcher.display_err(cx); + } + } +} diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index a37e2772d35..45433b2adf0 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. }) && let item = cx.tcx.hir().item(id) && let ItemKind::Impl(Impl { - items, - of_trait, - self_ty, - .. - }) = &item.kind + items, + of_trait, + self_ty, + .. + }) = &item.kind && let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind { if !map.contains_key(res) { diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs index 78ad52d8a87..c12519d723c 100644 --- a/clippy_lints/src/tuple_array_conversions.rs +++ b/clippy_lints/src/tuple_array_conversions.rs @@ -189,8 +189,8 @@ fn all_bindings_are_for_conv<'tcx>( tys.len() == elements.len() && tys.iter().chain(final_tys.iter().copied()).all_equal() }, (ToType::Tuple, ty::Array(ty, len)) => { - len.eval_target_usize(cx.tcx, cx.param_env) as usize == elements.len() - && final_tys.iter().chain(once(ty)).all_equal() + let Some(len) = len.try_eval_target_usize(cx.tcx, cx.param_env) else { return false }; + len as usize == elements.len() && final_tys.iter().chain(once(ty)).all_equal() }, _ => false, } |
