summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
commitc23a457e72abe608715ac76f076f47dc42af07a5 (patch)
tree2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /src/tools/clippy/clippy_lints
parentReleasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz
rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_lints')
-rw-r--r--src/tools/clippy/clippy_lints/Cargo.toml3
-rw-r--r--src/tools/clippy/clippy_lints/src/attrs.rs74
-rw-r--r--src/tools/clippy/clippy_lints/src/await_holding_invalid.rs43
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/mod.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs39
-rw-r--r--src/tools/clippy/clippy_lints/src/declared_lints.rs15
-rw-r--r--src/tools/clippy/clippy_lints/src/default_union_representation.rs25
-rw-r--r--src/tools/clippy/clippy_lints/src/dereference.rs404
-rw-r--r--src/tools/clippy/clippy_lints/src/derivable_impls.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/derive.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/disallowed_macros.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/doc.rs190
-rw-r--r--src/tools/clippy/clippy_lints/src/endian_bytes.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/enum_clike.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/enum_variants.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/error_impl_error.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/excessive_nesting.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/explicit_write.rs90
-rw-r--r--src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/format.rs14
-rw-r--r--src/tools/clippy/clippy_lints/src/format_args.rs20
-rw-r--r--src/tools/clippy/clippy_lints/src/format_impl.rs41
-rw-r--r--src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/ignored_unit_patterns.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs318
-rw-r--r--src/tools/clippy/clippy_lints/src/init_numbered_fields.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/large_const_arrays.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/large_futures.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/large_stack_frames.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/len_zero.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.rs36
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs17
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/mod.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/never_loop.rs268
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/utils.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_range_patterns.rs129
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_retain.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/needless_match.rs17
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/overlapping_arms.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs101
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs106
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs126
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/mod.rs189
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs19
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/path_ends_with_ext.rs53
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/redundant_as_str.rs34
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/misc.rs176
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs391
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs410
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_else.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs71
-rw-r--r--src/tools/clippy/clippy_lints/src/new_without_default.rs21
-rw-r--r--src/tools/clippy/clippy_lints/src/no_effect.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/non_canonical_impls.rs (renamed from src/tools/clippy/clippy_lints/src/incorrect_impls.rs)63
-rw-r--r--src/tools/clippy/clippy_lints/src/non_copy_const.rs21
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs63
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/float_cmp.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/ptr.rs46
-rw-r--r--src/tools/clippy/clippy_lints/src/raw_strings.rs96
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_closure_call.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_type_annotations.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/renamed_lints.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/reserve_after_initialization.rs134
-rw-r--r--src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/std_instead_of_core.rs26
-rw-r--r--src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/transmute_null_to_fn.rs30
-rw-r--r--src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/types/mod.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs209
-rw-r--r--src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs93
-rw-r--r--src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/unwrap.rs111
-rw-r--r--src/tools/clippy/clippy_lints/src/useless_conversion.rs110
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/conf.rs29
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/invalid_paths.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs37
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs15
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/write.rs10
94 files changed, 3386 insertions, 1493 deletions
diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml
index 11136867f..dcd9a4adc 100644
--- a/src/tools/clippy/clippy_lints/Cargo.toml
+++ b/src/tools/clippy/clippy_lints/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
-version = "0.1.73"
+version = "0.1.74"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"
@@ -15,7 +15,6 @@ clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
if_chain = "1.0"
itertools = "0.10.1"
-pulldown-cmark = { version = "0.9", default-features = false }
quine-mc_cluskey = "0.2"
regex-syntax = "0.7"
serde = { version = "1.0", features = ["derive"] }
diff --git a/src/tools/clippy/clippy_lints/src/attrs.rs b/src/tools/clippy/clippy_lints/src/attrs.rs
index 2a5be2756..0546807ba 100644
--- a/src/tools/clippy/clippy_lints/src/attrs.rs
+++ b/src/tools/clippy/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",
+ "#[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/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs
index d40a38543..7dd808a7b 100644
--- a/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs
+++ b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs
@@ -2,9 +2,9 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
-use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
+use rustc_hir::{AsyncGeneratorKind, Body, GeneratorKind};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::GeneratorInteriorTypeCause;
+use rustc_middle::mir::GeneratorLayout;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
@@ -197,28 +197,35 @@ impl LateLintPass<'_> for AwaitHolding {
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
use AsyncGeneratorKind::{Block, Closure, Fn};
if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
- let body_id = BodyId {
- hir_id: body.value.hir_id,
- };
- let typeck_results = cx.tcx.typeck_body(body_id);
- self.check_interior_types(
- cx,
- typeck_results.generator_interior_types.as_ref().skip_binder(),
- body.value.span,
- );
+ let def_id = cx.tcx.hir().body_owner_def_id(body.id());
+ if let Some(generator_layout) = cx.tcx.mir_generator_witnesses(def_id) {
+ self.check_interior_types(cx, generator_layout);
+ }
}
}
}
impl AwaitHolding {
- fn check_interior_types(&self, cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
- for ty_cause in ty_causes {
+ fn check_interior_types(&self, cx: &LateContext<'_>, generator: &GeneratorLayout<'_>) {
+ for (ty_index, ty_cause) in generator.field_tys.iter_enumerated() {
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
+ let await_points = || {
+ generator
+ .variant_source_info
+ .iter_enumerated()
+ .filter_map(|(variant, source_info)| {
+ generator.variant_fields[variant]
+ .raw
+ .contains(&ty_index)
+ .then_some(source_info.span)
+ })
+ .collect::<Vec<_>>()
+ };
if is_mutex_guard(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_LOCK,
- ty_cause.span,
+ ty_cause.source_info.span,
"this `MutexGuard` is held across an `await` point",
|diag| {
diag.help(
@@ -226,7 +233,7 @@ impl AwaitHolding {
`MutexGuard` is dropped before calling await",
);
diag.span_note(
- ty_cause.scope_span.unwrap_or(span),
+ await_points(),
"these are all the `await` points this lock is held through",
);
},
@@ -235,18 +242,18 @@ impl AwaitHolding {
span_lint_and_then(
cx,
AWAIT_HOLDING_REFCELL_REF,
- ty_cause.span,
+ ty_cause.source_info.span,
"this `RefCell` reference is held across an `await` point",
|diag| {
diag.help("ensure the reference is dropped before calling `await`");
diag.span_note(
- ty_cause.scope_span.unwrap_or(span),
+ await_points(),
"these are all the `await` points this reference is held through",
);
},
);
} else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
- emit_invalid_type(cx, ty_cause.span, disallowed);
+ emit_invalid_type(cx, ty_cause.source_info.span, disallowed);
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs b/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs
index cf07e050c..c586b572b 100644
--- a/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs
@@ -25,7 +25,7 @@ pub(super) fn check(
// The suggestion is to use a function call, so if the original expression
// has parens on the outside, they are no longer needed.
let mut applicability = Applicability::MachineApplicable;
- let opt = snippet_opt(cx, cast_op.span);
+ let opt = snippet_opt(cx, cast_op.span.source_callsite());
let sugg = opt.as_ref().map_or_else(
|| {
applicability = Applicability::HasPlaceholders;
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
index 84b99ad5c..f99a51e2b 100644
--- a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
@@ -44,7 +44,7 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
.unwrap_or(u64::max_value())
.min(apply_reductions(cx, nbits, left, signed)),
BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
- .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
+ .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).unwrap_or_default())),
_ => nbits,
},
ExprKind::MethodCall(method, left, [right], _) => {
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs
index 5bf467efa..1de691221 100644
--- a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs
@@ -5,6 +5,7 @@ use rustc_hir::{Expr, ExprKind, GenericArg};
use rustc_lint::LateContext;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, Ty};
+use rustc_span::sym;
use super::CAST_PTR_ALIGNMENT;
@@ -76,13 +77,14 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => {
static PATHS: &[&[&str]] = &[
paths::PTR_READ_UNALIGNED.as_slice(),
- paths::PTR_WRITE_UNALIGNED.as_slice(),
paths::PTR_UNALIGNED_VOLATILE_LOAD.as_slice(),
paths::PTR_UNALIGNED_VOLATILE_STORE.as_slice(),
];
+
if let ExprKind::Path(path) = &func.kind
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
- && match_any_def_paths(cx, def_id, PATHS).is_some()
+ && (match_any_def_paths(cx, def_id, PATHS).is_some()
+ || cx.tcx.is_diagnostic_item(sym::ptr_write_unaligned, def_id))
{
true
} else {
diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs
index d34de305f..b00130ffd 100644
--- a/src/tools/clippy/clippy_lints/src/casts/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs
@@ -20,6 +20,7 @@ mod ptr_as_ptr;
mod ptr_cast_constness;
mod unnecessary_cast;
mod utils;
+mod zero_ptr;
use clippy_utils::is_hir_ty_cfg_dependant;
use clippy_utils::msrvs::{self, Msrv};
@@ -418,7 +419,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"
@@ -665,6 +666,29 @@ declare_clippy_lint! {
"casting a known floating-point NaN into an integer"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Catch casts from `0` to some pointer type
+ ///
+ /// ### Why is this bad?
+ /// This generally means `null` and is better expressed as
+ /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = 0 as *const u32;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let a = std::ptr::null::<u32>();
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub ZERO_PTR,
+ style,
+ "using `0 as *{const, mut} T`"
+}
+
pub struct Casts {
msrv: Msrv,
}
@@ -699,6 +723,7 @@ impl_lint_pass!(Casts => [
CAST_SLICE_FROM_RAW_PARTS,
AS_PTR_CAST_MUT,
CAST_NAN_TO_INT,
+ ZERO_PTR,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -729,6 +754,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
+ zero_ptr::check(cx, expr, cast_expr, cast_to_hir);
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir.span);
diff --git a/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs
new file mode 100644
index 000000000..5071af5ec
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/casts/zero_ptr.rs
@@ -0,0 +1,39 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::{in_constant, is_integer_literal, std_or_core};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, Mutability, Ty, TyKind};
+use rustc_lint::LateContext;
+
+use super::ZERO_PTR;
+
+pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
+ if let TyKind::Ptr(ref mut_ty) = to.kind
+ && is_integer_literal(from, 0)
+ && !in_constant(cx, from.hir_id)
+ && let Some(std_or_core) = std_or_core(cx)
+ {
+ let (msg, sugg_fn) = match mut_ty.mutbl {
+ Mutability::Mut => ("`0 as *mut _` detected", "ptr::null_mut"),
+ Mutability::Not => ("`0 as *const _` detected", "ptr::null"),
+ };
+
+ let sugg = if let TyKind::Infer = mut_ty.ty.kind {
+ format!("{std_or_core}::{sugg_fn}()")
+ } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
+ format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()")
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ ZERO_PTR,
+ expr.span,
+ msg,
+ "try",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs
index db114abfc..4d1281ec1 100644
--- a/src/tools/clippy/clippy_lints/src/declared_lints.rs
+++ b/src/tools/clippy/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,
@@ -96,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::UNNECESSARY_CAST_INFO,
+ crate::casts::ZERO_PTR_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
@@ -208,9 +210,8 @@ 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,
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
crate::indexing_slicing::INDEXING_SLICING_INFO,
crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO,
@@ -363,6 +364,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::ITER_NTH_ZERO_INFO,
crate::methods::ITER_ON_EMPTY_COLLECTIONS_INFO,
crate::methods::ITER_ON_SINGLE_ITEMS_INFO,
+ crate::methods::ITER_OUT_OF_BOUNDS_INFO,
crate::methods::ITER_OVEREAGER_CLONED_INFO,
crate::methods::ITER_SKIP_NEXT_INFO,
crate::methods::ITER_SKIP_ZERO_INFO,
@@ -398,9 +400,11 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::OR_FUN_CALL_INFO,
crate::methods::OR_THEN_UNWRAP_INFO,
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
+ crate::methods::PATH_ENDS_WITH_EXT_INFO,
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
crate::methods::READONLY_WRITE_LOCK_INFO,
crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
+ crate::methods::REDUNDANT_AS_STR_INFO,
crate::methods::REPEAT_ONCE_INFO,
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
crate::methods::SEARCH_IS_SOME_INFO,
@@ -440,7 +444,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::misc::SHORT_CIRCUIT_STATEMENT_INFO,
crate::misc::TOPLEVEL_REF_ARG_INFO,
crate::misc::USED_UNDERSCORE_BINDING_INFO,
- crate::misc::ZERO_PTR_INFO,
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
crate::misc_early::DOUBLE_NEG_INFO,
crate::misc_early::DUPLICATE_UNDERSCORE_ARGUMENT_INFO,
@@ -454,6 +457,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::misc_early::ZERO_PREFIXED_LITERAL_INFO,
crate::mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER_INFO,
crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO,
+ crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO,
crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO,
crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO,
crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO,
@@ -477,6 +481,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::needless_bool::NEEDLESS_BOOL_INFO,
crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO,
crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO,
+ crate::needless_borrows_for_generic_args::NEEDLESS_BORROWS_FOR_GENERIC_ARGS_INFO,
crate::needless_continue::NEEDLESS_CONTINUE_INFO,
crate::needless_else::NEEDLESS_ELSE_INFO,
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
@@ -494,6 +499,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::no_effect::NO_EFFECT_UNDERSCORE_BINDING_INFO,
crate::no_effect::UNNECESSARY_OPERATION_INFO,
crate::no_mangle_with_rust_abi::NO_MANGLE_WITH_RUST_ABI_INFO,
+ crate::non_canonical_impls::NON_CANONICAL_CLONE_IMPL_INFO,
+ crate::non_canonical_impls::NON_CANONICAL_PARTIAL_ORD_IMPL_INFO,
crate::non_copy_const::BORROW_INTERIOR_MUTABLE_CONST_INFO,
crate::non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST_INFO,
crate::non_expressive_names::JUST_UNDERSCORES_AND_DIGITS_INFO,
@@ -578,6 +585,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,
@@ -666,6 +674,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
+ crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,
diff --git a/src/tools/clippy/clippy_lints/src/default_union_representation.rs b/src/tools/clippy/clippy_lints/src/default_union_representation.rs
index 03b5a2d6d..63ec81950 100644
--- a/src/tools/clippy/clippy_lints/src/default_union_representation.rs
+++ b/src/tools/clippy/clippy_lints/src/default_union_representation.rs
@@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_help;
-use rustc_hir::{self as hir, HirId, Item, ItemKind};
-use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_hir::{HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, FieldDef, GenericArg, List};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
@@ -52,7 +52,10 @@ declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION])
impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
- if is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) {
+ if !item.span.from_expansion()
+ && is_union_with_two_non_zst_fields(cx, item)
+ && !has_c_repr_attr(cx, item.hir_id())
+ {
span_lint_and_help(
cx,
DEFAULT_UNION_REPRESENTATION,
@@ -69,19 +72,21 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
}
/// Returns true if the given item is a union with at least two non-ZST fields.
+/// (ZST fields having an arbitrary offset is completely inconsequential, and
+/// if there is only one field left after ignoring ZST fields then the offset
+/// of that field does not matter either.)
fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
- if let ItemKind::Union(data, _) = &item.kind {
- data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2
+ if let ItemKind::Union(..) = &item.kind
+ && let ty::Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
+ {
+ adt_def.all_fields().filter(|f| !is_zst(cx, f, args)).count() >= 2
} else {
false
}
}
-fn is_zst(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) -> bool {
- if hir_ty.span.from_expansion() {
- return false;
- }
- let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+fn is_zst<'tcx>(cx: &LateContext<'tcx>, field: &FieldDef, args: &'tcx List<GenericArg<'tcx>>) -> bool {
+ let ty = field.ty(cx.tcx, args);
if let Ok(layout) = cx.layout_of(ty) {
layout.is_zst()
} else {
diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs
index 58c278550..148773856 100644
--- a/src/tools/clippy/clippy_lints/src/dereference.rs
+++ b/src/tools/clippy/clippy_lints/src/dereference.rs
@@ -1,42 +1,24 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
-use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
-use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
-use clippy_utils::ty::{is_copy, peel_mid_ty_refs};
+use clippy_utils::ty::{implements_trait, peel_mid_ty_refs};
use clippy_utils::{
expr_use_ctxt, get_parent_expr, get_parent_node, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
};
-
-use hir::def::DefKind;
-use hir::MatchSource;
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap;
-use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch};
use rustc_errors::Applicability;
-use rustc_hir::def::Res;
-use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{
- self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind,
- Path, QPath, TyKind, UnOp,
+ self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
+ Pat, PatKind, Path, QPath, TyKind, UnOp,
};
-use rustc_index::bit_set::BitSet;
-use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::mir::{Rvalue, StatementKind};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
-use rustc_middle::ty::{
- self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamEnv, ParamTy, ProjectionPredicate, Ty,
- TyCtxt, TypeVisitableExt, TypeckResults,
-};
+use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, TypeVisitableExt, TypeckResults};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol};
-use rustc_trait_selection::infer::InferCtxtExt as _;
-use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
-use rustc_trait_selection::traits::{Obligation, ObligationCause};
-use std::collections::VecDeque;
declare_clippy_lint! {
/// ### What it does
@@ -184,24 +166,6 @@ pub struct Dereferencing<'tcx> {
///
/// e.g. `m!(x) | Foo::Bar(ref x)`
ref_locals: FxIndexMap<HirId, Option<RefPat>>,
-
- /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
- /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
- /// be moved.
- possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
-
- // `IntoIterator` for arrays requires Rust 1.53.
- msrv: Msrv,
-}
-
-impl<'tcx> Dereferencing<'tcx> {
- #[must_use]
- pub fn new(msrv: Msrv) -> Self {
- Self {
- msrv,
- ..Dereferencing::default()
- }
- }
}
#[derive(Debug)]
@@ -356,52 +320,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
));
},
(Some(use_cx), RefOp::AddrOf(mutability)) => {
- let defined_ty = use_cx.node.defined_ty(cx);
-
- // Check needless_borrow for generic arguments.
- if !use_cx.is_ty_unified
- && let Some(DefinedTy::Mir(ty)) = defined_ty
- && let ty::Param(ty) = *ty.value.skip_binder().kind()
- && let Some((hir_id, fn_id, i)) = match use_cx.node {
- ExprUseNode::MethodArg(_, _, 0) => None,
- ExprUseNode::MethodArg(hir_id, None, i) => {
- typeck.type_dependent_def_id(hir_id).map(|id| (hir_id, id, i))
- },
- ExprUseNode::FnArg(&Expr { kind: ExprKind::Path(ref p), hir_id, .. }, i)
- if !path_has_args(p) => match typeck.qpath_res(p, hir_id) {
- Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) => {
- Some((hir_id, id, i))
- },
- _ => None,
- },
- _ => None,
- } && let count = needless_borrow_generic_arg_count(
- cx,
- &mut self.possible_borrowers,
- fn_id,
- typeck.node_args(hir_id),
- i,
- ty,
- expr,
- &self.msrv,
- ) && count != 0
- {
- self.state = Some((
- State::DerefedBorrow(DerefedBorrow {
- count: count - 1,
- msg: "the borrowed expression implements the required traits",
- stability: TyCoercionStability::None,
- for_field_access: None,
- }),
- StateData {
- span: expr.span,
- hir_id: expr.hir_id,
- adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target),
- },
- ));
- return;
- }
-
// Find the number of times the borrow is auto-derefed.
let mut iter = use_cx.adjustments.iter();
let mut deref_count = 0usize;
@@ -420,7 +338,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
};
};
- let stability = defined_ty.map_or(TyCoercionStability::None, |ty| {
+ let stability = use_cx.node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| {
TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return())
});
let can_auto_borrow = match use_cx.node {
@@ -452,13 +370,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
// Trait methods taking `self`
arg_ty
} && impl_ty.is_ref()
- && cx.tcx.infer_ctxt().build()
- .type_implements_trait(
- trait_id,
- [impl_ty.into()].into_iter().chain(args.iter().copied()),
- cx.param_env,
- )
- .must_apply_modulo_regions()
+ && implements_trait(
+ cx,
+ impl_ty,
+ trait_id,
+ &args[..cx.tcx.generics_of(trait_id).params.len() - 1],
+ )
{
false
} else {
@@ -609,12 +526,14 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
adjusted_ty,
},
));
- } else if stability.is_deref_stable() {
+ } else if stability.is_deref_stable()
+ && let Some(parent) = get_parent_expr(cx, expr)
+ {
self.state = Some((
State::ExplicitDeref { mutability: None },
StateData {
- span: expr.span,
- hir_id: expr.hir_id,
+ span: parent.span,
+ hir_id: parent.hir_id,
adjusted_ty,
},
));
@@ -700,12 +619,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
}
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
- if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
- local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
- }) {
- self.possible_borrowers.pop();
- }
-
if Some(body.id()) == self.current_body {
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
let replacements = pat.replacements;
@@ -729,8 +642,6 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
self.current_body = None;
}
}
-
- extract_msrv_attr!(LateContext);
}
fn try_parse_ref_op<'tcx>(
@@ -788,13 +699,6 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
}
}
-fn path_has_args(p: &QPath<'_>) -> bool {
- match *p {
- QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(),
- _ => false,
- }
-}
-
fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
if let Some(parent) = get_parent_expr(cx, e)
&& parent.span.ctxt() == e.span.ctxt()
@@ -940,7 +844,6 @@ impl TyCoercionStability {
| ty::FnDef(..)
| ty::Generator(..)
| ty::GeneratorWitness(..)
- | ty::GeneratorWitnessMIR(..)
| ty::Closure(..)
| ty::Never
| ty::Tuple(_)
@@ -981,274 +884,6 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
v.0
}
-/// Checks for the number of borrow expressions which can be removed from the given expression
-/// where the expression is used as an argument to a function expecting a generic type.
-///
-/// The following constraints will be checked:
-/// * The borrowed expression meets all the generic type's constraints.
-/// * The generic type appears only once in the functions signature.
-/// * The borrowed value will not be moved if it is used later in the function.
-#[expect(clippy::too_many_arguments)]
-fn needless_borrow_generic_arg_count<'tcx>(
- cx: &LateContext<'tcx>,
- possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
- fn_id: DefId,
- callee_args: &'tcx List<GenericArg<'tcx>>,
- arg_index: usize,
- param_ty: ParamTy,
- mut expr: &Expr<'tcx>,
- msrv: &Msrv,
-) -> usize {
- let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
- let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
-
- let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder();
- let predicates = cx.tcx.param_env(fn_id).caller_bounds();
- let projection_predicates = predicates
- .iter()
- .filter_map(|predicate| {
- if let ClauseKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
- Some(projection_predicate)
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
-
- let mut trait_with_ref_mut_self_method = false;
-
- // If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return.
- if predicates
- .iter()
- .filter_map(|predicate| {
- if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
- && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
- {
- Some(trait_predicate.trait_ref.def_id)
- } else {
- None
- }
- })
- .inspect(|trait_def_id| {
- trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id);
- })
- .all(|trait_def_id| {
- Some(trait_def_id) == destruct_trait_def_id
- || Some(trait_def_id) == sized_trait_def_id
- || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
- })
- {
- return 0;
- }
-
- // See:
- // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
- // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
- if projection_predicates
- .iter()
- .any(|projection_predicate| is_mixed_projection_predicate(cx, fn_id, projection_predicate))
- {
- return 0;
- }
-
- // `args_with_referent_ty` can be constructed outside of `check_referent` because the same
- // elements are modified each time `check_referent` is called.
- let mut args_with_referent_ty = callee_args.to_vec();
-
- let mut check_reference_and_referent = |reference, referent| {
- let referent_ty = cx.typeck_results().expr_ty(referent);
-
- if !is_copy(cx, referent_ty)
- && (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
- || !referent_used_exactly_once(cx, possible_borrowers, reference))
- {
- return false;
- }
-
- // https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
- if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
- return false;
- }
-
- if !replace_types(
- cx,
- param_ty,
- referent_ty,
- fn_sig,
- arg_index,
- &projection_predicates,
- &mut args_with_referent_ty,
- ) {
- return false;
- }
-
- predicates.iter().all(|predicate| {
- if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
- && cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id)
- && let ty::Param(param_ty) = trait_predicate.self_ty().kind()
- && let GenericArgKind::Type(ty) = args_with_referent_ty[param_ty.index as usize].unpack()
- && ty.is_array()
- && !msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
- {
- return false;
- }
-
- let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty);
- let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate);
- let infcx = cx.tcx.infer_ctxt().build();
- infcx.predicate_must_hold_modulo_regions(&obligation)
- })
- };
-
- let mut count = 0;
- while let ExprKind::AddrOf(_, _, referent) = expr.kind {
- if !check_reference_and_referent(expr, referent) {
- break;
- }
- expr = referent;
- count += 1;
- }
- count
-}
-
-fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
- cx.tcx
- .associated_items(trait_def_id)
- .in_definition_order()
- .any(|assoc_item| {
- if assoc_item.fn_has_self_parameter {
- let self_ty = cx
- .tcx
- .fn_sig(assoc_item.def_id)
- .instantiate_identity()
- .skip_binder()
- .inputs()[0];
- matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut))
- } else {
- false
- }
- })
-}
-
-fn is_mixed_projection_predicate<'tcx>(
- cx: &LateContext<'tcx>,
- callee_def_id: DefId,
- projection_predicate: &ProjectionPredicate<'tcx>,
-) -> bool {
- let generics = cx.tcx.generics_of(callee_def_id);
- // The predicate requires the projected type to equal a type parameter from the parent context.
- if let Some(term_ty) = projection_predicate.term.ty()
- && let ty::Param(term_param_ty) = term_ty.kind()
- && (term_param_ty.index as usize) < generics.parent_count
- {
- // The inner-most self type is a type parameter from the current function.
- let mut projection_ty = projection_predicate.projection_ty;
- loop {
- match projection_ty.self_ty().kind() {
- ty::Alias(ty::Projection, inner_projection_ty) => {
- projection_ty = *inner_projection_ty;
- }
- ty::Param(param_ty) => {
- return (param_ty.index as usize) >= generics.parent_count;
- }
- _ => {
- return false;
- }
- }
- }
- } else {
- false
- }
-}
-
-fn referent_used_exactly_once<'tcx>(
- cx: &LateContext<'tcx>,
- possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
- reference: &Expr<'tcx>,
-) -> bool {
- if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id)
- && let Some(local) = expr_local(cx.tcx, reference)
- && let [location] = *local_assignments(mir, local).as_slice()
- && let Some(statement) = mir.basic_blocks[location.block].statements.get(location.statement_index)
- && let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
- && !place.is_indirect_first_projection()
- // Ensure not in a loop (https://github.com/rust-lang/rust-clippy/issues/9710)
- && TriColorDepthFirstSearch::new(&mir.basic_blocks).run_from(location.block, &mut CycleDetector).is_none()
- {
- let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
- if possible_borrowers
- .last()
- .map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
- {
- possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
- }
- let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
- // If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
- // that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
- // itself. See the comment in that method for an explanation as to why.
- possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
- && used_exactly_once(mir, place.local).unwrap_or(false)
- } else {
- false
- }
-}
-
-// Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting
-// projected type that is a type parameter. Returns `false` if replacing the types would have an
-// effect on the function signature beyond substituting `new_ty` for `param_ty`.
-// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
-fn replace_types<'tcx>(
- cx: &LateContext<'tcx>,
- param_ty: ParamTy,
- new_ty: Ty<'tcx>,
- fn_sig: FnSig<'tcx>,
- arg_index: usize,
- projection_predicates: &[ProjectionPredicate<'tcx>],
- args: &mut [ty::GenericArg<'tcx>],
-) -> bool {
- let mut replaced = BitSet::new_empty(args.len());
-
- let mut deque = VecDeque::with_capacity(args.len());
- deque.push_back((param_ty, new_ty));
-
- while let Some((param_ty, new_ty)) = deque.pop_front() {
- // If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
- if !fn_sig
- .inputs_and_output
- .iter()
- .enumerate()
- .all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx)))
- {
- return false;
- }
-
- args[param_ty.index as usize] = ty::GenericArg::from(new_ty);
-
- // The `replaced.insert(...)` check provides some protection against infinite loops.
- if replaced.insert(param_ty.index) {
- for projection_predicate in projection_predicates {
- if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
- && let Some(term_ty) = projection_predicate.term.ty()
- && let ty::Param(term_param_ty) = term_ty.kind()
- {
- let projection = cx.tcx.mk_ty_from_kind(ty::Alias(
- ty::Projection,
- projection_predicate.projection_ty.with_self_ty(cx.tcx, new_ty),
- ));
-
- if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection)
- && args[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty)
- {
- deque.push_back((*term_param_ty, projected_ty));
- }
- }
- }
- }
- }
-
- true
-}
-
fn ty_contains_field(ty: Ty<'_>, name: Symbol) -> bool {
if let ty::Adt(adt, _) = *ty.kind() {
adt.is_struct() && adt.all_fields().any(|f| f.name == name)
@@ -1399,6 +1034,13 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
return;
}
+ if let ExprKind::Field(parent_expr, _) = expr.kind
+ && let ty::Adt(adt, _) = cx.typeck_results().expr_ty(parent_expr).kind()
+ && adt.is_union()
+ {
+ // Auto deref does not apply on union field
+ return;
+ }
span_lint_hir_and_then(
cx,
EXPLICIT_AUTO_DEREF,
diff --git a/src/tools/clippy/clippy_lints/src/derivable_impls.rs b/src/tools/clippy/clippy_lints/src/derivable_impls.rs
index 9a85cc4ce..d2bfc4f8e 100644
--- a/src/tools/clippy/clippy_lints/src/derivable_impls.rs
+++ b/src/tools/clippy/clippy_lints/src/derivable_impls.rs
@@ -217,8 +217,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
if let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind();
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|attr| attr.doc_str().is_some());
- if let child_attrs = cx.tcx.hir().attrs(impl_item_hir);
- if !child_attrs.iter().any(|attr| attr.doc_str().is_some());
+ if cx.tcx.hir().attrs(impl_item_hir).is_empty();
then {
if adt_def.is_struct() {
diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs
index d3311792c..2bdac1352 100644
--- a/src/tools/clippy/clippy_lints/src/derive.rs
+++ b/src/tools/clippy/clippy_lints/src/derive.rs
@@ -343,7 +343,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
// If the current self type doesn't implement Copy (due to generic constraints), search to see if
// there's a Copy impl for any instance of the adt.
if !is_copy(cx, ty) {
- if ty_subs.non_erasable_generics().next().is_some() {
+ if ty_subs.non_erasable_generics(cx.tcx, ty_adt.did()).next().is_some() {
let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(&copy_id).map_or(false, |impls| {
impls.iter().any(|&id| {
matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _)
diff --git a/src/tools/clippy/clippy_lints/src/disallowed_macros.rs b/src/tools/clippy/clippy_lints/src/disallowed_macros.rs
index 1971cab64..7469f813e 100644
--- a/src/tools/clippy/clippy_lints/src/disallowed_macros.rs
+++ b/src/tools/clippy/clippy_lints/src/disallowed_macros.rs
@@ -1,8 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::macro_backtrace;
+use rustc_ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::DefIdMap;
-use rustc_hir::{Expr, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
+use rustc_hir::{Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, Pat, Path, Stmt, TraitItem, Ty};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{ExpnId, Span};
@@ -111,6 +112,10 @@ impl LateLintPass<'_> for DisallowedMacros {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
self.check(cx, expr.span);
+ // `$t + $t` can have the context of $t, check also the span of the binary operator
+ if let ExprKind::Binary(op, ..) = expr.kind {
+ self.check(cx, op.span);
+ }
}
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
@@ -147,4 +152,8 @@ impl LateLintPass<'_> for DisallowedMacros {
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) {
self.check(cx, path.span);
}
+
+ fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) {
+ self.check(cx, attr.span);
+ }
}
diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs
index e29ab634c..e789e0da6 100644
--- a/src/tools/clippy/clippy_lints/src/doc.rs
+++ b/src/tools/clippy/clippy_lints/src/doc.rs
@@ -1,18 +1,16 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_then};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
-use clippy_utils::source::{first_line_of_span, snippet_with_applicability};
+use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
use if_chain::if_chain;
-use itertools::Itertools;
use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
};
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
-use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind};
-use rustc_ast::token::CommentKind;
+use rustc_ast::ast::{Async, Attribute, Fn, FnRetTy, ItemKind};
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::Lrc;
use rustc_errors::emitter::EmitterWriter;
@@ -26,6 +24,9 @@ use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_parse::maybe_new_parser_from_source_str;
use rustc_parse::parser::ForceCollect;
+use rustc_resolve::rustdoc::{
+ add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, DocFragment,
+};
use rustc_session::parse::ParseSess;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::edition::Edition;
@@ -450,53 +451,16 @@ fn lint_for_missing_headers(
}
}
-/// Cleanup documentation decoration.
-///
-/// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
-/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
-/// need to keep track of
-/// the spans but this function is inspired from the later.
-#[expect(clippy::cast_possible_truncation)]
-#[must_use]
-pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) {
- // one-line comments lose their prefix
- if comment_kind == CommentKind::Line {
- let mut doc = doc.to_owned();
- doc.push('\n');
- let len = doc.len();
- // +3 skips the opening delimiter
- return (doc, vec![(len, span.with_lo(span.lo() + BytePos(3)))]);
- }
+#[derive(Copy, Clone)]
+struct Fragments<'a> {
+ doc: &'a str,
+ fragments: &'a [DocFragment],
+}
- let mut sizes = vec![];
- let mut contains_initial_stars = false;
- for line in doc.lines() {
- let offset = line.as_ptr() as usize - doc.as_ptr() as usize;
- debug_assert_eq!(offset as u32 as usize, offset);
- contains_initial_stars |= line.trim_start().starts_with('*');
- // +1 adds the newline, +3 skips the opening delimiter
- sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + offset as u32))));
- }
- if !contains_initial_stars {
- return (doc.to_string(), sizes);
- }
- // remove the initial '*'s if any
- let mut no_stars = String::with_capacity(doc.len());
- for line in doc.lines() {
- let mut chars = line.chars();
- for c in &mut chars {
- if c.is_whitespace() {
- no_stars.push(c);
- } else {
- no_stars.push(if c == '*' { ' ' } else { c });
- break;
- }
- }
- no_stars.push_str(chars.as_str());
- no_stars.push('\n');
+impl Fragments<'_> {
+ fn span(self, cx: &LateContext<'_>, range: Range<usize>) -> Option<Span> {
+ source_span_for_markdown_range(cx.tcx, self.doc, &range, self.fragments)
}
-
- (no_stars, sizes)
}
#[derive(Copy, Clone, Default)]
@@ -508,34 +472,23 @@ struct DocHeaders {
fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> {
/// We don't want the parser to choke on intra doc links. Since we don't
- /// actually care about rendering them, just pretend that all broken links are
+ /// actually care about rendering them, just pretend that all broken links
/// point to a fake address.
#[expect(clippy::unnecessary_wraps)] // we're following a type signature
fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
Some(("fake".into(), "fake".into()))
}
- let mut doc = String::new();
- let mut spans = vec![];
-
- for attr in attrs {
- if let AttrKind::DocComment(comment_kind, comment) = attr.kind {
- let (comment, current_spans) = strip_doc_comment_decoration(comment.as_str(), comment_kind, attr.span);
- spans.extend_from_slice(&current_spans);
- doc.push_str(&comment);
- } else if attr.has_name(sym::doc) {
- // ignore mix of sugared and non-sugared doc
- // don't trigger the safety or errors check
- return None;
- }
+ if is_doc_hidden(attrs) {
+ return None;
}
- let mut current = 0;
- for &mut (ref mut offset, _) in &mut spans {
- let offset_copy = *offset;
- *offset = current;
- current += offset_copy;
+ let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
+ let mut doc = String::new();
+ for fragment in &fragments {
+ add_doc_fragment(&mut doc, fragment);
}
+ doc.pop();
if doc.is_empty() {
return Some(DocHeaders::default());
@@ -543,32 +496,29 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
let mut cb = fake_broken_link_callback;
- let parser =
- pulldown_cmark::Parser::new_with_broken_link_callback(&doc, Options::empty(), Some(&mut cb)).into_offset_iter();
- // Iterate over all `Events` and combine consecutive events into one
- let events = parser.coalesce(|previous, current| {
- let previous_range = previous.1;
- let current_range = current.1;
-
- match (previous.0, current.0) {
- (Text(previous), Text(current)) => {
- let mut previous = previous.to_string();
- previous.push_str(&current);
- Ok((Text(previous.into()), previous_range))
- },
- (previous, current) => Err(((previous, previous_range), (current, current_range))),
- }
- });
- Some(check_doc(cx, valid_idents, events, &spans))
+ // disable smart punctuation to pick up ['link'] more easily
+ let opts = main_body_opts() - Options::ENABLE_SMART_PUNCTUATION;
+ let parser = pulldown_cmark::Parser::new_with_broken_link_callback(&doc, opts, Some(&mut cb));
+
+ Some(check_doc(
+ cx,
+ valid_idents,
+ parser.into_offset_iter(),
+ Fragments {
+ fragments: &fragments,
+ doc: &doc,
+ },
+ ))
}
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
+#[allow(clippy::too_many_lines)] // Only a big match statement
fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
cx: &LateContext<'_>,
valid_idents: &FxHashSet<String>,
events: Events,
- spans: &[(usize, Span)],
+ fragments: Fragments<'_>,
) -> DocHeaders {
// true if a safety header was found
let mut headers = DocHeaders::default();
@@ -579,8 +529,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut no_test = false;
let mut edition = None;
let mut ticks_unbalanced = false;
- let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
- let mut paragraph_span = spans.get(0).expect("function isn't called if doc comment is empty").1;
+ let mut text_to_check: Vec<(CowStr<'_>, Range<usize>)> = Vec::new();
+ let mut paragraph_range = 0..0;
for (event, range) in events {
match event {
Start(CodeBlock(ref kind)) => {
@@ -613,25 +563,28 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
in_heading = true;
}
ticks_unbalanced = false;
- let (_, span) = get_current_span(spans, range.start);
- paragraph_span = first_line_of_span(cx, span);
+ paragraph_range = range;
},
End(Heading(_, _, _) | Paragraph | Item) => {
if let End(Heading(_, _, _)) = event {
in_heading = false;
}
- if ticks_unbalanced {
+ if ticks_unbalanced
+ && let Some(span) = fragments.span(cx, paragraph_range.clone())
+ {
span_lint_and_help(
cx,
DOC_MARKDOWN,
- paragraph_span,
+ span,
"backticks are unbalanced",
None,
"a backtick may be missing a pair",
);
} else {
- for (text, span) in text_to_check {
- check_text(cx, valid_idents, &text, span);
+ for (text, range) in text_to_check {
+ if let Some(span) = fragments.span(cx, range) {
+ check_text(cx, valid_idents, &text, span);
+ }
}
}
text_to_check = Vec::new();
@@ -640,8 +593,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
Html(_html) => (), // HTML is weird, just ignore it
SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
FootnoteReference(text) | Text(text) => {
- let (begin, span) = get_current_span(spans, range.start);
- paragraph_span = paragraph_span.with_hi(span.hi());
+ paragraph_range.end = range.end;
ticks_unbalanced |= text.contains('`') && !in_code;
if Some(&text) == in_link.as_ref() || ticks_unbalanced {
// Probably a link of the form `<http://example.com>`
@@ -658,19 +610,19 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
if in_code {
if is_rust && !no_test {
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
- check_code(cx, &text, edition, span);
+ check_code(cx, &text, edition, range.clone(), fragments);
}
} else {
- check_link_quotes(cx, in_link.is_some(), trimmed_text, span, &range, begin, text.len());
- // Adjust for the beginning of the current `Event`
- let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
+ if in_link.is_some() {
+ check_link_quotes(cx, trimmed_text, range.clone(), fragments);
+ }
if let Some(link) = in_link.as_ref()
&& let Ok(url) = Url::parse(link)
&& (url.scheme() == "https" || url.scheme() == "http") {
// Don't check the text associated with external URLs
continue;
}
- text_to_check.push((text, span));
+ text_to_check.push((text, range));
}
},
}
@@ -678,36 +630,21 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
headers
}
-fn check_link_quotes(
- cx: &LateContext<'_>,
- in_link: bool,
- trimmed_text: &str,
- span: Span,
- range: &Range<usize>,
- begin: usize,
- text_len: usize,
-) {
- if in_link && trimmed_text.starts_with('\'') && trimmed_text.ends_with('\'') {
- // fix the span to only point at the text within the link
- let lo = span.lo() + BytePos::from_usize(range.start - begin);
+fn check_link_quotes(cx: &LateContext<'_>, trimmed_text: &str, range: Range<usize>, fragments: Fragments<'_>) {
+ if trimmed_text.starts_with('\'')
+ && trimmed_text.ends_with('\'')
+ && let Some(span) = fragments.span(cx, range)
+ {
span_lint(
cx,
DOC_LINK_WITH_QUOTES,
- span.with_lo(lo).with_hi(lo + BytePos::from_usize(text_len)),
+ span,
"possible intra-doc link using quotes instead of backticks",
);
}
}
-fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
- let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
- Ok(o) => o,
- Err(e) => e - 1,
- };
- spans[index]
-}
-
-fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
+fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<usize>, fragments: Fragments<'_>) {
fn has_needless_main(code: String, edition: Edition) -> bool {
rustc_driver::catch_fatal_errors(|| {
rustc_span::create_session_globals_then(edition, || {
@@ -774,12 +711,13 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
.unwrap_or_default()
}
+ let trailing_whitespace = text.len() - text.trim_end().len();
+
// Because of the global session, we need to create a new session in a different thread with
// the edition we need.
let text = text.to_owned();
- if thread::spawn(move || has_needless_main(text, edition))
- .join()
- .expect("thread::spawn failed")
+ if thread::spawn(move || has_needless_main(text, edition)).join().expect("thread::spawn failed")
+ && let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace)
{
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
}
diff --git a/src/tools/clippy/clippy_lints/src/endian_bytes.rs b/src/tools/clippy/clippy_lints/src/endian_bytes.rs
index dda14b4df..affd08221 100644
--- a/src/tools/clippy/clippy_lints/src/endian_bytes.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/enum_clike.rs b/src/tools/clippy/clippy_lints/src/enum_clike.rs
index 96c5c7fc5..3f60e5a7c 100644
--- a/src/tools/clippy/clippy_lints/src/enum_clike.rs
+++ b/src/tools/clippy/clippy_lints/src/enum_clike.rs
@@ -50,7 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
.tcx
.const_eval_poly(def_id.to_def_id())
.ok()
- .map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty));
+ .map(|val| rustc_middle::mir::Const::from_value(val, ty));
if let Some(Constant::Int(val)) = constant.and_then(|c| miri_to_const(cx, c)) {
if let ty::Adt(adt, _) = ty.kind() {
if adt.is_enum() {
diff --git a/src/tools/clippy/clippy_lints/src/enum_variants.rs b/src/tools/clippy/clippy_lints/src/enum_variants.rs
index d4df6f7aa..e332f681b 100644
--- a/src/tools/clippy/clippy_lints/src/enum_variants.rs
+++ b/src/tools/clippy/clippy_lints/src/enum_variants.rs
@@ -167,7 +167,10 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
return;
}
- let first = &def.variants[0].ident.name.as_str();
+ let first = match def.variants.first() {
+ Some(variant) => variant.ident.name.as_str(),
+ None => return,
+ };
let mut pre = camel_case_split(first);
let mut post = pre.clone();
post.reverse();
diff --git a/src/tools/clippy/clippy_lints/src/error_impl_error.rs b/src/tools/clippy/clippy_lints/src/error_impl_error.rs
index 379af9b22..f24577c73 100644
--- a/src/tools/clippy/clippy_lints/src/error_impl_error.rs
+++ b/src/tools/clippy/clippy_lints/src/error_impl_error.rs
@@ -3,7 +3,6 @@ use clippy_utils::path_res;
use clippy_utils::ty::implements_trait;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{Item, ItemKind};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Visibility;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -42,9 +41,10 @@ impl<'tcx> LateLintPass<'tcx> for ErrorImplError {
};
match item.kind {
- ItemKind::TyAlias(ty, _) if implements_trait(cx, hir_ty_to_ty(cx.tcx, ty), error_def_id, &[])
- && item.ident.name == sym::Error
- && is_visible_outside_module(cx, item.owner_id.def_id) =>
+ ItemKind::TyAlias(..) if item.ident.name == sym::Error
+ && is_visible_outside_module(cx, item.owner_id.def_id)
+ && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
+ && implements_trait(cx, ty, error_def_id, &[]) =>
{
span_lint(
cx,
diff --git a/src/tools/clippy/clippy_lints/src/excessive_nesting.rs b/src/tools/clippy/clippy_lints/src/excessive_nesting.rs
index 8911f1872..83480fc5e 100644
--- a/src/tools/clippy/clippy_lints/src/excessive_nesting.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/explicit_write.rs b/src/tools/clippy/clippy_lints/src/explicit_write.rs
index 4b9ca8c91..b612cc00b 100644
--- a/src/tools/clippy/clippy_lints/src/explicit_write.rs
+++ b/src/tools/clippy/clippy_lints/src/explicit_write.rs
@@ -57,54 +57,52 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
} else {
None
}
+ && let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root())
{
- find_format_args(cx, write_arg, ExpnId::root(), |format_args| {
- let calling_macro =
- // ordering is important here, since `writeln!` uses `write!` internally
- if is_expn_of(write_call.span, "writeln").is_some() {
- Some("writeln")
- } else if is_expn_of(write_call.span, "write").is_some() {
- Some("write")
- } else {
- None
- };
- let prefix = if dest_name == "stderr" {
- "e"
- } else {
- ""
- };
+ // ordering is important here, since `writeln!` uses `write!` internally
+ let calling_macro = if is_expn_of(write_call.span, "writeln").is_some() {
+ Some("writeln")
+ } else if is_expn_of(write_call.span, "write").is_some() {
+ Some("write")
+ } else {
+ None
+ };
+ let prefix = if dest_name == "stderr" {
+ "e"
+ } else {
+ ""
+ };
- // We need to remove the last trailing newline from the string because the
- // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
- // used.
- let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
- (
- format!("{macro_name}!({dest_name}(), ...)"),
- macro_name.replace("write", "print"),
- )
- } else {
- (
- format!("{dest_name}().write_fmt(...)"),
- "print".into(),
- )
- };
- let mut applicability = Applicability::MachineApplicable;
- let inputs_snippet = snippet_with_applicability(
- cx,
- format_args_inputs_span(format_args),
- "..",
- &mut applicability,
- );
- span_lint_and_sugg(
- cx,
- EXPLICIT_WRITE,
- expr.span,
- &format!("use of `{used}.unwrap()`"),
- "try",
- format!("{prefix}{sugg_mac}!({inputs_snippet})"),
- applicability,
- );
- });
+ // We need to remove the last trailing newline from the string because the
+ // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
+ // used.
+ let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
+ (
+ format!("{macro_name}!({dest_name}(), ...)"),
+ macro_name.replace("write", "print"),
+ )
+ } else {
+ (
+ format!("{dest_name}().write_fmt(...)"),
+ "print".into(),
+ )
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let inputs_snippet = snippet_with_applicability(
+ cx,
+ format_args_inputs_span(&format_args),
+ "..",
+ &mut applicability,
+ );
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_WRITE,
+ expr.span,
+ &format!("use of `{used}.unwrap()`"),
+ "try",
+ format!("{prefix}{sugg_mac}!({inputs_snippet})"),
+ applicability,
+ );
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs
index c18006a71..0a885984a 100644
--- a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs
+++ b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs
@@ -246,8 +246,13 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
{
self.ty_params.remove(&def_id);
}
+ } else {
+ // If the bounded type isn't a generic param, but is instead a concrete generic
+ // type, any params we find nested inside of it are being used as concrete types,
+ // and can therefore can be considered used. So, we're fine to walk the left-hand
+ // side of the where bound.
+ walk_ty(self, predicate.bounded_ty);
}
- // Only walk the right-hand side of where bounds
for bound in predicate.bounds {
walk_param_bound(self, bound);
}
diff --git a/src/tools/clippy/clippy_lints/src/format.rs b/src/tools/clippy/clippy_lints/src/format.rs
index f4f8bdc2c..b748d3293 100644
--- a/src/tools/clippy/clippy_lints/src/format.rs
+++ b/src/tools/clippy/clippy_lints/src/format.rs
@@ -43,14 +43,10 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
- return;
- };
- if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
- return;
- }
-
- find_format_args(cx, expr, macro_call.expn, |format_args| {
+ if let Some(macro_call) = root_macro_call_first_node(cx, expr)
+ && cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
+ && let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
+ {
let mut applicability = Applicability::MachineApplicable;
let call_site = macro_call.span;
@@ -91,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
},
_ => {},
}
- });
+ }
}
}
diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs
index 01c714c41..39abf5c2d 100644
--- a/src/tools/clippy/clippy_lints/src/format_args.rs
+++ b/src/tools/clippy/clippy_lints/src/format_args.rs
@@ -186,15 +186,10 @@ impl FormatArgs {
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
- let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
- return;
- };
- if !is_format_macro(cx, macro_call.def_id) {
- return;
- }
- let name = cx.tcx.item_name(macro_call.def_id);
-
- find_format_args(cx, expr, macro_call.expn, |format_args| {
+ if let Some(macro_call) = root_macro_call_first_node(cx, expr)
+ && is_format_macro(cx, macro_call.def_id)
+ && let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
+ {
for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let Ok(index) = placeholder.argument.index
@@ -206,12 +201,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
if placeholder.format_trait != FormatTrait::Display
|| placeholder.format_options != FormatOptions::default()
- || is_aliased(format_args, index)
+ || is_aliased(&format_args, index)
{
continue;
}
if let Ok(arg_hir_expr) = arg_expr {
+ let name = cx.tcx.item_name(macro_call.def_id);
check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
check_to_string_in_format_args(cx, name, arg_hir_expr);
}
@@ -219,9 +215,9 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
}
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
- check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
+ check_uninlined_args(cx, &format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
}
- });
+ }
}
extract_msrv_attr!(LateContext);
diff --git a/src/tools/clippy/clippy_lints/src/format_impl.rs b/src/tools/clippy/clippy_lints/src/format_impl.rs
index 76369bccf..1d2f7cb71 100644
--- a/src/tools/clippy/clippy_lints/src/format_impl.rs
+++ b/src/tools/clippy/clippy_lints/src/format_impl.rs
@@ -170,30 +170,29 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
&& let macro_def_id = outer_macro.def_id
&& is_format_macro(cx, macro_def_id)
+ && let Some(format_args) = find_format_args(cx, expr, outer_macro.expn)
{
- find_format_args(cx, expr, outer_macro.expn, |format_args| {
- for piece in &format_args.template {
- if let FormatArgsPiece::Placeholder(placeholder) = piece
- && let trait_name = match placeholder.format_trait {
- FormatTrait::Display => sym::Display,
- FormatTrait::Debug => sym::Debug,
- FormatTrait::LowerExp => sym!(LowerExp),
- FormatTrait::UpperExp => sym!(UpperExp),
- FormatTrait::Octal => sym!(Octal),
- FormatTrait::Pointer => sym::Pointer,
- FormatTrait::Binary => sym!(Binary),
- FormatTrait::LowerHex => sym!(LowerHex),
- FormatTrait::UpperHex => sym!(UpperHex),
- }
- && trait_name == impl_trait.name
- && let Ok(index) = placeholder.argument.index
- && let Some(arg) = format_args.arguments.all_args().get(index)
- && let Ok(arg_expr) = find_format_arg_expr(expr, arg)
- {
- check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
+ for piece in &format_args.template {
+ if let FormatArgsPiece::Placeholder(placeholder) = piece
+ && let trait_name = match placeholder.format_trait {
+ FormatTrait::Display => sym::Display,
+ FormatTrait::Debug => sym::Debug,
+ FormatTrait::LowerExp => sym!(LowerExp),
+ FormatTrait::UpperExp => sym!(UpperExp),
+ FormatTrait::Octal => sym!(Octal),
+ FormatTrait::Pointer => sym::Pointer,
+ FormatTrait::Binary => sym!(Binary),
+ FormatTrait::LowerHex => sym!(LowerHex),
+ FormatTrait::UpperHex => sym!(UpperHex),
}
+ && trait_name == impl_trait.name
+ && let Ok(index) = placeholder.argument.index
+ && let Some(arg) = format_args.arguments.all_args().get(index)
+ && let Ok(arg_expr) = find_format_arg_expr(expr, arg)
+ {
+ check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
}
- });
+ }
}
}
diff --git a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
index ab6ad3f3b..e2d19e245 100644
--- a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
+++ b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
@@ -6,7 +6,7 @@ use clippy_utils::sugg::Sugg;
use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
-use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
+use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
&& then_expr.span.ctxt() == ctxt
&& is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome)
&& is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone)
- && !stmts_contains_early_return(then_block.stmts)
+ && !contains_return(then_block.stmts)
{
let mut app = Applicability::Unspecified;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app).maybe_par().to_string();
@@ -116,17 +116,3 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
extract_msrv_attr!(LateContext);
}
-
-fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool {
- stmts.iter().any(|stmt| {
- let Stmt {
- kind: StmtKind::Semi(e),
- ..
- } = stmt
- else {
- return false;
- };
-
- contains_return(e)
- })
-}
diff --git a/src/tools/clippy/clippy_lints/src/ignored_unit_patterns.rs b/src/tools/clippy/clippy_lints/src/ignored_unit_patterns.rs
index c635120b8..d8ead1c9d 100644
--- a/src/tools/clippy/clippy_lints/src/ignored_unit_patterns.rs
+++ b/src/tools/clippy/clippy_lints/src/ignored_unit_patterns.rs
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
-use hir::PatKind;
+use hir::{Node, PatKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@@ -37,6 +37,17 @@ declare_lint_pass!(IgnoredUnitPatterns => [IGNORED_UNIT_PATTERNS]);
impl<'tcx> LateLintPass<'tcx> for IgnoredUnitPatterns {
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx hir::Pat<'tcx>) {
+ match cx.tcx.hir().get_parent(pat.hir_id) {
+ Node::Param(param) if matches!(cx.tcx.hir().get_parent(param.hir_id), Node::Item(_)) => {
+ // Ignore function parameters
+ return;
+ },
+ Node::Local(local) if local.ty.is_some() => {
+ // Ignore let bindings with explicit type
+ return;
+ },
+ _ => {},
+ }
if matches!(pat.kind, PatKind::Wild) && cx.typeck_results().pat_ty(pat).is_unit() {
span_lint_and_sugg(
cx,
diff --git a/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs b/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs
new file mode 100644
index 000000000..ec9044bba
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs
@@ -0,0 +1,318 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use rustc_errors::{Applicability, SuggestionStyle};
+use rustc_hir::def_id::{DefId, 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, Generics, Ty, 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,
+ nursery,
+ "specifying bounds that are implied by other bounds in `impl Trait` type"
+}
+declare_lint_pass!(ImpliedBoundsInImpls => [IMPLIED_BOUNDS_IN_IMPLS]);
+
+#[allow(clippy::too_many_arguments)]
+fn emit_lint(
+ cx: &LateContext<'_>,
+ poly_trait: &rustc_hir::PolyTraitRef<'_>,
+ opaque_ty: &rustc_hir::OpaqueTy<'_>,
+ index: usize,
+ // The bindings that were implied
+ implied_bindings: &[rustc_hir::TypeBinding<'_>],
+ // The original bindings that `implied_bindings` are implied from
+ implied_by_bindings: &[rustc_hir::TypeBinding<'_>],
+ implied_by_args: &[GenericArg<'_>],
+ implied_by_span: Span,
+) {
+ 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 to extend the span
+ // to include the `+` token that is ahead or behind,
+ // so we don't end up with something like `impl + B` or `impl A + `
+
+ 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 if index > 0
+ && let Some(prev_bound) = opaque_ty.bounds.get(index - 1)
+ {
+ prev_bound.span().shrink_to_hi().to(poly_trait.span.shrink_to_hi())
+ } else {
+ poly_trait.span
+ };
+
+ let mut sugg = vec![(implied_span_extended, String::new())];
+
+ // We also might need to include associated type binding that were specified in the implied bound,
+ // but omitted in the implied-by bound:
+ // `fn f() -> impl Deref<Target = u8> + DerefMut`
+ // If we're going to suggest removing `Deref<..>`, we'll need to put `<Target = u8>` on `DerefMut`
+ let omitted_assoc_tys: Vec<_> = implied_bindings
+ .iter()
+ .filter(|binding| !implied_by_bindings.iter().any(|b| b.ident == binding.ident))
+ .collect();
+
+ if !omitted_assoc_tys.is_empty() {
+ // `<>` needs to be added if there aren't yet any generic arguments or bindings
+ let needs_angle_brackets = implied_by_args.is_empty() && implied_by_bindings.is_empty();
+ let insert_span = match (implied_by_args, implied_by_bindings) {
+ ([.., arg], [.., binding]) => arg.span().max(binding.span).shrink_to_hi(),
+ ([.., arg], []) => arg.span().shrink_to_hi(),
+ ([], [.., binding]) => binding.span.shrink_to_hi(),
+ ([], []) => implied_by_span.shrink_to_hi(),
+ };
+
+ let mut associated_tys_sugg = if needs_angle_brackets {
+ "<".to_owned()
+ } else {
+ // If angle brackets aren't needed (i.e., there are already generic arguments or bindings),
+ // we need to add a comma:
+ // `impl A<B, C >`
+ // ^ if we insert `Assoc=i32` without a comma here, that'd be invalid syntax:
+ // `impl A<B, C Assoc=i32>`
+ ", ".to_owned()
+ };
+
+ for (index, binding) in omitted_assoc_tys.into_iter().enumerate() {
+ if index > 0 {
+ associated_tys_sugg += ", ";
+ }
+ associated_tys_sugg += &snippet(cx, binding.span, "..");
+ }
+ if needs_angle_brackets {
+ associated_tys_sugg += ">";
+ }
+ sugg.push((insert_span, associated_tys_sugg));
+ }
+
+ diag.multipart_suggestion_with_style(
+ "try removing this bound",
+ sugg,
+ Applicability::MachineApplicable,
+ SuggestionStyle::ShowAlways,
+ );
+ },
+ );
+}
+
+/// Tries to "resolve" a type.
+/// The index passed to this function must start with `Self=0`, i.e. it must be a valid
+/// type parameter index.
+/// If the index is out of bounds, it means that the generic parameter has a default type.
+fn try_resolve_type<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ args: &'tcx [GenericArg<'tcx>],
+ generics: &'tcx Generics,
+ index: usize,
+) -> Option<Ty<'tcx>> {
+ match args.get(index - 1) {
+ Some(GenericArg::Type(ty)) => Some(hir_ty_to_ty(tcx, ty)),
+ Some(_) => None,
+ None => Some(tcx.type_of(generics.params[index].def_id).skip_binder()),
+ }
+}
+
+/// This function tries to, for all generic type parameters in a supertrait predicate `trait ...<U>:
+/// GenericTrait<U>`, check if the substituted type in the implied-by bound matches with what's
+/// subtituted in the implied bound.
+///
+/// Consider this example.
+/// ```rust,ignore
+/// trait GenericTrait<T> {}
+/// trait GenericSubTrait<T, U, V>: GenericTrait<U> {}
+/// ^^^^^^^^^^^^^^^ trait_predicate_args: [Self#0, U#2]
+/// (the Self#0 is implicit: `<Self as GenericTrait<U>>`)
+/// impl GenericTrait<i32> for () {}
+/// impl GenericSubTrait<(), i32, ()> for () {}
+/// impl GenericSubTrait<(), i64, ()> for () {}
+///
+/// fn f() -> impl GenericTrait<i32> + GenericSubTrait<(), i64, ()> {
+/// ^^^ implied_args ^^^^^^^^^^^ implied_by_args
+/// (we are interested in `i64` specifically, as that
+/// is what `U` in `GenericTrait<U>` is substituted with)
+/// }
+/// ```
+/// Here i32 != i64, so this will return false.
+fn is_same_generics<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ trait_predicate_args: &'tcx [ty::GenericArg<'tcx>],
+ implied_by_args: &'tcx [GenericArg<'tcx>],
+ implied_args: &'tcx [GenericArg<'tcx>],
+ implied_by_def_id: DefId,
+ implied_def_id: DefId,
+) -> bool {
+ // Get the generics of the two traits to be able to get default generic parameter.
+ let implied_by_generics = tcx.generics_of(implied_by_def_id);
+ let implied_generics = tcx.generics_of(implied_def_id);
+
+ trait_predicate_args
+ .iter()
+ .enumerate()
+ .skip(1) // skip `Self` implicit arg
+ .all(|(arg_index, arg)| {
+ if let Some(ty) = arg.as_type() {
+ if let &ty::Param(ty::ParamTy { index, .. }) = ty.kind()
+ // `index == 0` means that it's referring to `Self`,
+ // in which case we don't try to substitute it
+ && index != 0
+ && let Some(ty_a) = try_resolve_type(tcx, implied_by_args, implied_by_generics, index as usize)
+ && let Some(ty_b) = try_resolve_type(tcx, implied_args, implied_generics, arg_index)
+ {
+ ty_a == ty_b
+ } else if let Some(ty_b) = try_resolve_type(tcx, implied_args, implied_generics, arg_index) {
+ ty == ty_b
+ } else {
+ false
+ }
+ } 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, predicates, trait_def_id))
+ } 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 implied_bindings = path.args.map_or([].as_slice(), |a| a.bindings)
+ && let Some(def_id) = poly_trait.trait_ref.path.res.opt_def_id()
+ && let Some((implied_by_span, implied_by_args, implied_by_bindings)) = implied_bounds
+ .iter()
+ .find_map(|&(span, implied_by_path, preds, implied_by_def_id)| {
+ let implied_by_args = implied_by_path.args.map_or([].as_slice(), |a| a.args);
+ let implied_by_bindings = implied_by_path.args.map_or([].as_slice(), |a| a.bindings);
+
+ 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,
+ implied_by_def_id,
+ def_id,
+ )
+ {
+ Some((span, implied_by_args, implied_by_bindings))
+ } else {
+ None
+ }
+ })
+ })
+ {
+ emit_lint(
+ cx,
+ poly_trait,
+ opaque_ty,
+ index,
+ implied_bindings,
+ implied_by_bindings,
+ implied_by_args,
+ implied_by_span
+ );
+ }
+ }
+ }
+}
+
+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/src/tools/clippy/clippy_lints/src/init_numbered_fields.rs b/src/tools/clippy/clippy_lints/src/init_numbered_fields.rs
index b00fa104f..f95d2c2ed 100644
--- a/src/tools/clippy/clippy_lints/src/init_numbered_fields.rs
+++ b/src/tools/clippy/clippy_lints/src/init_numbered_fields.rs
@@ -50,7 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields {
&& fields
.iter()
.all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit))
- && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias { .. }, ..))
+ && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..))
{
let expr_spans = fields
.iter()
diff --git a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs
index 9b26c3573..a4f3d4983 100644
--- a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs
+++ b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs
@@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, ConstKind};
@@ -50,12 +49,12 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if !item.span.from_expansion();
- if let ItemKind::Const(hir_ty, generics, _) = &item.kind;
+ if let ItemKind::Const(_, generics, _) = &item.kind;
// Since static items may not have generics, skip generic const items.
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
// doesn't account for empty where-clauses that only consist of keyword `where` IINM.
if generics.params.is_empty() && !generics.has_where_clause_predicates;
- let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
if let ty::Array(element_type, cst) = ty.kind();
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind();
if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx);
diff --git a/src/tools/clippy/clippy_lints/src/large_futures.rs b/src/tools/clippy/clippy_lints/src/large_futures.rs
index d67d58993..19f1e08b5 100644
--- a/src/tools/clippy/clippy_lints/src/large_futures.rs
+++ b/src/tools/clippy/clippy_lints/src/large_futures.rs
@@ -17,26 +17,20 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
- /// async fn wait(f: impl std::future::Future<Output = ()>) {}
+ /// async fn large_future(_x: [u8; 16 * 1024]) {}
///
- /// async fn big_fut(arg: [u8; 1024]) {}
- ///
- /// pub async fn test() {
- /// let fut = big_fut([0u8; 1024]);
- /// wait(fut).await;
+ /// pub async fn trigger() {
+ /// large_future([0u8; 16 * 1024]).await;
/// }
/// ```
///
/// `Box::pin` the big future instead.
///
/// ```rust
- /// async fn wait(f: impl std::future::Future<Output = ()>) {}
- ///
- /// async fn big_fut(arg: [u8; 1024]) {}
+ /// async fn large_future(_x: [u8; 16 * 1024]) {}
///
- /// pub async fn test() {
- /// let fut = Box::pin(big_fut([0u8; 1024]));
- /// wait(fut).await;
+ /// pub async fn trigger() {
+ /// Box::pin(large_future([0u8; 16 * 1024])).await;
/// }
/// ```
#[clippy::version = "1.70.0"]
diff --git a/src/tools/clippy/clippy_lints/src/large_stack_frames.rs b/src/tools/clippy/clippy_lints/src/large_stack_frames.rs
index 7aa1446d5..1d28d7dd0 100644
--- a/src/tools/clippy/clippy_lints/src/large_stack_frames.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs
index deba232bd..c06b35ca0 100644
--- a/src/tools/clippy/clippy_lints/src/len_zero.rs
+++ b/src/tools/clippy/clippy_lints/src/len_zero.rs
@@ -424,6 +424,14 @@ fn check_for_is_empty(
item_name: Symbol,
item_kind: &str,
) {
+ // Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
+ // find the correct inherent impls.
+ let impl_ty = if let Some(adt) = cx.tcx.type_of(impl_ty).skip_binder().ty_adt_def() {
+ adt.did()
+ } else {
+ return;
+ };
+
let is_empty = Symbol::intern("is_empty");
let is_empty = cx
.tcx
diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs
index 358004cf4..1271be2fd 100644
--- a/src/tools/clippy/clippy_lints/src/lib.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.rs
@@ -21,6 +21,7 @@
// FIXME: switch to something more ergonomic here, once available.
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate pulldown_cmark;
extern crate rustc_arena;
extern crate rustc_ast;
extern crate rustc_ast_pretty;
@@ -37,6 +38,7 @@ extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_parse;
+extern crate rustc_resolve;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
@@ -152,8 +154,8 @@ 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;
mod indexing_slicing;
mod infinite_iter;
@@ -209,6 +211,7 @@ mod misc;
mod misc_early;
mod mismatching_type_param_order;
mod missing_assert_message;
+mod missing_asserts_for_indexing;
mod missing_const_for_fn;
mod missing_doc;
mod missing_enforced_import_rename;
@@ -227,6 +230,7 @@ mod mutex_atomic;
mod needless_arbitrary_self_type;
mod needless_bool;
mod needless_borrowed_ref;
+mod needless_borrows_for_generic_args;
mod needless_continue;
mod needless_else;
mod needless_for_each;
@@ -242,6 +246,7 @@ mod neg_multiply;
mod new_without_default;
mod no_effect;
mod no_mangle_with_rust_abi;
+mod non_canonical_impls;
mod non_copy_const;
mod non_expressive_names;
mod non_octal_unix_permissions;
@@ -285,6 +290,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;
@@ -326,6 +332,7 @@ mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
mod unnecessary_box_returns;
+mod unnecessary_map_on_constructor;
mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports;
mod unnecessary_struct_initialization;
@@ -605,7 +612,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
.collect(),
))
});
- store.register_early_pass(|| Box::new(utils::format_args_collector::FormatArgsCollector));
+ store.register_early_pass(|| Box::<utils::format_args_collector::FormatArgsCollector>::default());
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author));
let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
@@ -632,7 +639,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool));
store.register_late_pass(|_| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach));
- store.register_late_pass(|_| Box::<misc::LintPass>::default());
+ store.register_late_pass(|_| Box::new(misc::LintPass));
store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction));
store.register_late_pass(|_| Box::new(mut_mut::MutMut));
store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed));
@@ -658,12 +665,19 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
let suppress_restriction_lint_in_const = conf.suppress_restriction_lint_in_const;
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
+ let allowed_dotfiles = conf
+ .allowed_dotfiles
+ .iter()
+ .cloned()
+ .chain(methods::DEFAULT_ALLOWED_DOTFILES.iter().copied().map(ToOwned::to_owned))
+ .collect::<FxHashSet<_>>();
store.register_late_pass(move |_| {
Box::new(methods::Methods::new(
avoid_breaking_exported_api,
msrv(),
allow_expect_in_tests,
allow_unwrap_in_tests,
+ allowed_dotfiles.clone(),
))
});
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
@@ -693,7 +707,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
- store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv())));
+ let enforce_iter_loop_reborrow = conf.enforce_iter_loop_reborrow;
+ store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv(), enforce_iter_loop_reborrow)));
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
store.register_late_pass(|_| Box::new(entry::HashMapPass));
@@ -875,7 +890,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
- store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
+ store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
let future_size_threshold = conf.future_size_threshold;
@@ -1066,7 +1081,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
avoid_breaking_exported_api,
))
});
- store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
+ store.register_late_pass(|_| Box::new(non_canonical_impls::NonCanonicalImpls));
store.register_late_pass(move |_| {
Box::new(single_call_fn::SingleCallFn {
avoid_breaking_exported_api,
@@ -1095,6 +1110,15 @@ 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));
+ store.register_late_pass(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing));
+ store.register_late_pass(|_| Box::new(unnecessary_map_on_constructor::UnnecessaryMapOnConstructor));
+ store.register_late_pass(move |_| {
+ Box::new(needless_borrows_for_generic_args::NeedlessBorrowsForGenericArgs::new(
+ msrv(),
+ ))
+ });
// add lints here, do not remove this comment, it's used in `new_lint`
}
diff --git a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs
index 7b8c88235..6ab256ef0 100644
--- a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs
@@ -13,8 +13,14 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMut
use rustc_middle::ty::{self, EarlyBinder, Ty, TypeAndMut};
use rustc_span::sym;
-pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>, msrv: &Msrv) {
- let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr) else {
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ self_arg: &Expr<'_>,
+ call_expr: &Expr<'_>,
+ msrv: &Msrv,
+ enforce_iter_loop_reborrow: bool,
+) {
+ let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr, enforce_iter_loop_reborrow) else {
return;
};
if let ty::Array(_, count) = *ty.peel_refs().kind() {
@@ -102,6 +108,7 @@ fn is_ref_iterable<'tcx>(
cx: &LateContext<'tcx>,
self_arg: &Expr<'_>,
call_expr: &Expr<'_>,
+ enforce_iter_loop_reborrow: bool,
) -> Option<(AdjustKind, Ty<'tcx>)> {
let typeck = cx.typeck_results();
if let Some(trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
@@ -142,7 +149,8 @@ fn is_ref_iterable<'tcx>(
{
return Some((AdjustKind::None, self_ty));
}
- } else if let ty::Ref(region, ty, Mutability::Mut) = *self_ty.kind()
+ } else if enforce_iter_loop_reborrow
+ && let ty::Ref(region, ty, Mutability::Mut) = *self_ty.kind()
&& let Some(mutbl) = mutbl
{
// Attempt to reborrow the mutable reference
@@ -186,7 +194,8 @@ fn is_ref_iterable<'tcx>(
},
..
] => {
- if target != self_ty
+ if enforce_iter_loop_reborrow
+ && target != self_ty
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs
index ffd29ab76..1fb16adad 100644
--- a/src/tools/clippy/clippy_lints/src/loops/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs
@@ -609,10 +609,14 @@ declare_clippy_lint! {
pub struct Loops {
msrv: Msrv,
+ enforce_iter_loop_reborrow: bool,
}
impl Loops {
- pub fn new(msrv: Msrv) -> Self {
- Self { msrv }
+ pub fn new(msrv: Msrv, enforce_iter_loop_reborrow: bool) -> Self {
+ Self {
+ msrv,
+ enforce_iter_loop_reborrow,
+ }
}
}
impl_lint_pass!(Loops => [
@@ -719,7 +723,7 @@ impl Loops {
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
match method.ident.as_str() {
"iter" | "iter_mut" => {
- explicit_iter_loop::check(cx, self_arg, arg, &self.msrv);
+ explicit_iter_loop::check(cx, self_arg, arg, &self.msrv, self.enforce_iter_loop_reborrow);
},
"into_iter" => {
explicit_into_iter_loop::check(cx, self_arg, arg);
diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
index cc19ac55e..3d8a4cd94 100644
--- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
@@ -1,13 +1,13 @@
use super::utils::make_iterator_snippet;
use super::NEVER_LOOP;
-use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
+use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::{Block, Destination, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
use rustc_lint::LateContext;
-use rustc_span::Span;
+use rustc_span::{sym, Span};
use std::iter::{once, Iterator};
pub(super) fn check<'tcx>(
@@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
for_loop: Option<&ForLoop<'_>>,
) {
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
- NeverLoopResult::AlwaysBreak => {
+ NeverLoopResult::Diverging => {
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
if let Some(ForLoop {
arg: iterator,
@@ -39,67 +39,76 @@ pub(super) fn check<'tcx>(
}
});
},
- NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
- NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(),
+ NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Normal => (),
}
}
+/// The `never_loop` analysis keeps track of three things:
+///
+/// * Has any (reachable) code path hit a `continue` of the main loop?
+/// * Is the current code path diverging (that is, the next expression is not reachable)
+/// * For each block label `'a` inside the main loop, has any (reachable) code path encountered a
+/// `break 'a`?
+///
+/// The first two bits of information are in this enum, and the last part is in the
+/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
+/// scope.
#[derive(Copy, Clone)]
enum NeverLoopResult {
- // A break/return always get triggered but not necessarily for the main loop.
- AlwaysBreak,
- // A continue may occur for the main loop.
+ /// A continue may occur for the main loop.
MayContinueMainLoop,
- // Ignore everything until the end of the block with this id
- IgnoreUntilEnd(HirId),
- Otherwise,
+ /// We have not encountered any main loop continue,
+ /// but we are diverging (subsequent control flow is not reachable)
+ Diverging,
+ /// We have not encountered any main loop continue,
+ /// and subsequent control flow is (possibly) reachable
+ Normal,
}
#[must_use]
fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult {
match arg {
- NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise,
+ NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
- NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id),
}
}
// Combine two results for parts that are called in order.
#[must_use]
-fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult {
+fn combine_seq(first: NeverLoopResult, second: impl FnOnce() -> NeverLoopResult) -> NeverLoopResult {
match first {
- NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => {
- first
- },
- NeverLoopResult::Otherwise => second,
+ NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop => first,
+ NeverLoopResult::Normal => second(),
+ }
+}
+
+// Combine an iterator of results for parts that are called in order.
+#[must_use]
+fn combine_seq_many(iter: impl IntoIterator<Item = NeverLoopResult>) -> NeverLoopResult {
+ for e in iter {
+ if let NeverLoopResult::Diverging | NeverLoopResult::MayContinueMainLoop = e {
+ return e;
+ }
}
+ NeverLoopResult::Normal
}
// Combine two results where only one of the part may have been executed.
#[must_use]
-fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult {
+fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult {
match (b1, b2) {
- (NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => {
- if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a {
- NeverLoopResult::IgnoreUntilEnd(b)
- } else {
- NeverLoopResult::IgnoreUntilEnd(a)
- }
- },
- (i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak)
- | (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i,
- (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak,
(NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => {
NeverLoopResult::MayContinueMainLoop
},
- (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise,
+ (NeverLoopResult::Normal, _) | (_, NeverLoopResult::Normal) => NeverLoopResult::Normal,
+ (NeverLoopResult::Diverging, NeverLoopResult::Diverging) => NeverLoopResult::Diverging,
}
}
fn never_loop_block<'tcx>(
cx: &LateContext<'tcx>,
block: &Block<'tcx>,
- ignore_ids: &mut Vec<HirId>,
+ local_labels: &mut Vec<(HirId, bool)>,
main_loop_id: HirId,
) -> NeverLoopResult {
let iter = block
@@ -107,15 +116,21 @@ fn never_loop_block<'tcx>(
.iter()
.filter_map(stmt_to_expr)
.chain(block.expr.map(|expr| (expr, None)));
-
- iter.map(|(e, els)| {
- let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
+ combine_seq_many(iter.map(|(e, els)| {
+ let e = never_loop_expr(cx, e, local_labels, main_loop_id);
// els is an else block in a let...else binding
els.map_or(e, |els| {
- combine_branches(e, never_loop_block(cx, els, ignore_ids, main_loop_id), ignore_ids)
+ combine_seq(e, || match never_loop_block(cx, els, local_labels, main_loop_id) {
+ // Returning MayContinueMainLoop here means that
+ // we will not evaluate the rest of the body
+ NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop,
+ // An else block always diverges, so the Normal case should not happen,
+ // but the analysis is approximate so it might return Normal anyway.
+ // Returning Normal here says that nothing more happens on the main path
+ NeverLoopResult::Diverging | NeverLoopResult::Normal => NeverLoopResult::Normal,
+ })
})
- })
- .fold(NeverLoopResult::Otherwise, combine_seq)
+ }))
}
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'tcx Block<'tcx>>)> {
@@ -131,76 +146,69 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
fn never_loop_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
- ignore_ids: &mut Vec<HirId>,
+ local_labels: &mut Vec<(HirId, bool)>,
main_loop_id: HirId,
) -> NeverLoopResult {
- match expr.kind {
+ let result = match expr.kind {
ExprKind::Unary(_, e)
| ExprKind::Cast(e, _)
| ExprKind::Type(e, _)
| ExprKind::Field(e, _)
| ExprKind::AddrOf(_, _, e)
| ExprKind::Repeat(e, _)
- | ExprKind::DropTemps(e) => never_loop_expr(cx, e, ignore_ids, main_loop_id),
- ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, ignore_ids, main_loop_id),
- ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, &mut es.iter(), ignore_ids, main_loop_id),
+ | ExprKind::DropTemps(e) => never_loop_expr(cx, e, local_labels, main_loop_id),
+ ExprKind::Let(let_expr) => never_loop_expr(cx, let_expr.init, local_labels, main_loop_id),
+ ExprKind::Array(es) | ExprKind::Tup(es) => never_loop_expr_all(cx, es.iter(), local_labels, main_loop_id),
ExprKind::MethodCall(_, receiver, es, _) => never_loop_expr_all(
cx,
- &mut std::iter::once(receiver).chain(es.iter()),
- ignore_ids,
+ std::iter::once(receiver).chain(es.iter()),
+ local_labels,
main_loop_id,
),
ExprKind::Struct(_, fields, base) => {
- let fields = never_loop_expr_all(cx, &mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id);
+ let fields = never_loop_expr_all(cx, fields.iter().map(|f| f.expr), local_labels, main_loop_id);
if let Some(base) = base {
- combine_seq(fields, never_loop_expr(cx, base, ignore_ids, main_loop_id))
+ combine_seq(fields, || never_loop_expr(cx, base, local_labels, main_loop_id))
} else {
fields
}
},
- ExprKind::Call(e, es) => never_loop_expr_all(cx, &mut once(e).chain(es.iter()), ignore_ids, main_loop_id),
+ ExprKind::Call(e, es) => never_loop_expr_all(cx, once(e).chain(es.iter()), local_labels, main_loop_id),
ExprKind::Binary(_, e1, e2)
| ExprKind::Assign(e1, e2, _)
| ExprKind::AssignOp(_, e1, e2)
- | ExprKind::Index(e1, e2, _) => {
- never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id)
- },
+ | ExprKind::Index(e1, e2, _) => never_loop_expr_all(cx, [e1, e2].iter().copied(), local_labels, main_loop_id),
ExprKind::Loop(b, _, _, _) => {
- // Break can come from the inner loop so remove them.
- absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))
+ // We don't attempt to track reachability after a loop,
+ // just assume there may have been a break somewhere
+ absorb_break(never_loop_block(cx, b, local_labels, main_loop_id))
},
ExprKind::If(e, e2, e3) => {
- let e1 = never_loop_expr(cx, e, ignore_ids, main_loop_id);
- let e2 = never_loop_expr(cx, e2, ignore_ids, main_loop_id);
- // If we know the `if` condition evaluates to `true`, don't check everything past it; it
- // should just return whatever's evaluated for `e1` and `e2` since `e3` is unreachable
- if let Some(Constant::Bool(true)) = constant(cx, cx.typeck_results(), e) {
- return combine_seq(e1, e2);
- }
- let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| {
- never_loop_expr(cx, e, ignore_ids, main_loop_id)
- });
- combine_seq(e1, combine_branches(e2, e3, ignore_ids))
+ let e1 = never_loop_expr(cx, e, local_labels, main_loop_id);
+ combine_seq(e1, || {
+ let e2 = never_loop_expr(cx, e2, local_labels, main_loop_id);
+ let e3 = e3.as_ref().map_or(NeverLoopResult::Normal, |e| {
+ never_loop_expr(cx, e, local_labels, main_loop_id)
+ });
+ combine_branches(e2, e3)
+ })
},
ExprKind::Match(e, arms, _) => {
- let e = never_loop_expr(cx, e, ignore_ids, main_loop_id);
- if arms.is_empty() {
- e
- } else {
- let arms = never_loop_expr_branch(cx, &mut arms.iter().map(|a| a.body), ignore_ids, main_loop_id);
- combine_seq(e, arms)
- }
+ let e = never_loop_expr(cx, e, local_labels, main_loop_id);
+ combine_seq(e, || {
+ arms.iter().fold(NeverLoopResult::Diverging, |a, b| {
+ combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id))
+ })
+ })
},
ExprKind::Block(b, l) => {
if l.is_some() {
- ignore_ids.push(b.hir_id);
- }
- let ret = never_loop_block(cx, b, ignore_ids, main_loop_id);
- if l.is_some() {
- ignore_ids.pop();
+ local_labels.push((b.hir_id, false));
}
+ let ret = never_loop_block(cx, b, local_labels, main_loop_id);
+ let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
match ret {
- NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise,
+ NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
_ => ret,
}
},
@@ -211,74 +219,78 @@ fn never_loop_expr<'tcx>(
if id == main_loop_id {
NeverLoopResult::MayContinueMainLoop
} else {
- NeverLoopResult::AlwaysBreak
+ NeverLoopResult::Diverging
}
},
- // checks if break targets a block instead of a loop
- ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e
- .map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| {
- never_loop_expr(cx, e, ignore_ids, main_loop_id)
- }),
- ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
- combine_seq(
- never_loop_expr(cx, e, ignore_ids, main_loop_id),
- NeverLoopResult::AlwaysBreak,
- )
- }),
- ExprKind::Become(e) => combine_seq(
- never_loop_expr(cx, e, ignore_ids, main_loop_id),
- NeverLoopResult::AlwaysBreak,
- ),
- ExprKind::InlineAsm(asm) => asm
- .operands
- .iter()
- .map(|(o, _)| match o {
- InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
- never_loop_expr(cx, expr, ignore_ids, main_loop_id)
- },
- InlineAsmOperand::Out { expr, .. } => {
- never_loop_expr_all(cx, &mut expr.iter().copied(), ignore_ids, main_loop_id)
- },
- InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
- cx,
- &mut once(*in_expr).chain(out_expr.iter().copied()),
- ignore_ids,
- main_loop_id,
- ),
- InlineAsmOperand::Const { .. }
- | InlineAsmOperand::SymFn { .. }
- | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
+ ExprKind::Break(_, e) | ExprKind::Ret(e) => {
+ let first = e.as_ref().map_or(NeverLoopResult::Normal, |e| {
+ never_loop_expr(cx, e, local_labels, main_loop_id)
+ });
+ combine_seq(first, || {
+ // checks if break targets a block instead of a loop
+ if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind {
+ if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) {
+ *reachable = true;
+ }
+ }
+ NeverLoopResult::Diverging
})
- .fold(NeverLoopResult::Otherwise, combine_seq),
+ },
+ ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
+ NeverLoopResult::Diverging
+ }),
+ ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
+ InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
+ never_loop_expr(cx, expr, local_labels, main_loop_id)
+ },
+ InlineAsmOperand::Out { expr, .. } => {
+ never_loop_expr_all(cx, expr.iter().copied(), local_labels, main_loop_id)
+ },
+ InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => never_loop_expr_all(
+ cx,
+ once(*in_expr).chain(out_expr.iter().copied()),
+ local_labels,
+ main_loop_id,
+ ),
+ InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
+ NeverLoopResult::Normal
+ },
+ })),
ExprKind::OffsetOf(_, _)
| ExprKind::Yield(_, _)
| ExprKind::Closure { .. }
| ExprKind::Path(_)
| ExprKind::ConstBlock(_)
| ExprKind::Lit(_)
- | ExprKind::Err(_) => NeverLoopResult::Otherwise,
+ | ExprKind::Err(_) => NeverLoopResult::Normal,
+ };
+ let result = combine_seq(result, || {
+ if cx.typeck_results().expr_ty(expr).is_never() {
+ NeverLoopResult::Diverging
+ } else {
+ NeverLoopResult::Normal
+ }
+ });
+ if let NeverLoopResult::Diverging = result &&
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) &&
+ let Some(sym::todo_macro) = cx.tcx.get_diagnostic_name(macro_call.def_id)
+ {
+ // We return MayContinueMainLoop here because we treat `todo!()`
+ // as potentially containing any code, including a continue of the main loop.
+ // This effectively silences the lint whenever a loop contains this macro anywhere.
+ NeverLoopResult::MayContinueMainLoop
+ } else {
+ result
}
}
fn never_loop_expr_all<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
cx: &LateContext<'tcx>,
- es: &mut T,
- ignore_ids: &mut Vec<HirId>,
- main_loop_id: HirId,
-) -> NeverLoopResult {
- es.map(|e| never_loop_expr(cx, e, ignore_ids, main_loop_id))
- .fold(NeverLoopResult::Otherwise, combine_seq)
-}
-
-fn never_loop_expr_branch<'tcx, T: Iterator<Item = &'tcx Expr<'tcx>>>(
- cx: &LateContext<'tcx>,
- e: &mut T,
- ignore_ids: &mut Vec<HirId>,
+ es: T,
+ local_labels: &mut Vec<(HirId, bool)>,
main_loop_id: HirId,
) -> NeverLoopResult {
- e.fold(NeverLoopResult::AlwaysBreak, |a, b| {
- combine_branches(a, never_loop_expr(cx, b, ignore_ids, main_loop_id), ignore_ids)
- })
+ combine_seq_many(es.map(|e| never_loop_expr(cx, e, local_labels, main_loop_id)))
}
fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String {
diff --git a/src/tools/clippy/clippy_lints/src/loops/utils.rs b/src/tools/clippy/clippy_lints/src/loops/utils.rs
index 6edca2d55..0a2bd89eb 100644
--- a/src/tools/clippy/clippy_lints/src/loops/utils.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/utils.rs
@@ -5,7 +5,6 @@ use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
@@ -150,7 +149,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
if l.pat.hir_id == self.var_id;
if let PatKind::Binding(.., ident, _) = l.pat.kind;
then {
- let ty = l.ty.map(|ty| hir_ty_to_ty(self.cx.tcx, ty));
+ let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
InitializeVisitorState::Initialized {
diff --git a/src/tools/clippy/clippy_lints/src/manual_range_patterns.rs b/src/tools/clippy/clippy_lints/src/manual_range_patterns.rs
index 39d8b20d3..90557b555 100644
--- a/src/tools/clippy/clippy_lints/src/manual_range_patterns.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_range_patterns.rs
@@ -1,4 +1,5 @@
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
use rustc_ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
@@ -6,6 +7,7 @@ use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd, UnOp};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{Span, DUMMY_SP};
declare_clippy_lint! {
/// ### What it does
@@ -49,6 +51,29 @@ fn expr_as_i128(expr: &Expr<'_>) -> Option<i128> {
}
}
+#[derive(Copy, Clone)]
+struct Num {
+ val: i128,
+ span: Span,
+}
+
+impl Num {
+ fn new(expr: &Expr<'_>) -> Option<Self> {
+ Some(Self {
+ val: expr_as_i128(expr)?,
+ span: expr.span,
+ })
+ }
+
+ fn dummy(val: i128) -> Self {
+ Self { val, span: DUMMY_SP }
+ }
+
+ fn min(self, other: Self) -> Self {
+ if self.val < other.val { self } else { other }
+ }
+}
+
impl LateLintPass<'_> for ManualRangePatterns {
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) {
if in_external_macro(cx.sess(), pat.span) {
@@ -56,71 +81,83 @@ impl LateLintPass<'_> for ManualRangePatterns {
}
// a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives
+ // or at least one range
if let PatKind::Or(pats) = pat.kind
- && pats.len() >= 3
+ && (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..))))
{
- let mut min = i128::MAX;
- let mut max = i128::MIN;
+ let mut min = Num::dummy(i128::MAX);
+ let mut max = Num::dummy(i128::MIN);
+ let mut range_kind = RangeEnd::Included;
let mut numbers_found = FxHashSet::default();
let mut ranges_found = Vec::new();
for pat in pats {
if let PatKind::Lit(lit) = pat.kind
- && let Some(num) = expr_as_i128(lit)
+ && let Some(num) = Num::new(lit)
{
- numbers_found.insert(num);
+ numbers_found.insert(num.val);
min = min.min(num);
- max = max.max(num);
+ if num.val >= max.val {
+ max = num;
+ range_kind = RangeEnd::Included;
+ }
} else if let PatKind::Range(Some(left), Some(right), end) = pat.kind
- && let Some(left) = expr_as_i128(left)
- && let Some(right) = expr_as_i128(right)
- && right >= left
+ && let Some(left) = Num::new(left)
+ && let Some(mut right) = Num::new(right)
{
+ if let RangeEnd::Excluded = end {
+ right.val -= 1;
+ }
+
min = min.min(left);
- max = max.max(right);
- ranges_found.push(left..=match end {
- RangeEnd::Included => right,
- RangeEnd::Excluded => right - 1,
- });
+ if right.val > max.val {
+ max = right;
+ range_kind = end;
+ }
+ ranges_found.push(left.val..=right.val);
} else {
return;
}
}
- let contains_whole_range = 'contains: {
- let mut num = min;
- while num <= max {
- if numbers_found.contains(&num) {
- num += 1;
- }
- // Given a list of (potentially overlapping) ranges like:
- // 1..=5, 3..=7, 6..=10
- // We want to find the range with the highest end that still contains the current number
- else if let Some(range) = ranges_found
- .iter()
- .filter(|range| range.contains(&num))
- .max_by_key(|range| range.end())
- {
- num = range.end() + 1;
- } else {
- break 'contains false;
- }
+ let mut num = min.val;
+ while num <= max.val {
+ if numbers_found.contains(&num) {
+ num += 1;
+ }
+ // Given a list of (potentially overlapping) ranges like:
+ // 1..=5, 3..=7, 6..=10
+ // We want to find the range with the highest end that still contains the current number
+ else if let Some(range) = ranges_found
+ .iter()
+ .filter(|range| range.contains(&num))
+ .max_by_key(|range| range.end())
+ {
+ num = range.end() + 1;
+ } else {
+ return;
}
- break 'contains true;
- };
-
- if contains_whole_range {
- span_lint_and_sugg(
- cx,
- MANUAL_RANGE_PATTERNS,
- pat.span,
- "this OR pattern can be rewritten using a range",
- "try",
- format!("{min}..={max}"),
- Applicability::MachineApplicable,
- );
}
+
+ span_lint_and_then(
+ cx,
+ MANUAL_RANGE_PATTERNS,
+ pat.span,
+ "this OR pattern can be rewritten using a range",
+ |diag| {
+ if let Some(min) = snippet_opt(cx, min.span)
+ && let Some(max) = snippet_opt(cx, max.span)
+ {
+ diag.span_suggestion(
+ pat.span,
+ "try",
+ format!("{min}{range_kind}{max}"),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/manual_retain.rs b/src/tools/clippy/clippy_lints/src/manual_retain.rs
index 5259066eb..1a69a48c5 100644
--- a/src/tools/clippy/clippy_lints/src/manual_retain.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/matches/needless_match.rs b/src/tools/clippy/clippy_lints/src/matches/needless_match.rs
index c4f6852ae..44dc29c36 100644
--- a/src/tools/clippy/clippy_lints/src/matches/needless_match.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/needless_match.rs
@@ -8,8 +8,7 @@ use clippy_utils::{
};
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
-use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, FnRetTy, Guard, Node, Pat, PatKind, Path, QPath};
-use rustc_hir_analysis::hir_ty_to_ty;
+use rustc_hir::{Arm, BindingAnnotation, ByRef, Expr, ExprKind, Guard, ItemKind, Node, Pat, PatKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_span::sym;
@@ -141,11 +140,15 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
},
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
- Node::Item(..) => {
- if let Some(fn_decl) = p_node.fn_decl() {
- if let FnRetTy::Return(ret_ty) = fn_decl.output {
- return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
- }
+ Node::Item(item) => {
+ if let ItemKind::Fn(..) = item.kind {
+ let output = cx
+ .tcx
+ .fn_sig(item.owner_id)
+ .instantiate_identity()
+ .output()
+ .skip_binder();
+ return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
}
},
// check the parent expr for this whole block `{ match match_expr {..} }`
diff --git a/src/tools/clippy/clippy_lints/src/matches/overlapping_arms.rs b/src/tools/clippy/clippy_lints/src/matches/overlapping_arms.rs
index 8be3c178a..7c0485914 100644
--- a/src/tools/clippy/clippy_lints/src/matches/overlapping_arms.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/overlapping_arms.rs
@@ -37,22 +37,14 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?,
None => {
let min_val_const = ty.numeric_min_val(cx.tcx)?;
- let min_constant = mir::ConstantKind::from_value(
- cx.tcx.valtree_to_const_val((ty, min_val_const.to_valtree())),
- ty,
- );
- miri_to_const(cx, min_constant)?
+ miri_to_const(cx, mir::Const::from_ty_const(min_val_const, cx.tcx))?
},
};
let rhs_const = match rhs {
Some(rhs) => constant(cx, cx.typeck_results(), rhs)?,
None => {
let max_val_const = ty.numeric_max_val(cx.tcx)?;
- let max_constant = mir::ConstantKind::from_value(
- cx.tcx.valtree_to_const_val((ty, max_val_const.to_valtree())),
- ty,
- );
- miri_to_const(cx, max_constant)?
+ miri_to_const(cx, mir::Const::from_ty_const(max_val_const, cx.tcx))?
},
};
let lhs_val = lhs_const.int_value(cx, ty)?;
diff --git a/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs b/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs
index 29af48123..0efeeacc9 100644
--- a/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/redundant_guards.rs
@@ -2,11 +2,12 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::visitors::{for_each_expr, is_local_used};
-use rustc_ast::LitKind;
+use rustc_ast::{BorrowKind, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
use rustc_lint::LateContext;
+use rustc_span::symbol::Ident;
use rustc_span::Span;
use std::ops::ControlFlow;
@@ -34,32 +35,45 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
],
MatchSource::Normal,
) = if_expr.kind
+ && let Some(binding) = get_pat_binding(cx, scrutinee, outer_arm)
{
+ let pat_span = match (arm.pat.kind, binding.byref_ident) {
+ (PatKind::Ref(pat, _), Some(_)) => pat.span,
+ (PatKind::Ref(..), None) | (_, Some(_)) => continue,
+ _ => arm.pat.span,
+ };
emit_redundant_guards(
cx,
outer_arm,
if_expr.span,
- scrutinee,
- arm.pat.span,
+ pat_span,
+ &binding,
arm.guard,
);
}
// `Some(x) if let Some(2) = x`
- else if let Guard::IfLet(let_expr) = guard {
+ else if let Guard::IfLet(let_expr) = guard
+ && let Some(binding) = get_pat_binding(cx, let_expr.init, outer_arm)
+ {
+ let pat_span = match (let_expr.pat.kind, binding.byref_ident) {
+ (PatKind::Ref(pat, _), Some(_)) => pat.span,
+ (PatKind::Ref(..), None) | (_, Some(_)) => continue,
+ _ => let_expr.pat.span,
+ };
emit_redundant_guards(
cx,
outer_arm,
let_expr.span,
- let_expr.init,
- let_expr.pat.span,
+ pat_span,
+ &binding,
None,
);
}
// `Some(x) if x == Some(2)`
+ // `Some(x) if Some(2) == x`
else if let Guard::If(if_expr) = guard
&& let ExprKind::Binary(bin_op, local, pat) = if_expr.kind
&& matches!(bin_op.node, BinOpKind::Eq)
- && expr_can_be_pat(cx, pat)
// Ensure they have the same type. If they don't, we'd need deref coercion which isn't
// possible (currently) in a pattern. In some cases, you can use something like
// `as_deref` or similar but in general, we shouldn't lint this as it'd create an
@@ -67,43 +81,68 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>]) {
//
// This isn't necessary in the other two checks, as they must be a pattern already.
&& cx.typeck_results().expr_ty(local) == cx.typeck_results().expr_ty(pat)
+ // Since we want to lint on both `x == Some(2)` and `Some(2) == x`, we might have to "swap"
+ // `local` and `pat`, depending on which side they are.
+ && let Some((binding, pat)) = get_pat_binding(cx, local, outer_arm)
+ .map(|binding| (binding, pat))
+ .or_else(|| get_pat_binding(cx, pat, outer_arm).map(|binding| (binding, local)))
+ && expr_can_be_pat(cx, pat)
{
+ let pat_span = match (pat.kind, binding.byref_ident) {
+ (ExprKind::AddrOf(BorrowKind::Ref, _, expr), Some(_)) => expr.span,
+ (ExprKind::AddrOf(..), None) | (_, Some(_)) => continue,
+ _ => pat.span,
+ };
emit_redundant_guards(
cx,
outer_arm,
if_expr.span,
- local,
- pat.span,
+ pat_span,
+ &binding,
None,
);
}
}
}
-fn get_pat_binding<'tcx>(cx: &LateContext<'tcx>, guard_expr: &Expr<'_>, outer_arm: &Arm<'tcx>) -> Option<(Span, bool)> {
+struct PatBindingInfo {
+ span: Span,
+ byref_ident: Option<Ident>,
+ is_field: bool,
+}
+
+fn get_pat_binding<'tcx>(
+ cx: &LateContext<'tcx>,
+ guard_expr: &Expr<'_>,
+ outer_arm: &Arm<'tcx>,
+) -> Option<PatBindingInfo> {
if let Some(local) = path_to_local(guard_expr) && !is_local_used(cx, outer_arm.body, local) {
let mut span = None;
+ let mut byref_ident = None;
let mut multiple_bindings = false;
// `each_binding` gives the `HirId` of the `Pat` itself, not the binding
outer_arm.pat.walk(|pat| {
- if let PatKind::Binding(_, hir_id, _, _) = pat.kind
+ if let PatKind::Binding(bind_annot, hir_id, ident, _) = pat.kind
&& hir_id == local
- && span.replace(pat.span).is_some()
{
- multiple_bindings = true;
- return false;
+ if matches!(bind_annot.0, rustc_ast::ByRef::Yes) {
+ let _ = byref_ident.insert(ident);
+ }
+ // the second call of `replace()` returns a `Some(span)`, meaning a multi-binding pattern
+ if span.replace(pat.span).is_some() {
+ multiple_bindings = true;
+ return false;
+ }
}
-
true
});
// Ignore bindings from or patterns, like `First(x) | Second(x, _) | Third(x, _, _)`
if !multiple_bindings {
- return span.map(|span| {
- (
- span,
- !matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
- )
+ return span.map(|span| PatBindingInfo {
+ span,
+ byref_ident,
+ is_field: matches!(cx.tcx.hir().get_parent(local), Node::PatField(_)),
});
}
}
@@ -115,14 +154,11 @@ fn emit_redundant_guards<'tcx>(
cx: &LateContext<'tcx>,
outer_arm: &Arm<'tcx>,
guard_span: Span,
- local: &Expr<'_>,
pat_span: Span,
+ pat_binding: &PatBindingInfo,
inner_guard: Option<Guard<'_>>,
) {
let mut app = Applicability::MaybeIncorrect;
- let Some((pat_binding, can_use_shorthand)) = get_pat_binding(cx, local, outer_arm) else {
- return;
- };
span_lint_and_then(
cx,
@@ -131,14 +167,21 @@ fn emit_redundant_guards<'tcx>(
"redundant guard",
|diag| {
let binding_replacement = snippet_with_applicability(cx, pat_span, "<binding_repl>", &mut app);
+ let suggestion_span = match *pat_binding {
+ PatBindingInfo {
+ span,
+ byref_ident: Some(ident),
+ is_field: true,
+ } => (span, format!("{ident}: {binding_replacement}")),
+ PatBindingInfo {
+ span, is_field: true, ..
+ } => (span.shrink_to_hi(), format!(": {binding_replacement}")),
+ PatBindingInfo { span, .. } => (span, binding_replacement.into_owned()),
+ };
diag.multipart_suggestion_verbose(
"try",
vec![
- if can_use_shorthand {
- (pat_binding, binding_replacement.into_owned())
- } else {
- (pat_binding.shrink_to_hi(), format!(": {binding_replacement}"))
- },
+ suggestion_span,
(
guard_span.source_callsite().with_lo(outer_arm.pat.span.hi()),
inner_guard.map_or_else(String::new, |guard| {
diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
index d3e90e4bb..40e487bf6 100644
--- a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
@@ -131,13 +131,12 @@ pub(super) fn check<'tcx>(
let mut applicability = Applicability::MachineApplicable;
- //Special handling for `format!` as arg_root
+ // Special handling for `format!` as arg_root
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
- if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
- return;
- }
- find_format_args(cx, arg_root, macro_call.expn, |format_args| {
- let span = format_args_inputs_span(format_args);
+ if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
+ && let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn)
+ {
+ let span = format_args_inputs_span(&format_args);
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg(
cx,
@@ -148,7 +147,7 @@ pub(super) fn check<'tcx>(
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
applicability,
);
- });
+ }
return;
}
diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs
index fafc97097..336572549 100644
--- a/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/filter_map_bool_then.rs
@@ -8,6 +8,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::Binder;
use rustc_span::{sym, Span};
@@ -36,6 +37,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
&& match_def_path(cx, def_id, &BOOL_THEN)
&& !is_from_proc_macro(cx, expr)
+ // Count the number of derefs needed to get to the bool because we need those in the suggestion
+ && let needed_derefs = cx.typeck_results().expr_adjustments(recv)
+ .iter()
+ .filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ .count()
&& let Some(param_snippet) = snippet_opt(cx, param.span)
&& let Some(filter) = snippet_opt(cx, recv.span)
&& let Some(map) = snippet_opt(cx, then_body.span)
@@ -46,7 +52,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &
call_span,
"usage of `bool::then` in `filter_map`",
"use `filter` then `map` instead",
- format!("filter(|&{param_snippet}| {filter}).map(|{param_snippet}| {map})"),
+ format!(
+ "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})",
+ derefs="*".repeat(needed_derefs)
+ ),
Applicability::MachineApplicable,
);
}
diff --git a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
index 043425300..e91ce64d8 100644
--- a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs b/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs
new file mode 100644
index 000000000..79c6d6325
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs
@@ -0,0 +1,106 @@
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::higher::VecArgs;
+use clippy_utils::{expr_or_init, is_trait_method, match_def_path, paths};
+use rustc_ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self};
+use rustc_span::sym;
+
+use super::ITER_OUT_OF_BOUNDS;
+
+fn expr_as_u128(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<u128> {
+ if let ExprKind::Lit(lit) = expr_or_init(cx, e).kind
+ && let LitKind::Int(n, _) = lit.node
+ {
+ Some(n)
+ } else {
+ None
+ }
+}
+
+/// Attempts to extract the length out of an iterator expression.
+fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> Option<u128> {
+ let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else {
+ return None;
+ };
+ let did = adt.did();
+
+ if match_def_path(cx, did, &paths::ARRAY_INTO_ITER) {
+ // For array::IntoIter<T, const N: usize>, the length is the second generic
+ // parameter.
+ substs
+ .const_at(1)
+ .try_eval_target_usize(cx.tcx, cx.param_env)
+ .map(u128::from)
+ } else if match_def_path(cx, did, &paths::SLICE_ITER)
+ && let ExprKind::MethodCall(_, recv, ..) = iter.kind
+ {
+ if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() {
+ // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..)
+ len.try_eval_target_usize(cx.tcx, cx.param_env).map(u128::from)
+ } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) {
+ match args {
+ VecArgs::Vec(vec) => vec.len().try_into().ok(),
+ VecArgs::Repeat(_, len) => expr_as_u128(cx, len),
+ }
+ } else {
+ None
+ }
+ } else if match_def_path(cx, did, &paths::ITER_EMPTY) {
+ Some(0)
+ } else if match_def_path(cx, did, &paths::ITER_ONCE) {
+ Some(1)
+ } else {
+ None
+ }
+}
+
+fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ recv: &'tcx Expr<'tcx>,
+ arg: &'tcx Expr<'tcx>,
+ message: &'static str,
+ note: &'static str,
+) {
+ if is_trait_method(cx, expr, sym::Iterator)
+ && let Some(len) = get_iterator_length(cx, recv)
+ && let Some(skipped) = expr_as_u128(cx, arg)
+ && skipped > len
+ {
+ span_lint_and_note(cx, ITER_OUT_OF_BOUNDS, expr.span, message, None, note);
+ }
+}
+
+pub(super) fn check_skip<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ recv: &'tcx Expr<'tcx>,
+ arg: &'tcx Expr<'tcx>,
+) {
+ check(
+ cx,
+ expr,
+ recv,
+ arg,
+ "this `.skip()` call skips more items than the iterator will produce",
+ "this operation is useless and will create an empty iterator",
+ );
+}
+
+pub(super) fn check_take<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ recv: &'tcx Expr<'tcx>,
+ arg: &'tcx Expr<'tcx>,
+) {
+ check(
+ cx,
+ expr,
+ recv,
+ arg,
+ "this `.take()` call takes more items than the iterator will produce",
+ "this operation is useless and the returned iterator will simply yield the same items",
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
index 9f7ec19aa..a49dd98db 100644
--- a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
+++ b/src/tools/clippy/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` `all` `any`
+ 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/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs
index 42756b27d..e7fcef9e9 100644
--- a/src/tools/clippy/clippy_lints/src/methods/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs
@@ -43,6 +43,7 @@ mod iter_next_slice;
mod iter_nth;
mod iter_nth_zero;
mod iter_on_single_or_empty_collections;
+mod iter_out_of_bounds;
mod iter_overeager_cloned;
mod iter_skip_next;
mod iter_skip_zero;
@@ -73,9 +74,11 @@ mod option_map_unwrap_or;
mod or_fun_call;
mod or_then_unwrap;
mod path_buf_push_overwrite;
+mod path_ends_with_ext;
mod range_zip_with_len;
mod read_line_without_trim;
mod readonly_write_lock;
+mod redundant_as_str;
mod repeat_once;
mod search_is_some;
mod seek_from_current;
@@ -119,9 +122,10 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
use if_chain::if_chain;
+pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
+use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, TraitRef, Ty};
@@ -301,7 +305,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"
@@ -3054,12 +3058,12 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
- /// vec!(1, 2, 3, 4, 5).resize(0, 5)
+ /// vec![1, 2, 3, 4, 5].resize(0, 5)
/// ```
///
/// Use instead:
/// ```rust
- /// vec!(1, 2, 3, 4, 5).clear()
+ /// vec![1, 2, 3, 4, 5].clear()
/// ```
#[clippy::version = "1.46.0"]
pub VEC_RESIZE_TO_ZERO,
@@ -3328,7 +3332,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"
@@ -3538,11 +3542,101 @@ declare_clippy_lint! {
"acquiring a write lock when a read lock would work"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for iterator combinator calls such as `.take(x)` or `.skip(x)`
+ /// where `x` is greater than the amount of items that an iterator will produce.
+ ///
+ /// ### Why is this bad?
+ /// Taking or skipping more items than there are in an iterator either creates an iterator
+ /// with all items from the original iterator or an iterator with no items at all.
+ /// This is most likely not what the user intended to do.
+ ///
+ /// ### Example
+ /// ```rust
+ /// for _ in [1, 2, 3].iter().take(4) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// for _ in [1, 2, 3].iter() {}
+ /// ```
+ #[clippy::version = "1.74.0"]
+ pub ITER_OUT_OF_BOUNDS,
+ suspicious,
+ "calls to `.take()` or `.skip()` that are out of bounds"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for calls to `Path::ends_with` calls where the argument looks like a file extension.
+ ///
+ /// By default, Clippy has a short list of known filenames that start with a dot
+ /// but aren't necessarily file extensions (e.g. the `.git` folder), which are allowed by default.
+ /// The `allowed-dotfiles` configuration can be used to allow additional
+ /// file extensions that Clippy should not lint.
+ ///
+ /// ### Why is this bad?
+ /// This doesn't actually compare file extensions. Rather, `ends_with` compares the given argument
+ /// to the last **component** of the path and checks if it matches exactly.
+ ///
+ /// ### Known issues
+ /// File extensions are often at most three characters long, so this only lints in those cases
+ /// in an attempt to avoid false positives.
+ /// Any extension names longer than that are assumed to likely be real path components and are
+ /// therefore ignored.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::path::Path;
+ /// fn is_markdown(path: &Path) -> bool {
+ /// path.ends_with(".md")
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::path::Path;
+ /// fn is_markdown(path: &Path) -> bool {
+ /// path.extension().is_some_and(|ext| ext == "md")
+ /// }
+ /// ```
+ #[clippy::version = "1.74.0"]
+ pub PATH_ENDS_WITH_EXT,
+ suspicious,
+ "attempting to compare file extensions using `Path::ends_with`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `as_str()` on a `String`` chained with a method available on the `String` itself.
+ ///
+ /// ### Why is this bad?
+ /// The `as_str()` conversion is pointless and can be removed for simplicity and cleanliness.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #![allow(unused)]
+ /// let owned_string = "This is a string".to_owned();
+ /// owned_string.as_str().as_bytes();
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # #![allow(unused)]
+ /// let owned_string = "This is a string".to_owned();
+ /// owned_string.as_bytes();
+ /// ```
+ #[clippy::version = "1.74.0"]
+ pub REDUNDANT_AS_STR,
+ complexity,
+ "`as_str` used to call a method on `str` that is also available on `String`"
+}
+
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
+ allowed_dotfiles: FxHashSet<String>,
}
impl Methods {
@@ -3552,12 +3646,14 @@ impl Methods {
msrv: Msrv,
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
+ allowed_dotfiles: FxHashSet<String>,
) -> Self {
Self {
avoid_breaking_exported_api,
msrv,
allow_expect_in_tests,
allow_unwrap_in_tests,
+ allowed_dotfiles,
}
}
}
@@ -3676,7 +3772,10 @@ impl_lint_pass!(Methods => [
STRING_LIT_CHARS_ANY,
ITER_SKIP_ZERO,
FILTER_MAP_BOOL_THEN,
- READONLY_WRITE_LOCK
+ READONLY_WRITE_LOCK,
+ ITER_OUT_OF_BOUNDS,
+ PATH_ENDS_WITH_EXT,
+ REDUNDANT_AS_STR,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3826,18 +3925,20 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
if_chain! {
if let TraitItemKind::Fn(ref sig, _) = item.kind;
if sig.decl.implicit_self.has_implicit_self();
- if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
-
+ if let Some(first_arg_hir_ty) = sig.decl.inputs.first();
+ if let Some(&first_arg_ty) = cx.tcx.fn_sig(item.owner_id)
+ .instantiate_identity()
+ .inputs()
+ .skip_binder()
+ .first();
then {
- let first_arg_span = first_arg_ty.span;
- let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
wrong_self_convention::check(
cx,
item.ident.name.as_str(),
self_ty,
first_arg_ty,
- first_arg_span,
+ first_arg_hir_ty.span,
false,
true,
);
@@ -3873,6 +3974,12 @@ impl Methods {
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
zst_offset::check(cx, expr, recv);
},
+ ("all", [arg]) => {
+ if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
+ iter_overeager_cloned::check(cx, expr, recv, recv2,
+ iter_overeager_cloned::Op::NeedlessMove(name, arg), false);
+ }
+ }
("and_then", [arg]) => {
let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
@@ -3880,12 +3987,16 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
}
},
- ("any", [arg]) if let ExprKind::Closure(arg) = arg.kind
- && let body = cx.tcx.hir().body(arg.body)
- && let [param] = body.params
- && let Some(("chars", recv, _, _, _)) = method_call(recv) =>
- {
- string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
+ ("any", [arg]) => {
+ match method_call(recv) {
+ Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, iter_overeager_cloned::Op::NeedlessMove(name, arg), false),
+ Some(("chars", recv, _, _, _)) if let ExprKind::Closure(arg) = arg.kind
+ && let body = cx.tcx.hir().body(arg.body)
+ && let [param] = body.params => {
+ string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
+ }
+ _ => {}
+ }
}
("arg", [arg]) => {
suspicious_command_arg_space::check(cx, recv, arg, span);
@@ -3893,6 +4004,7 @@ impl Methods {
("as_deref" | "as_deref_mut", []) => {
needless_option_as_deref::check(cx, expr, recv, name);
},
+ ("as_bytes" | "is_empty", []) => if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { redundant_as_str::check(cx, expr, recv, as_str_span, span); },
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
@@ -3919,7 +4031,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);
},
@@ -3942,6 +4054,7 @@ impl Methods {
if let ExprKind::MethodCall(.., span) = expr.kind {
case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
}
+ path_ends_with_ext::check(cx, recv, arg, expr, &self.msrv, &self.allowed_dotfiles);
},
("expect", [_]) => {
match method_call(recv) {
@@ -3973,6 +4086,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 +4107,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 +4143,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 +4153,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 +4183,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 +4196,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),
@@ -4124,9 +4249,11 @@ impl Methods {
},
("skip", [arg]) => {
iter_skip_zero::check(cx, expr, arg);
+ iter_out_of_bounds::check_skip(cx, expr, recv, 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", []) => {
@@ -4150,9 +4277,11 @@ impl Methods {
}
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
- ("take", [_arg]) => {
+ ("take", [arg]) => {
+ iter_out_of_bounds::check_take(cx, expr, recv, 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/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
index 8b2f57160..942f3bd79 100644
--- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/methods/path_ends_with_ext.rs b/src/tools/clippy/clippy_lints/src/methods/path_ends_with_ext.rs
new file mode 100644
index 000000000..3347c8c16
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/path_ends_with_ext.rs
@@ -0,0 +1,53 @@
+use super::PATH_ENDS_WITH_EXT;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::msrvs;
+use clippy_utils::msrvs::Msrv;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_ast::{LitKind, StrStyle};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::sym;
+use std::fmt::Write;
+
+pub const DEFAULT_ALLOWED_DOTFILES: &[&str] = &[
+ "git", "svn", "gem", "npm", "vim", "env", "rnd", "ssh", "vnc", "smb", "nvm", "bin",
+];
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ recv: &Expr<'_>,
+ path: &Expr<'_>,
+ expr: &Expr<'_>,
+ msrv: &Msrv,
+ allowed_dotfiles: &FxHashSet<String>,
+) {
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::Path)
+ && !path.span.from_expansion()
+ && let ExprKind::Lit(lit) = path.kind
+ && let LitKind::Str(path, StrStyle::Cooked) = lit.node
+ && let Some(path) = path.as_str().strip_prefix('.')
+ && (1..=3).contains(&path.len())
+ && !allowed_dotfiles.contains(path)
+ && path.chars().all(char::is_alphanumeric)
+ {
+ let mut sugg = snippet(cx, recv.span, "..").into_owned();
+ if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
+ let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
+ } else {
+ let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
+ };
+
+ span_lint_and_sugg(
+ cx,
+ PATH_ENDS_WITH_EXT,
+ expr.span,
+ "this looks like a failed attempt at checking for the file extension",
+ "try",
+ sugg,
+ Applicability::MaybeIncorrect
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/redundant_as_str.rs b/src/tools/clippy/clippy_lints/src/methods/redundant_as_str.rs
new file mode 100644
index 000000000..98cd6afc2
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/redundant_as_str.rs
@@ -0,0 +1,34 @@
+use super::REDUNDANT_AS_STR;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::Expr;
+use rustc_lint::LateContext;
+use rustc_middle::query::Key;
+use rustc_span::Span;
+
+pub(super) fn check(
+ cx: &LateContext<'_>,
+ _expr: &Expr<'_>,
+ recv: &Expr<'_>,
+ as_str_span: Span,
+ other_method_span: Span,
+) {
+ if cx
+ .tcx
+ .lang_items()
+ .string()
+ .is_some_and(|id| Some(id) == cx.typeck_results().expr_ty(recv).ty_adt_id())
+ {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_AS_STR,
+ as_str_span.to(other_method_span),
+ "this `as_str` is redundant and can be removed as the method immediately following exists on `String` too",
+ "try",
+ snippet_with_applicability(cx, other_method_span, "..", &mut applicability).into_owned(),
+ applicability,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
index 5c5ee2620..50d6f3b7e 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -401,7 +401,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
= get_callee_generic_args_and_args(cx, parent_expr)
{
// FIXME: the `instantiate_identity()` below seems incorrect, since we eventually
- // call `tcx.try_subst_and_normalize_erasing_regions` further down
+ // call `tcx.try_instantiate_and_normalize_erasing_regions` further down
// (i.e., we are explicitly not in the identity context).
let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder();
if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
@@ -452,7 +452,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
let output_ty = fn_sig.output();
if output_ty.contains(*param_ty) {
- if let Ok(new_ty) = cx.tcx.try_subst_and_normalize_erasing_regions(
+ if let Ok(new_ty) = cx.tcx.try_instantiate_and_normalize_erasing_regions(
new_subst, cx.param_env, EarlyBinder::bind(output_ty)) {
expr = parent_expr;
ty = new_ty;
diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs
index 303f01256..9c8b47fb3 100644
--- a/src/tools/clippy/clippy_lints/src/misc.rs
+++ b/src/tools/clippy/clippy_lints/src/misc.rs
@@ -1,24 +1,22 @@
-use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
-use clippy_utils::source::{snippet, snippet_opt, snippet_with_context};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then, span_lint_hir_and_then};
+use clippy_utils::source::{snippet, snippet_with_context};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{
+ any_parent_is_automatically_derived, fulfill_or_allowed, get_parent_expr, is_lint_allowed, iter_input_pats,
+ last_path_segment, SpanlessEq,
+};
use if_chain::if_chain;
use rustc_errors::Applicability;
+use rustc_hir::def::Res;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
- self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, Stmt,
- StmtKind, TyKind,
+ BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind,
};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
-use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
-use rustc_span::hygiene::DesugaringKind;
-use rustc_span::source_map::{ExpnKind, Span};
-
-use clippy_utils::sugg::Sugg;
-use clippy_utils::{
- get_parent_expr, in_constant, is_integer_literal, is_lint_allowed, is_no_std_crate, iter_input_pats,
- last_path_segment, SpanlessEq,
-};
+use rustc_span::source_map::Span;
use crate::ref_patterns::REF_PATTERNS;
@@ -56,6 +54,7 @@ declare_clippy_lint! {
style,
"an entire binding declared as `ref`, in a function argument or a `let` statement"
}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for the use of bindings with a single leading
@@ -103,51 +102,13 @@ declare_clippy_lint! {
"using a short circuit boolean condition as a statement"
}
-declare_clippy_lint! {
- /// ### What it does
- /// Catch casts from `0` to some pointer type
- ///
- /// ### Why is this bad?
- /// This generally means `null` and is better expressed as
- /// {`std`, `core`}`::ptr::`{`null`, `null_mut`}.
- ///
- /// ### Example
- /// ```rust
- /// let a = 0 as *const u32;
- /// ```
- ///
- /// Use instead:
- /// ```rust
- /// let a = std::ptr::null::<u32>();
- /// ```
- #[clippy::version = "pre 1.29.0"]
- pub ZERO_PTR,
- style,
- "using `0 as *{const, mut} T`"
-}
-
-pub struct LintPass {
- std_or_core: &'static str,
-}
-impl Default for LintPass {
- fn default() -> Self {
- Self { std_or_core: "std" }
- }
-}
-impl_lint_pass!(LintPass => [
+declare_lint_pass!(LintPass => [
TOPLEVEL_REF_ARG,
USED_UNDERSCORE_BINDING,
SHORT_CIRCUIT_STATEMENT,
- ZERO_PTR,
]);
impl<'tcx> LateLintPass<'tcx> for LintPass {
- fn check_crate(&mut self, cx: &LateContext<'_>) {
- if is_no_std_crate(cx) {
- self.std_or_core = "core";
- }
- }
-
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
@@ -253,50 +214,56 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- if let ExprKind::Cast(e, ty) = expr.kind {
- self.check_cast(cx, expr.span, e, ty);
- return;
- }
- if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
- // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
+ if in_external_macro(cx.sess(), expr.span)
+ || expr.span.desugaring_kind().is_some()
+ || any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
+ {
return;
}
- let sym;
- let binding = match expr.kind {
- ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
- let binding = last_path_segment(qpath).ident.as_str();
- if binding.starts_with('_') &&
- !binding.starts_with("__") &&
- binding != "_result" && // FIXME: #944
- is_used(cx, expr) &&
- // don't lint if the declaration is in a macro
- non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
+ let (definition_hir_id, ident) = match expr.kind {
+ ExprKind::Path(ref qpath) => {
+ if let QPath::Resolved(None, path) = qpath
+ && let Res::Local(id) = path.res
+ && is_used(cx, expr)
{
- Some(binding)
+ (id, last_path_segment(qpath).ident)
} else {
- None
+ return;
}
},
- ExprKind::Field(_, ident) => {
- sym = ident.name;
- let name = sym.as_str();
- if name.starts_with('_') && !name.starts_with("__") {
- Some(name)
+ ExprKind::Field(recv, ident) => {
+ if let Some(adt_def) = cx.typeck_results().expr_ty_adjusted(recv).ty_adt_def()
+ && let Some(field) = adt_def.all_fields().find(|field| field.name == ident.name)
+ && let Some(local_did) = field.did.as_local()
+ && let Some(hir_id) = cx.tcx.opt_local_def_id_to_hir_id(local_did)
+ && !cx.tcx.type_of(field.did).skip_binder().is_phantom_data()
+ {
+ (hir_id, ident)
} else {
- None
+ return;
}
},
- _ => None,
+ _ => return,
};
- if let Some(binding) = binding {
- span_lint(
+
+ let name = ident.name.as_str();
+ if name.starts_with('_')
+ && !name.starts_with("__")
+ && let definition_span = cx.tcx.hir().span(definition_hir_id)
+ && !definition_span.from_expansion()
+ && !fulfill_or_allowed(cx, USED_UNDERSCORE_BINDING, [expr.hir_id, definition_hir_id])
+ {
+ span_lint_and_then(
cx,
USED_UNDERSCORE_BINDING,
expr.span,
&format!(
- "used binding `{binding}` which is prefixed with an underscore. A leading \
+ "used binding `{name}` which is prefixed with an underscore. A leading \
underscore signals that a binding will not be used"
),
+ |diag| {
+ diag.span_note(definition_span, format!("`{name}` is defined here"));
+ }
);
}
}
@@ -311,50 +278,3 @@ fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
_ => is_used(cx, parent),
})
}
-
-/// Tests whether an expression is in a macro expansion (e.g., something
-/// generated by `#[derive(...)]` or the like).
-fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
- use rustc_span::hygiene::MacroKind;
- if expr.span.from_expansion() {
- let data = expr.span.ctxt().outer_expn_data();
- matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
- } else {
- false
- }
-}
-
-/// Tests whether `res` is a variable defined outside a macro.
-fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
- if let def::Res::Local(id) = res {
- !cx.tcx.hir().span(id).from_expansion()
- } else {
- false
- }
-}
-
-impl LintPass {
- fn check_cast(&self, cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
- if_chain! {
- if let TyKind::Ptr(ref mut_ty) = ty.kind;
- if is_integer_literal(e, 0);
- if !in_constant(cx, e.hir_id);
- then {
- let (msg, sugg_fn) = match mut_ty.mutbl {
- Mutability::Mut => ("`0 as *mut _` detected", "ptr::null_mut"),
- Mutability::Not => ("`0 as *const _` detected", "ptr::null"),
- };
-
- let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
- (format!("{}::{sugg_fn}()", self.std_or_core), Applicability::MachineApplicable)
- } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
- (format!("{}::{sugg_fn}::<{mut_ty_snip}>()", self.std_or_core), Applicability::MachineApplicable)
- } else {
- // `MaybeIncorrect` as type inference may not work with the suggested code
- (format!("{}::{sugg_fn}()", self.std_or_core), Applicability::MaybeIncorrect)
- };
- span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
- }
- }
- }
-}
diff --git a/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs
new file mode 100644
index 000000000..08fec2b8e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/missing_asserts_for_indexing.rs
@@ -0,0 +1,391 @@
+use std::mem;
+use std::ops::ControlFlow;
+
+use clippy_utils::comparisons::{normalize_comparison, Rel};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::visitors::for_each_expr;
+use clippy_utils::{eq_expr_value, hash_expr, higher};
+use rustc_ast::{LitKind, RangeLimits};
+use rustc_data_structures::unhash::UnhashMap;
+use rustc_errors::{Applicability, Diagnostic};
+use rustc_hir::{BinOp, Block, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for repeated slice indexing without asserting beforehand that the length
+ /// is greater than the largest index used to index into the slice.
+ ///
+ /// ### Why is this bad?
+ /// In the general case where the compiler does not have a lot of information
+ /// about the length of a slice, indexing it repeatedly will generate a bounds check
+ /// for every single index.
+ ///
+ /// Asserting that the length of the slice is at least as large as the largest value
+ /// to index beforehand gives the compiler enough information to elide the bounds checks,
+ /// effectively reducing the number of bounds checks from however many times
+ /// the slice was indexed to just one (the assert).
+ ///
+ /// ### Drawbacks
+ /// False positives. It is, in general, very difficult to predict how well
+ /// the optimizer will be able to elide bounds checks and it very much depends on
+ /// the surrounding code. For example, indexing into the slice yielded by the
+ /// [`slice::chunks_exact`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.chunks_exact)
+ /// iterator will likely have all of the bounds checks elided even without an assert
+ /// if the `chunk_size` is a constant.
+ ///
+ /// Asserts are not tracked across function calls. Asserting the length of a slice
+ /// in a different function likely gives the optimizer enough information
+ /// about the length of a slice, but this lint will not detect that.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn sum(v: &[u8]) -> u8 {
+ /// // 4 bounds checks
+ /// v[0] + v[1] + v[2] + v[3]
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn sum(v: &[u8]) -> u8 {
+ /// assert!(v.len() > 4);
+ /// // no bounds checks
+ /// v[0] + v[1] + v[2] + v[3]
+ /// }
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub MISSING_ASSERTS_FOR_INDEXING,
+ restriction,
+ "indexing into a slice multiple times without an `assert`"
+}
+declare_lint_pass!(MissingAssertsForIndexing => [MISSING_ASSERTS_FOR_INDEXING]);
+
+fn report_lint<F>(cx: &LateContext<'_>, full_span: Span, msg: &str, indexes: &[Span], f: F)
+where
+ F: FnOnce(&mut Diagnostic),
+{
+ span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, full_span, msg, |diag| {
+ f(diag);
+ for span in indexes {
+ diag.span_note(*span, "slice indexed here");
+ }
+ diag.note("asserting the length before indexing will elide bounds checks");
+ });
+}
+
+#[derive(Copy, Clone, Debug)]
+enum LengthComparison {
+ /// `v.len() < 5`
+ LengthLessThanInt,
+ /// `5 < v.len()`
+ IntLessThanLength,
+ /// `v.len() <= 5`
+ LengthLessThanOrEqualInt,
+ /// `5 <= v.len()`
+ IntLessThanOrEqualLength,
+}
+
+/// Extracts parts out of a length comparison expression.
+///
+/// E.g. for `v.len() > 5` this returns `Some((LengthComparison::IntLessThanLength, 5, `v.len()`))`
+fn len_comparison<'hir>(
+ bin_op: BinOp,
+ left: &'hir Expr<'hir>,
+ right: &'hir Expr<'hir>,
+) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
+ macro_rules! int_lit_pat {
+ ($id:ident) => {
+ ExprKind::Lit(Spanned {
+ node: LitKind::Int($id, _),
+ ..
+ })
+ };
+ }
+
+ // normalize comparison, `v.len() > 4` becomes `4 < v.len()`
+ // this simplifies the logic a bit
+ let (op, left, right) = normalize_comparison(bin_op.node, left, right)?;
+ match (op, &left.kind, &right.kind) {
+ (Rel::Lt, int_lit_pat!(left), _) => Some((LengthComparison::IntLessThanLength, *left as usize, right)),
+ (Rel::Lt, _, int_lit_pat!(right)) => Some((LengthComparison::LengthLessThanInt, *right as usize, left)),
+ (Rel::Le, int_lit_pat!(left), _) => Some((LengthComparison::IntLessThanOrEqualLength, *left as usize, right)),
+ (Rel::Le, _, int_lit_pat!(right)) => Some((LengthComparison::LengthLessThanOrEqualInt, *right as usize, left)),
+ _ => None,
+ }
+}
+
+/// Attempts to extract parts out of an `assert!`-like expression
+/// in the form `assert!(some_slice.len() > 5)`.
+///
+/// `assert!` has expanded to an if expression at the HIR, so this
+/// actually works not just with `assert!` specifically, but anything
+/// that has a never type expression in the `then` block (e.g. `panic!`).
+fn assert_len_expr<'hir>(
+ cx: &LateContext<'_>,
+ expr: &'hir Expr<'hir>,
+) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
+ if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
+ && let ExprKind::Unary(UnOp::Not, condition) = &cond.kind
+ && let ExprKind::Binary(bin_op, left, right) = &condition.kind
+
+ && let Some((cmp, asserted_len, slice_len)) = len_comparison(*bin_op, left, right)
+ && let ExprKind::MethodCall(method, recv, ..) = &slice_len.kind
+ && cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
+ && method.ident.name == sym::len
+
+ // check if `then` block has a never type expression
+ && let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind
+ && cx.typeck_results().expr_ty(then_expr).is_never()
+ {
+ Some((cmp, asserted_len, recv))
+ } else {
+ None
+ }
+}
+
+#[derive(Debug)]
+enum IndexEntry<'hir> {
+ /// `assert!` without any indexing (so far)
+ StrayAssert {
+ asserted_len: usize,
+ comparison: LengthComparison,
+ assert_span: Span,
+ slice: &'hir Expr<'hir>,
+ },
+ /// `assert!` with indexing
+ ///
+ /// We also store the highest index to be able to check
+ /// if the `assert!` asserts the right length.
+ AssertWithIndex {
+ highest_index: usize,
+ asserted_len: usize,
+ assert_span: Span,
+ slice: &'hir Expr<'hir>,
+ indexes: Vec<Span>,
+ comparison: LengthComparison,
+ },
+ /// Indexing without an `assert!`
+ IndexWithoutAssert {
+ highest_index: usize,
+ indexes: Vec<Span>,
+ slice: &'hir Expr<'hir>,
+ },
+}
+
+impl<'hir> IndexEntry<'hir> {
+ pub fn slice(&self) -> &'hir Expr<'hir> {
+ match self {
+ IndexEntry::StrayAssert { slice, .. }
+ | IndexEntry::AssertWithIndex { slice, .. }
+ | IndexEntry::IndexWithoutAssert { slice, .. } => slice,
+ }
+ }
+
+ pub fn index_spans(&self) -> Option<&[Span]> {
+ match self {
+ IndexEntry::StrayAssert { .. } => None,
+ IndexEntry::AssertWithIndex { indexes, .. } | IndexEntry::IndexWithoutAssert { indexes, .. } => {
+ Some(indexes)
+ },
+ }
+ }
+}
+
+/// Extracts the upper index of a slice indexing expression.
+///
+/// E.g. for `5` this returns `Some(5)`, for `..5` this returns `Some(4)`,
+/// for `..=5` this returns `Some(5)`
+fn upper_index_expr(expr: &Expr<'_>) -> Option<usize> {
+ if let ExprKind::Lit(lit) = &expr.kind && let LitKind::Int(index, _) = lit.node {
+ Some(index as usize)
+ } else if let Some(higher::Range { end: Some(end), limits, .. }) = higher::Range::hir(expr)
+ && let ExprKind::Lit(lit) = &end.kind
+ && let LitKind::Int(index @ 1.., _) = lit.node
+ {
+ match limits {
+ RangeLimits::HalfOpen => Some(index as usize - 1),
+ RangeLimits::Closed => Some(index as usize),
+ }
+ } else {
+ None
+ }
+}
+
+/// Checks if the expression is an index into a slice and adds it to `indexes`
+fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) {
+ if let ExprKind::Index(slice, index_lit, _) = expr.kind
+ && cx.typeck_results().expr_ty_adjusted(slice).peel_refs().is_slice()
+ && let Some(index) = upper_index_expr(index_lit)
+ {
+ let hash = hash_expr(cx, slice);
+
+ let indexes = map.entry(hash).or_default();
+ let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
+
+ if let Some(entry) = entry {
+ match entry {
+ IndexEntry::StrayAssert { asserted_len, comparison, assert_span, slice } => {
+ *entry = IndexEntry::AssertWithIndex {
+ highest_index: index,
+ asserted_len: *asserted_len,
+ assert_span: *assert_span,
+ slice,
+ indexes: vec![expr.span],
+ comparison: *comparison,
+ };
+ },
+ IndexEntry::IndexWithoutAssert { highest_index, indexes, .. }
+ | IndexEntry::AssertWithIndex { highest_index, indexes, .. } => {
+ indexes.push(expr.span);
+ *highest_index = (*highest_index).max(index);
+ },
+ }
+ } else {
+ indexes.push(IndexEntry::IndexWithoutAssert {
+ highest_index: index,
+ indexes: vec![expr.span],
+ slice,
+ });
+ }
+ }
+}
+
+/// Checks if the expression is an `assert!` expression and adds it to `asserts`
+fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) {
+ if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) {
+ let hash = hash_expr(cx, slice);
+ let indexes = map.entry(hash).or_default();
+
+ let entry = indexes.iter_mut().find(|entry| eq_expr_value(cx, entry.slice(), slice));
+
+ if let Some(entry) = entry {
+ if let IndexEntry::IndexWithoutAssert {
+ highest_index,
+ indexes,
+ slice,
+ } = entry
+ {
+ *entry = IndexEntry::AssertWithIndex {
+ highest_index: *highest_index,
+ indexes: mem::take(indexes),
+ slice,
+ assert_span: expr.span,
+ comparison,
+ asserted_len,
+ };
+ }
+ } else {
+ indexes.push(IndexEntry::StrayAssert {
+ asserted_len,
+ comparison,
+ assert_span: expr.span,
+ slice,
+ });
+ }
+ }
+}
+
+/// Inspects indexes and reports lints.
+///
+/// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
+fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>>>) {
+ for bucket in map.values() {
+ for entry in bucket {
+ let Some(full_span) = entry
+ .index_spans()
+ .and_then(|spans| spans.first().zip(spans.last()))
+ .map(|(low, &high)| low.to(high))
+ else {
+ continue;
+ };
+
+ match entry {
+ IndexEntry::AssertWithIndex {
+ highest_index,
+ asserted_len,
+ indexes,
+ comparison,
+ assert_span,
+ slice,
+ } if indexes.len() > 1 => {
+ // if we have found an `assert!`, let's also check that it's actually right
+ // and if it convers the highest index and if not, suggest the correct length
+ let sugg = match comparison {
+ // `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
+ // The user probably meant `v.len() > 5`
+ LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => Some(
+ format!("assert!({}.len() > {highest_index})", snippet(cx, slice.span, "..")),
+ ),
+ // `5 < v.len()` == `v.len() > 5`
+ LengthComparison::IntLessThanLength if asserted_len < highest_index => Some(format!(
+ "assert!({}.len() > {highest_index})",
+ snippet(cx, slice.span, "..")
+ )),
+ // `5 <= v.len() == `v.len() >= 5`
+ LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => Some(format!(
+ "assert!({}.len() > {highest_index})",
+ snippet(cx, slice.span, "..")
+ )),
+ _ => None,
+ };
+
+ if let Some(sugg) = sugg {
+ report_lint(
+ cx,
+ full_span,
+ "indexing into a slice multiple times with an `assert` that does not cover the highest index",
+ indexes,
+ |diag| {
+ diag.span_suggestion(
+ *assert_span,
+ "provide the highest index that is indexed with",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ },
+ IndexEntry::IndexWithoutAssert {
+ indexes,
+ highest_index,
+ slice,
+ } if indexes.len() > 1 => {
+ // if there was no `assert!` but more than one index, suggest
+ // adding an `assert!` that covers the highest index
+ report_lint(
+ cx,
+ full_span,
+ "indexing into a slice multiple times without an `assert`",
+ indexes,
+ |diag| {
+ diag.help(format!(
+ "consider asserting the length before indexing: `assert!({}.len() > {highest_index});`",
+ snippet(cx, slice.span, "..")
+ ));
+ },
+ );
+ },
+ _ => {},
+ }
+ }
+ }
+}
+
+impl LateLintPass<'_> for MissingAssertsForIndexing {
+ fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
+ let mut map = UnhashMap::default();
+
+ for_each_expr(block, |expr| {
+ check_index(cx, expr, &mut map);
+ check_assert(cx, expr, &mut map);
+ ControlFlow::<!, ()>::Continue(())
+ });
+
+ report_indexes(cx, &map);
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
index 3b7eccad7..f598a65d2 100644
--- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
+++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
@@ -7,7 +7,6 @@ use rustc_hir as hir;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, Constness, FnDecl, GenericParamKind};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -124,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
FnKind::Method(_, sig, ..) => {
if trait_ref_of_method(cx, def_id).is_some()
|| already_const(sig.header)
- || method_accepts_droppable(cx, sig.decl.inputs)
+ || method_accepts_droppable(cx, def_id)
{
return;
}
@@ -165,12 +164,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
/// Returns true if any of the method parameters is a type that implements `Drop`. The method
/// can't be made const then, because `drop` can't be const-evaluated.
-fn method_accepts_droppable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool {
+fn method_accepts_droppable(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
+ let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
+
// If any of the params are droppable, return true
- param_tys.iter().any(|hir_ty| {
- let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);
- has_drop(cx, ty_ty)
- })
+ sig.inputs().iter().any(|&ty| has_drop(cx, ty))
}
// We don't have to lint on something that's already `const`
diff --git a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
new file mode 100644
index 000000000..d55c77a92
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
@@ -0,0 +1,410 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
+use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{expr_use_ctxt, peel_n_hir_expr_refs, DefinedTy, ExprUseNode};
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::{Body, Expr, ExprKind, Mutability, Path, QPath};
+use rustc_index::bit_set::BitSet;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::{Rvalue, StatementKind};
+use rustc_middle::ty::{
+ self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, List, ParamTy, ProjectionPredicate, Ty,
+};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
+use rustc_trait_selection::traits::{Obligation, ObligationCause};
+use std::collections::VecDeque;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for borrow operations (`&`) that used as a generic argument to a
+ /// function when the borrowed value could be used.
+ ///
+ /// ### Why is this bad?
+ /// Suggests that the receiver of the expression borrows
+ /// the expression.
+ ///
+ /// ### Known problems
+ /// The lint cannot tell when the implementation of a trait
+ /// for `&T` and `T` do different things. Removing a borrow
+ /// in such a case can change the semantics of the code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn f(_: impl AsRef<str>) {}
+ ///
+ /// let x = "foo";
+ /// f(&x);
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// fn f(_: impl AsRef<str>) {}
+ ///
+ /// let x = "foo";
+ /// f(x);
+ /// ```
+ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_BORROWS_FOR_GENERIC_ARGS,
+ style,
+ "taking a reference that is going to be automatically dereferenced"
+}
+
+pub struct NeedlessBorrowsForGenericArgs<'tcx> {
+ /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
+ /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
+ /// be moved.
+ possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+
+ // `IntoIterator` for arrays requires Rust 1.53.
+ msrv: Msrv,
+}
+impl_lint_pass!(NeedlessBorrowsForGenericArgs<'_> => [NEEDLESS_BORROWS_FOR_GENERIC_ARGS]);
+
+impl NeedlessBorrowsForGenericArgs<'_> {
+ #[must_use]
+ pub fn new(msrv: Msrv) -> Self {
+ Self {
+ possible_borrowers: Vec::new(),
+ msrv,
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if matches!(expr.kind, ExprKind::AddrOf(..))
+ && !expr.span.from_expansion()
+ && let Some(use_cx) = expr_use_ctxt(cx, expr)
+ && !use_cx.is_ty_unified
+ && let Some(DefinedTy::Mir(ty)) = use_cx.node.defined_ty(cx)
+ && let ty::Param(ty) = *ty.value.skip_binder().kind()
+ && let Some((hir_id, fn_id, i)) = match use_cx.node {
+ ExprUseNode::MethodArg(_, _, 0) => None,
+ ExprUseNode::MethodArg(hir_id, None, i) => {
+ cx.typeck_results().type_dependent_def_id(hir_id).map(|id| (hir_id, id, i))
+ },
+ ExprUseNode::FnArg(&Expr { kind: ExprKind::Path(ref p), hir_id, .. }, i)
+ if !path_has_args(p) => match cx.typeck_results().qpath_res(p, hir_id) {
+ Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) => {
+ Some((hir_id, id, i))
+ },
+ _ => None,
+ },
+ _ => None,
+ } && let count = needless_borrow_count(
+ cx,
+ &mut self.possible_borrowers,
+ fn_id,
+ cx.typeck_results().node_args(hir_id),
+ i,
+ ty,
+ expr,
+ &self.msrv,
+ ) && count != 0
+ {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_BORROWS_FOR_GENERIC_ARGS,
+ expr.span,
+ "the borrowed expression implements the required traits",
+ |diag| {
+ let mut app = Applicability::MachineApplicable;
+ let snip_span = peel_n_hir_expr_refs(expr, count).0.span;
+ let snip = snippet_with_context(cx, snip_span, expr.span.ctxt(), "..", &mut app).0;
+ diag.span_suggestion(expr.span, "change this to", snip.into_owned(), app);
+ }
+ );
+ }
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
+ local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
+ }) {
+ self.possible_borrowers.pop();
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+fn path_has_args(p: &QPath<'_>) -> bool {
+ match *p {
+ QPath::Resolved(_, Path { segments: [.., s], .. }) | QPath::TypeRelative(_, s) => s.args.is_some(),
+ _ => false,
+ }
+}
+
+/// Checks for the number of borrow expressions which can be removed from the given expression
+/// where the expression is used as an argument to a function expecting a generic type.
+///
+/// The following constraints will be checked:
+/// * The borrowed expression meets all the generic type's constraints.
+/// * The generic type appears only once in the functions signature.
+/// * The borrowed value will not be moved if it is used later in the function.
+#[expect(clippy::too_many_arguments)]
+fn needless_borrow_count<'tcx>(
+ cx: &LateContext<'tcx>,
+ possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+ fn_id: DefId,
+ callee_args: &'tcx List<GenericArg<'tcx>>,
+ arg_index: usize,
+ param_ty: ParamTy,
+ mut expr: &Expr<'tcx>,
+ msrv: &Msrv,
+) -> usize {
+ let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
+ let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
+
+ let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity().skip_binder();
+ let predicates = cx.tcx.param_env(fn_id).caller_bounds();
+ let projection_predicates = predicates
+ .iter()
+ .filter_map(|predicate| {
+ if let ClauseKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ Some(projection_predicate)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let mut trait_with_ref_mut_self_method = false;
+
+ // If no traits were found, or only the `Destruct`, `Sized`, or `Any` traits were found, return.
+ if predicates
+ .iter()
+ .filter_map(|predicate| {
+ if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && trait_predicate.trait_ref.self_ty() == param_ty.to_ty(cx.tcx)
+ {
+ Some(trait_predicate.trait_ref.def_id)
+ } else {
+ None
+ }
+ })
+ .inspect(|trait_def_id| {
+ trait_with_ref_mut_self_method |= has_ref_mut_self_method(cx, *trait_def_id);
+ })
+ .all(|trait_def_id| {
+ Some(trait_def_id) == destruct_trait_def_id
+ || Some(trait_def_id) == sized_trait_def_id
+ || cx.tcx.is_diagnostic_item(sym::Any, trait_def_id)
+ })
+ {
+ return 0;
+ }
+
+ // See:
+ // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1289294201
+ // - https://github.com/rust-lang/rust-clippy/pull/9674#issuecomment-1292225232
+ if projection_predicates
+ .iter()
+ .any(|projection_predicate| is_mixed_projection_predicate(cx, fn_id, projection_predicate))
+ {
+ return 0;
+ }
+
+ // `args_with_referent_ty` can be constructed outside of `check_referent` because the same
+ // elements are modified each time `check_referent` is called.
+ let mut args_with_referent_ty = callee_args.to_vec();
+
+ let mut check_reference_and_referent = |reference, referent| {
+ let referent_ty = cx.typeck_results().expr_ty(referent);
+
+ if !is_copy(cx, referent_ty)
+ && (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
+ || !referent_used_exactly_once(cx, possible_borrowers, reference))
+ {
+ return false;
+ }
+
+ // https://github.com/rust-lang/rust-clippy/pull/9136#pullrequestreview-1037379321
+ if trait_with_ref_mut_self_method && !matches!(referent_ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
+ return false;
+ }
+
+ if !replace_types(
+ cx,
+ param_ty,
+ referent_ty,
+ fn_sig,
+ arg_index,
+ &projection_predicates,
+ &mut args_with_referent_ty,
+ ) {
+ return false;
+ }
+
+ predicates.iter().all(|predicate| {
+ if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder()
+ && cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_predicate.trait_ref.def_id)
+ && let ty::Param(param_ty) = trait_predicate.self_ty().kind()
+ && let GenericArgKind::Type(ty) = args_with_referent_ty[param_ty.index as usize].unpack()
+ && ty.is_array()
+ && !msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
+ {
+ return false;
+ }
+
+ let predicate = EarlyBinder::bind(predicate).instantiate(cx.tcx, &args_with_referent_ty);
+ let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate);
+ let infcx = cx.tcx.infer_ctxt().build();
+ infcx.predicate_must_hold_modulo_regions(&obligation)
+ })
+ };
+
+ let mut count = 0;
+ while let ExprKind::AddrOf(_, _, referent) = expr.kind {
+ if !check_reference_and_referent(expr, referent) {
+ break;
+ }
+ expr = referent;
+ count += 1;
+ }
+ count
+}
+
+fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
+ cx.tcx
+ .associated_items(trait_def_id)
+ .in_definition_order()
+ .any(|assoc_item| {
+ if assoc_item.fn_has_self_parameter {
+ let self_ty = cx
+ .tcx
+ .fn_sig(assoc_item.def_id)
+ .instantiate_identity()
+ .skip_binder()
+ .inputs()[0];
+ matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut))
+ } else {
+ false
+ }
+ })
+}
+
+fn is_mixed_projection_predicate<'tcx>(
+ cx: &LateContext<'tcx>,
+ callee_def_id: DefId,
+ projection_predicate: &ProjectionPredicate<'tcx>,
+) -> bool {
+ let generics = cx.tcx.generics_of(callee_def_id);
+ // The predicate requires the projected type to equal a type parameter from the parent context.
+ if let Some(term_ty) = projection_predicate.term.ty()
+ && let ty::Param(term_param_ty) = term_ty.kind()
+ && (term_param_ty.index as usize) < generics.parent_count
+ {
+ // The inner-most self type is a type parameter from the current function.
+ let mut projection_ty = projection_predicate.projection_ty;
+ loop {
+ match projection_ty.self_ty().kind() {
+ ty::Alias(ty::Projection, inner_projection_ty) => {
+ projection_ty = *inner_projection_ty;
+ }
+ ty::Param(param_ty) => {
+ return (param_ty.index as usize) >= generics.parent_count;
+ }
+ _ => {
+ return false;
+ }
+ }
+ }
+ } else {
+ false
+ }
+}
+
+fn referent_used_exactly_once<'tcx>(
+ cx: &LateContext<'tcx>,
+ possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
+ reference: &Expr<'tcx>,
+) -> bool {
+ if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id)
+ && let Some(local) = expr_local(cx.tcx, reference)
+ && let [location] = *local_assignments(mir, local).as_slice()
+ && let block_data = &mir.basic_blocks[location.block]
+ && let Some(statement) = block_data.statements.get(location.statement_index)
+ && let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
+ && !place.is_indirect_first_projection()
+ {
+ let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
+ if possible_borrowers
+ .last()
+ .map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
+ {
+ possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
+ }
+ let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
+ // If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
+ // that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
+ // itself. See the comment in that method for an explanation as to why.
+ possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
+ && used_exactly_once(mir, place.local).unwrap_or(false)
+ } else {
+ false
+ }
+}
+
+// Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting
+// projected type that is a type parameter. Returns `false` if replacing the types would have an
+// effect on the function signature beyond substituting `new_ty` for `param_ty`.
+// See: https://github.com/rust-lang/rust-clippy/pull/9136#discussion_r927212757
+fn replace_types<'tcx>(
+ cx: &LateContext<'tcx>,
+ param_ty: ParamTy,
+ new_ty: Ty<'tcx>,
+ fn_sig: FnSig<'tcx>,
+ arg_index: usize,
+ projection_predicates: &[ProjectionPredicate<'tcx>],
+ args: &mut [ty::GenericArg<'tcx>],
+) -> bool {
+ let mut replaced = BitSet::new_empty(args.len());
+
+ let mut deque = VecDeque::with_capacity(args.len());
+ deque.push_back((param_ty, new_ty));
+
+ while let Some((param_ty, new_ty)) = deque.pop_front() {
+ // If `replaced.is_empty()`, then `param_ty` and `new_ty` are those initially passed in.
+ if !fn_sig
+ .inputs_and_output
+ .iter()
+ .enumerate()
+ .all(|(i, ty)| (replaced.is_empty() && i == arg_index) || !ty.contains(param_ty.to_ty(cx.tcx)))
+ {
+ return false;
+ }
+
+ args[param_ty.index as usize] = ty::GenericArg::from(new_ty);
+
+ // The `replaced.insert(...)` check provides some protection against infinite loops.
+ if replaced.insert(param_ty.index) {
+ for projection_predicate in projection_predicates {
+ if projection_predicate.projection_ty.self_ty() == param_ty.to_ty(cx.tcx)
+ && let Some(term_ty) = projection_predicate.term.ty()
+ && let ty::Param(term_param_ty) = term_ty.kind()
+ {
+ let projection = cx.tcx.mk_ty_from_kind(ty::Alias(
+ ty::Projection,
+ projection_predicate.projection_ty.with_self_ty(cx.tcx, new_ty),
+ ));
+
+ if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection)
+ && args[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty)
+ {
+ deque.push_back((*term_param_ty, projected_ty));
+ }
+ }
+ }
+ }
+ }
+
+ true
+}
diff --git a/src/tools/clippy/clippy_lints/src/needless_else.rs b/src/tools/clippy/clippy_lints/src/needless_else.rs
index 03bab86c6..0c1fe881f 100644
--- a/src/tools/clippy/clippy_lints/src/needless_else.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs
index 7f0a5964a..3ad9ae030 100644
--- a/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_ref_mut.rs
@@ -1,6 +1,7 @@
use super::needless_pass_by_value::requires_exact_signature;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
+use clippy_utils::visitors::for_each_expr_with_closures;
use clippy_utils::{get_parent_node, inherits_cfg, is_from_proc_macro, is_self};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
@@ -9,7 +10,7 @@ use rustc_hir::{
Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath,
};
use rustc_hir_typeck::expr_use_visitor as euv;
-use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::associated_body;
use rustc_middle::hir::nested_filter::OnlyBodies;
@@ -21,6 +22,8 @@ use rustc_span::symbol::kw;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
+use core::ops::ControlFlow;
+
declare_clippy_lint! {
/// ### What it does
/// Check if a `&mut` function argument is actually used mutably.
@@ -95,6 +98,30 @@ fn should_skip<'tcx>(
is_from_proc_macro(cx, &input)
}
+fn check_closures<'tcx>(
+ ctx: &mut MutablyUsedVariablesCtxt<'tcx>,
+ cx: &LateContext<'tcx>,
+ infcx: &InferCtxt<'tcx>,
+ checked_closures: &mut FxHashSet<LocalDefId>,
+ closures: FxHashSet<LocalDefId>,
+) {
+ let hir = cx.tcx.hir();
+ for closure in closures {
+ if !checked_closures.insert(closure) {
+ continue;
+ }
+ ctx.prev_bind = None;
+ ctx.prev_move_to_closure.clear();
+ if let Some(body) = hir
+ .find_by_def_id(closure)
+ .and_then(associated_body)
+ .map(|(_, body_id)| hir.body(body_id))
+ {
+ euv::ExprUseVisitor::new(ctx, infcx, closure, cx.param_env, cx.typeck_results()).consume_body(body);
+ }
+ }
+}
+
impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
fn check_fn(
&mut self,
@@ -161,25 +188,22 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
if is_async {
let mut checked_closures = FxHashSet::default();
+
+ // We retrieve all the closures declared in the async function because they will
+ // not be found by `euv::Delegate`.
+ let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
+ for_each_expr_with_closures(cx, body, |expr| {
+ if let ExprKind::Closure(closure) = expr.kind {
+ closures.insert(closure.def_id);
+ }
+ ControlFlow::<()>::Continue(())
+ });
+ check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
+
while !ctx.async_closures.is_empty() {
- let closures = ctx.async_closures.clone();
+ let async_closures = ctx.async_closures.clone();
ctx.async_closures.clear();
- let hir = cx.tcx.hir();
- for closure in closures {
- if !checked_closures.insert(closure) {
- continue;
- }
- ctx.prev_bind = None;
- ctx.prev_move_to_closure.clear();
- if let Some(body) = hir
- .find_by_def_id(closure)
- .and_then(associated_body)
- .map(|(_, body_id)| hir.body(body_id))
- {
- euv::ExprUseVisitor::new(&mut ctx, &infcx, closure, cx.param_env, cx.typeck_results())
- .consume_body(body);
- }
- }
+ check_closures(&mut ctx, cx, &infcx, &mut checked_closures, async_closures);
}
}
ctx
@@ -244,6 +268,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
struct MutablyUsedVariablesCtxt<'tcx> {
mutably_used_vars: HirIdSet,
prev_bind: Option<HirId>,
+ /// In async functions, the inner AST is composed of multiple layers until we reach the code
+ /// defined by the user. Because of that, some variables are marked as mutably borrowed even
+ /// though they're not. This field lists the `HirId` that should not be considered as mutable
+ /// use of a variable.
prev_move_to_closure: HirIdSet,
aliases: HirIdMap<HirId>,
async_closures: FxHashSet<LocalDefId>,
@@ -308,7 +336,12 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
self.prev_bind = None;
if let euv::Place {
- base: euv::PlaceBase::Local(vid),
+ base:
+ euv::PlaceBase::Local(vid)
+ | euv::PlaceBase::Upvar(UpvarId {
+ var_path: UpvarPath { hir_id: vid },
+ ..
+ }),
base_ty,
..
} = &cmt.place
diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs
index cf7cd671d..f7f9dccfb 100644
--- a/src/tools/clippy/clippy_lints/src/new_without_default.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs
index 5f2a324b0..aee184252 100644
--- a/src/tools/clippy/clippy_lints/src/no_effect.rs
+++ b/src/tools/clippy/clippy_lints/src/no_effect.rs
@@ -5,10 +5,8 @@ use clippy_utils::{get_parent_node, is_lint_allowed, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{
- is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, FnRetTy, ItemKind, Node, PatKind, Stmt, StmtKind,
- UnsafeSource,
+ is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, ItemKind, Node, PatKind, Stmt, StmtKind, UnsafeSource,
};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::TyCtxtInferExt as _;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@@ -99,14 +97,13 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|diag| {
for parent in cx.tcx.hir().parent_iter(stmt.hir_id) {
if let Node::Item(item) = parent.1
- && let ItemKind::Fn(sig, ..) = item.kind
- && let FnRetTy::Return(ret_ty) = sig.decl.output
+ && let ItemKind::Fn(..) = item.kind
&& let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id)
&& let [.., final_stmt] = block.stmts
&& final_stmt.hir_id == stmt.hir_id
{
let expr_ty = cx.typeck_results().expr_ty(expr);
- let mut ret_ty = hir_ty_to_ty(cx.tcx, ret_ty);
+ let mut ret_ty = cx.tcx.fn_sig(item.owner_id).instantiate_identity().output().skip_binder();
// Remove `impl Future<Output = T>` to get `T`
if cx.tcx.ty_is_opaque_future(ret_ty) &&
@@ -115,7 +112,7 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
ret_ty = true_ret_ty;
}
- if ret_ty == expr_ty {
+ if !ret_ty.is_unit() && ret_ty == expr_ty {
diag.span_suggestion(
stmt.span.shrink_to_lo(),
"did you mean to return it?",
diff --git a/src/tools/clippy/clippy_lints/src/incorrect_impls.rs b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs
index 3c59b839a..20b4b4f03 100644
--- a/src/tools/clippy/clippy_lints/src/incorrect_impls.rs
+++ b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs
@@ -4,7 +4,7 @@ use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_def_path, path_res, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::def_id::LocalDefId;
-use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp};
+use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::EarlyBinder;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -13,11 +13,12 @@ use rustc_span::symbol::kw;
declare_clippy_lint! {
/// ### What it does
- /// Checks for manual implementations of `Clone` when `Copy` is already implemented.
+ /// Checks for non-canonical implementations of `Clone` when `Copy` is already implemented.
///
/// ### Why is this bad?
- /// If both `Clone` and `Copy` are implemented, they must agree. This is done by dereferencing
- /// `self` in `Clone`'s implementation. Anything else is incorrect.
+ /// If both `Clone` and `Copy` are implemented, they must agree. This can done by dereferencing
+ /// `self` in `Clone`'s implementation, which will avoid any possibility of the implementations
+ /// becoming out of sync.
///
/// ### Example
/// ```rust,ignore
@@ -46,14 +47,13 @@ declare_clippy_lint! {
/// impl Copy for A {}
/// ```
#[clippy::version = "1.72.0"]
- pub INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
- correctness,
- "manual implementation of `Clone` on a `Copy` type"
+ pub NON_CANONICAL_CLONE_IMPL,
+ suspicious,
+ "non-canonical implementation of `Clone` on a `Copy` type"
}
declare_clippy_lint! {
/// ### What it does
- /// Checks for manual implementations of both `PartialOrd` and `Ord` when only `Ord` is
- /// necessary.
+ /// Checks for non-canonical implementations of `PartialOrd` when `Ord` is already implemented.
///
/// ### Why is this bad?
/// If both `PartialOrd` and `Ord` are implemented, they must agree. This is commonly done by
@@ -61,11 +61,8 @@ declare_clippy_lint! {
/// introduce an error upon refactoring.
///
/// ### Known issues
- /// Code that calls the `.into()` method instead will be flagged as incorrect, despite `.into()`
- /// wrapping it in `Some`.
- ///
- /// ### Limitations
- /// Will not lint if `Self` and `Rhs` do not have the same type.
+ /// Code that calls the `.into()` method instead will be flagged, despite `.into()` wrapping it
+ /// in `Some`.
///
/// ### Example
/// ```rust
@@ -107,13 +104,13 @@ declare_clippy_lint! {
/// }
/// ```
#[clippy::version = "1.72.0"]
- pub INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE,
- correctness,
- "manual implementation of `PartialOrd` when `Ord` is already implemented"
+ pub NON_CANONICAL_PARTIAL_ORD_IMPL,
+ suspicious,
+ "non-canonical implementation of `PartialOrd` on an `Ord` type"
}
-declare_lint_pass!(IncorrectImpls => [INCORRECT_CLONE_IMPL_ON_COPY_TYPE, INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE]);
+declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
-impl LateLintPass<'_> for IncorrectImpls {
+impl LateLintPass<'_> for NonCanonicalImpls {
#[expect(clippy::too_many_lines)]
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
let Some(Node::Item(item)) = get_parent_node(cx.tcx, impl_item.hir_id()) else {
@@ -125,9 +122,6 @@ impl LateLintPass<'_> for IncorrectImpls {
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
return;
}
- let ItemKind::Impl(_) = item.kind else {
- return;
- };
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
return;
};
@@ -154,9 +148,9 @@ impl LateLintPass<'_> for IncorrectImpls {
{} else {
span_lint_and_sugg(
cx,
- INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
+ NON_CANONICAL_CLONE_IMPL,
block.span,
- "incorrect implementation of `clone` on a `Copy` type",
+ "non-canonical implementation of `clone` on a `Copy` type",
"change this to",
"{ *self }".to_owned(),
Applicability::MaybeIncorrect,
@@ -169,9 +163,9 @@ impl LateLintPass<'_> for IncorrectImpls {
if impl_item.ident.name == sym::clone_from {
span_lint_and_sugg(
cx,
- INCORRECT_CLONE_IMPL_ON_COPY_TYPE,
+ NON_CANONICAL_CLONE_IMPL,
impl_item.span,
- "incorrect implementation of `clone_from` on a `Copy` type",
+ "unnecessary implementation of `clone_from` on a `Copy` type",
"remove it",
String::new(),
Applicability::MaybeIncorrect,
@@ -183,17 +177,8 @@ impl LateLintPass<'_> for IncorrectImpls {
if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id)
&& impl_item.ident.name == sym::partial_cmp
- && let Some(ord_def_id) = cx
- .tcx
- .diagnostic_items(trait_impl.def_id.krate)
- .name_to_id
- .get(&sym::Ord)
- && implements_trait(
- cx,
- trait_impl.self_ty(),
- *ord_def_id,
- &[],
- )
+ && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
+ && implements_trait(cx, trait_impl.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
@@ -222,9 +207,9 @@ impl LateLintPass<'_> for IncorrectImpls {
span_lint_and_then(
cx,
- INCORRECT_PARTIAL_ORD_IMPL_ON_ORD_TYPE,
+ NON_CANONICAL_PARTIAL_ORD_IMPL,
item.span,
- "incorrect implementation of `partial_cmp` on an `Ord` type",
+ "non-canonical implementation of `partial_cmp` on an `Ord` type",
|diag| {
let [_, other] = body.params else {
return;
diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs
index 243192385..2b4e3260c 100644
--- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs
+++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs
@@ -13,7 +13,6 @@ use rustc_hir::def_id::DefId;
use rustc_hir::{
BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp,
};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
use rustc_middle::ty::adjustment::Adjust;
@@ -204,7 +203,7 @@ fn is_value_unfrozen_raw<'tcx>(
// similar to 2., but with the a frozen variant) (e.g. borrowing
// `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
// I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
- err == ErrorHandled::TooGeneric
+ matches!(err, ErrorHandled::TooGeneric(..))
},
|val| val.map_or(true, |val| inner(cx, val, ty)),
)
@@ -244,8 +243,8 @@ pub fn const_eval_resolve<'tcx>(
};
tcx.const_eval_global_id_for_typeck(param_env, cid, span)
},
- Ok(None) => Err(ErrorHandled::TooGeneric),
- Err(err) => Err(ErrorHandled::Reported(err.into())),
+ Ok(None) => Err(ErrorHandled::TooGeneric(span.unwrap_or(rustc_span::DUMMY_SP))),
+ Err(err) => Err(ErrorHandled::Reported(err.into(), span.unwrap_or(rustc_span::DUMMY_SP))),
}
}
@@ -297,8 +296,8 @@ declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTER
impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
- if let ItemKind::Const(hir_ty, _generics, body_id) = it.kind {
- let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let ItemKind::Const(.., body_id) = it.kind {
+ let ty = cx.tcx.type_of(it.owner_id).instantiate_identity();
if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
lint(cx, Source::Item { item: it.span });
}
@@ -306,8 +305,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'_>) {
- if let TraitItemKind::Const(hir_ty, body_id_opt) = &trait_item.kind {
- let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let TraitItemKind::Const(_, body_id_opt) = &trait_item.kind {
+ let ty = cx.tcx.type_of(trait_item.owner_id).instantiate_identity();
// Normalize assoc types because ones originated from generic params
// bounded other traits could have their bound.
@@ -333,7 +332,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
- if let ImplItemKind::Const(hir_ty, body_id) = &impl_item.kind {
+ if let ImplItemKind::Const(_, body_id) = &impl_item.kind {
let item_def_id = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id;
let item = cx.tcx.hir().expect_item(item_def_id);
@@ -366,7 +365,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
// we should use here as a frozen variant is a potential to be frozen
// similar to unknown layouts.
// e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
- let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity();
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
if is_unfrozen(cx, normalized);
if is_value_unfrozen_poly(cx, *body_id, normalized);
@@ -381,7 +380,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
}
},
ItemKind::Impl(Impl { of_trait: None, .. }) => {
- let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity();
// Normalize assoc types originated from generic params.
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
index f9108145c..a10aa65e5 100644
--- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
@@ -1,24 +1,25 @@
use super::ARITHMETIC_SIDE_EFFECTS;
use clippy_utils::consts::{constant, constant_simple, Constant};
use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::type_diagnostic_name;
use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::impl_lint_pass;
use rustc_span::source_map::{Span, Spanned};
+use rustc_span::symbol::sym;
use rustc_span::Symbol;
use {rustc_ast as ast, rustc_hir as hir};
-const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[
- ["f32", "f32"],
- ["f64", "f64"],
- ["std::num::Saturating", "*"],
- ["std::num::Wrapping", "*"],
- ["std::string::String", "str"],
-];
+const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[["f32", "f32"], ["f64", "f64"], ["std::string::String", "str"]];
const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
-const INTEGER_METHODS: &[&str] = &["saturating_div", "wrapping_div", "wrapping_rem", "wrapping_rem_euclid"];
+const INTEGER_METHODS: &[Symbol] = &[
+ sym::saturating_div,
+ sym::wrapping_div,
+ sym::wrapping_rem,
+ sym::wrapping_rem_euclid,
+];
#[derive(Debug)]
pub struct ArithmeticSideEffects {
@@ -53,7 +54,7 @@ impl ArithmeticSideEffects {
allowed_unary,
const_span: None,
expr_span: None,
- integer_methods: INTEGER_METHODS.iter().map(|el| Symbol::intern(el)).collect(),
+ integer_methods: INTEGER_METHODS.iter().copied().collect(),
}
}
@@ -86,6 +87,45 @@ impl ArithmeticSideEffects {
self.allowed_unary.contains(ty_string_elem)
}
+ /// Verifies built-in types that have specific allowed operations
+ fn has_specific_allowed_type_and_operation(
+ cx: &LateContext<'_>,
+ lhs_ty: Ty<'_>,
+ op: &Spanned<hir::BinOpKind>,
+ rhs_ty: Ty<'_>,
+ ) -> bool {
+ let is_div_or_rem = matches!(op.node, hir::BinOpKind::Div | hir::BinOpKind::Rem);
+ let is_non_zero_u = |symbol: Option<Symbol>| {
+ matches!(
+ symbol,
+ Some(
+ sym::NonZeroU128
+ | sym::NonZeroU16
+ | sym::NonZeroU32
+ | sym::NonZeroU64
+ | sym::NonZeroU8
+ | sym::NonZeroUsize
+ )
+ )
+ };
+ let is_sat_or_wrap = |ty: Ty<'_>| {
+ let is_sat = type_diagnostic_name(cx, ty) == Some(sym::Saturating);
+ let is_wrap = type_diagnostic_name(cx, ty) == Some(sym::Wrapping);
+ is_sat || is_wrap
+ };
+
+ // If the RHS is NonZeroU*, then division or module by zero will never occur
+ if is_non_zero_u(type_diagnostic_name(cx, rhs_ty)) && is_div_or_rem {
+ return true;
+ }
+ // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module
+ if is_sat_or_wrap(lhs_ty) {
+ return !is_div_or_rem;
+ }
+
+ false
+ }
+
// For example, 8i32 or &i64::MAX.
fn is_integral(ty: Ty<'_>) -> bool {
ty.peel_refs().is_integral()
@@ -147,6 +187,9 @@ impl ArithmeticSideEffects {
if self.has_allowed_binary(lhs_ty, rhs_ty) {
return;
}
+ if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) {
+ return;
+ }
let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op.node {
// At least for integers, shifts are already handled by the CTFE
@@ -272,7 +315,7 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
let body_owner_def_id = cx.tcx.hir().body_owner_def_id(body.id());
let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id);
- if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind {
+ if let hir::BodyOwnerKind::Const { .. } | hir::BodyOwnerKind::Static(_) = body_owner_kind {
let body_span = cx.tcx.hir().span_with_body(body_owner);
if let Some(span) = self.const_span && span.contains(body_span) {
return;
diff --git a/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs b/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs
index f3e0c58a7..bce6bdcaf 100644
--- a/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/float_cmp.rs
@@ -17,7 +17,7 @@ pub(crate) fn check<'tcx>(
left: &'tcx Expr<'_>,
right: &'tcx Expr<'_>,
) {
- if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
+ if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) {
let left_is_local = match constant_with_source(cx, cx.typeck_results(), left) {
Some((c, s)) if !is_allowed(&c) => s.is_local(),
Some(_) => return,
diff --git a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs
index 102845cee..80389cbf8 100644
--- a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs
@@ -72,7 +72,7 @@ impl Context {
let body_owner_def_id = cx.tcx.hir().body_owner_def_id(body.id());
match cx.tcx.hir().body_owner_kind(body_owner_def_id) {
- hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => {
+ hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const { .. } => {
let body_span = cx.tcx.hir().span_with_body(body_owner);
if let Some(span) = self.const_span {
diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs
index 8009b00b4..7dabdcd58 100644
--- a/src/tools/clippy/clippy_lints/src/ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/ptr.rs
@@ -16,7 +16,6 @@ use rustc_hir::{
ImplItemKind, ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind,
TyKind, Unsafety,
};
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_lint::{LateContext, LateLintPass};
@@ -172,13 +171,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
for arg in check_fn_args(
cx,
- cx.tcx
- .fn_sig(item.owner_id)
- .instantiate_identity()
- .skip_binder()
- .inputs(),
+ cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(),
sig.decl.inputs,
- &sig.decl.output,
&[],
)
.filter(|arg| arg.mutability() == Mutability::Not)
@@ -237,7 +231,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
let decl = sig.decl;
let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder();
- let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, &decl.output, body.params)
+ let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params)
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
.collect();
let results = check_ptr_arg_usage(cx, body, &lint_args);
@@ -278,7 +272,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
- const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
+ const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 13] = [
(&paths::SLICE_FROM_RAW_PARTS, &[0]),
(&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
(&paths::PTR_COPY, &[0, 1]),
@@ -291,20 +285,33 @@ fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
(&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
(&paths::PTR_SWAP, &[0, 1]),
(&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
- (&paths::PTR_WRITE, &[0]),
- (&paths::PTR_WRITE_UNALIGNED, &[0]),
- (&paths::PTR_WRITE_VOLATILE, &[0]),
(&paths::PTR_WRITE_BYTES, &[0]),
];
+ let invalid_null_ptr_usage_table_diag_items: [(Option<DefId>, &[usize]); 3] = [
+ (cx.tcx.get_diagnostic_item(sym::ptr_write), &[0]),
+ (cx.tcx.get_diagnostic_item(sym::ptr_write_unaligned), &[0]),
+ (cx.tcx.get_diagnostic_item(sym::ptr_write_volatile), &[0]),
+ ];
if_chain! {
if let ExprKind::Call(fun, args) = expr.kind;
if let ExprKind::Path(ref qpath) = fun.kind;
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
- if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
+ if let Some(arg_indices) = INVALID_NULL_PTR_USAGE_TABLE
.iter()
- .find(|&&(fn_path, _)| fn_path == fun_def_path);
+ .find_map(|&(fn_path, indices)| if fn_path == fun_def_path { Some(indices) } else { None })
+ .or_else(|| {
+ invalid_null_ptr_usage_table_diag_items
+ .iter()
+ .find_map(|&(def_id, indices)| {
+ if def_id == Some(fun_def_id) {
+ Some(indices)
+ } else {
+ None
+ }
+ })
+ });
then {
for &arg_idx in arg_indices {
if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
@@ -430,12 +437,13 @@ impl<'tcx> DerefTy<'tcx> {
#[expect(clippy::too_many_lines)]
fn check_fn_args<'cx, 'tcx: 'cx>(
cx: &'cx LateContext<'tcx>,
- tys: &'tcx [Ty<'tcx>],
+ fn_sig: ty::FnSig<'tcx>,
hir_tys: &'tcx [hir::Ty<'tcx>],
- ret_ty: &'tcx FnRetTy<'tcx>,
params: &'tcx [Param<'tcx>],
) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
- tys.iter()
+ fn_sig
+ .inputs()
+ .iter()
.zip(hir_tys.iter())
.enumerate()
.filter_map(move |(i, (ty, hir_ty))| {
@@ -481,9 +489,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
})
{
if !lifetime.is_anonymous()
- && let FnRetTy::Return(ret_ty) = ret_ty
- && let ret_ty = hir_ty_to_ty(cx.tcx, ret_ty)
- && ret_ty
+ && fn_sig.output()
.walk()
.filter_map(|arg| {
arg.as_region().and_then(|lifetime| {
diff --git a/src/tools/clippy/clippy_lints/src/raw_strings.rs b/src/tools/clippy/clippy_lints/src/raw_strings.rs
index ccabb577c..8a7e48746 100644
--- a/src/tools/clippy/clippy_lints/src/raw_strings.rs
+++ b/src/tools/clippy/clippy_lints/src/raw_strings.rs
@@ -1,7 +1,7 @@
use std::iter::once;
use std::ops::ControlFlow;
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_ast::ast::{Expr, ExprKind};
use rustc_ast::token::LitKind;
@@ -9,6 +9,7 @@ use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{BytePos, Pos, Span};
declare_clippy_lint! {
/// ### What it does
@@ -49,7 +50,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.72.0"]
pub NEEDLESS_RAW_STRING_HASHES,
- style,
+ pedantic,
"suggests reducing the number of hashes around a raw string literal"
}
impl_lint_pass!(RawStrings => [NEEDLESS_RAW_STRINGS, NEEDLESS_RAW_STRING_HASHES]);
@@ -76,17 +77,37 @@ impl EarlyLintPass for RawStrings {
}
if !str.contains(['\\', '"']) {
- span_lint_and_sugg(
+ span_lint_and_then(
cx,
NEEDLESS_RAW_STRINGS,
expr.span,
"unnecessary raw string literal",
- "try",
- format!("{}\"{}\"", prefix.replace('r', ""), lit.symbol),
- Applicability::MachineApplicable,
- );
+ |diag| {
+ let (start, end) = hash_spans(expr.span, prefix, 0, max);
- return;
+ // BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
+ let r_pos = expr.span.lo() + BytePos::from_usize(prefix.len() - 1);
+ let start = start.with_lo(r_pos);
+
+ if end.is_empty() {
+ diag.span_suggestion(
+ start,
+ "use a string literal instead",
+ format!("\"{str}\""),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ diag.multipart_suggestion(
+ "try",
+ vec![(start, String::new()), (end, String::new())],
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ if !matches!(cx.get_lint_level(NEEDLESS_RAW_STRINGS), rustc_lint::Allow) {
+ return;
+ }
}
let req = {
@@ -96,13 +117,6 @@ impl EarlyLintPass for RawStrings {
let num = str.as_bytes().iter().chain(once(&0)).try_fold(0u8, |acc, &b| {
match b {
b'"' if !following_quote => (following_quote, req) = (true, 1),
- // I'm a bit surprised the compiler didn't optimize this out, there's no
- // branch but it still ends up doing an unnecessary comparison, it's:
- // - cmp r9b,1h
- // - sbb cl,-1h
- // which will add 1 if it's true. With this change, it becomes:
- // - add cl,r9b
- // isn't that so much nicer?
b'#' => req += u8::from(following_quote),
_ => {
if following_quote {
@@ -126,18 +140,58 @@ impl EarlyLintPass for RawStrings {
};
if req < max {
- let hashes = "#".repeat(req as usize);
-
- span_lint_and_sugg(
+ span_lint_and_then(
cx,
NEEDLESS_RAW_STRING_HASHES,
expr.span,
"unnecessary hashes around raw string literal",
- "try",
- format!(r#"{prefix}{hashes}"{}"{hashes}"#, lit.symbol),
- Applicability::MachineApplicable,
+ |diag| {
+ let (start, end) = hash_spans(expr.span, prefix, req, max);
+
+ let message = match max - req {
+ _ if req == 0 => "remove all the hashes around the literal".to_string(),
+ 1 => "remove one hash from both sides of the literal".to_string(),
+ n => format!("remove {n} hashes from both sides of the literal"),
+ };
+
+ diag.multipart_suggestion(
+ message,
+ vec![(start, String::new()), (end, String::new())],
+ Applicability::MachineApplicable,
+ );
+ },
);
}
}
}
}
+
+/// Returns spans pointing at the unneeded hashes, e.g. for a `req` of `1` and `max` of `3`:
+///
+/// ```ignore
+/// r###".."###
+/// ^^ ^^
+/// ```
+fn hash_spans(literal_span: Span, prefix: &str, req: u8, max: u8) -> (Span, Span) {
+ let literal_span = literal_span.data();
+
+ // BytePos: we checked prefix appears literally in the source text
+ let hash_start = literal_span.lo + BytePos::from_usize(prefix.len());
+ let hash_end = literal_span.hi;
+
+ // BytePos: req/max are counts of the ASCII character #
+ let start = Span::new(
+ hash_start + BytePos(req.into()),
+ hash_start + BytePos(max.into()),
+ literal_span.ctxt,
+ None,
+ );
+ let end = Span::new(
+ hash_end - BytePos(req.into()),
+ hash_end - BytePos(max.into()),
+ literal_span.ctxt,
+ None,
+ );
+
+ (start, end)
+}
diff --git a/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs b/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs
index fc49b58e0..f42836611 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs
+++ b/src/tools/clippy/clippy_lints/src/redundant_closure_call.rs
@@ -10,6 +10,7 @@ use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
@@ -84,7 +85,7 @@ fn find_innermost_closure<'tcx>(
cx: &LateContext<'tcx>,
mut expr: &'tcx hir::Expr<'tcx>,
mut steps: usize,
-) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, hir::IsAsync)> {
+) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, ty::Asyncness)> {
let mut data = None;
while let hir::ExprKind::Closure(closure) = expr.kind
@@ -98,9 +99,9 @@ fn find_innermost_closure<'tcx>(
{
expr = body.value;
data = Some((body.value, closure.fn_decl, if is_async_closure(body) {
- hir::IsAsync::Async
+ ty::Asyncness::Yes
} else {
- hir::IsAsync::NotAsync
+ ty::Asyncness::No
}));
steps -= 1;
}
diff --git a/src/tools/clippy/clippy_lints/src/redundant_type_annotations.rs b/src/tools/clippy/clippy_lints/src/redundant_type_annotations.rs
index 3e963d798..1d4fdb43a 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_type_annotations.rs
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/renamed_lints.rs b/src/tools/clippy/clippy_lints/src/renamed_lints.rs
index fc1fabcc0..613f1ecc6 100644
--- a/src/tools/clippy/clippy_lints/src/renamed_lints.rs
+++ b/src/tools/clippy/clippy_lints/src/renamed_lints.rs
@@ -15,6 +15,8 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
("clippy::identity_conversion", "clippy::useless_conversion"),
("clippy::if_let_some_result", "clippy::match_result_ok"),
+ ("clippy::incorrect_clone_impl_on_copy_type", "clippy::non_canonical_clone_impl"),
+ ("clippy::incorrect_partial_ord_impl_on_ord_type", "clippy::non_canonical_partial_ord_impl"),
("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"),
("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
("clippy::new_without_default_derive", "clippy::new_without_default"),
@@ -38,12 +40,12 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::drop_bounds", "drop_bounds"),
("clippy::drop_copy", "dropping_copy_types"),
("clippy::drop_ref", "dropping_references"),
+ ("clippy::fn_null_check", "useless_ptr_null_checks"),
("clippy::for_loop_over_option", "for_loops_over_fallibles"),
("clippy::for_loop_over_result", "for_loops_over_fallibles"),
("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"),
("clippy::forget_copy", "forgetting_copy_types"),
("clippy::forget_ref", "forgetting_references"),
- ("clippy::fn_null_check", "useless_ptr_null_checks"),
("clippy::into_iter_on_array", "array_into_iter"),
("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
("clippy::invalid_ref", "invalid_value"),
diff --git a/src/tools/clippy/clippy_lints/src/reserve_after_initialization.rs b/src/tools/clippy/clippy_lints/src/reserve_after_initialization.rs
new file mode 100644
index 000000000..0c8c904e3
--- /dev/null
+++ b/src/tools/clippy/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/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs
index c9ab622ad..9db18c297 100644
--- a/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs
+++ b/src/tools/clippy/clippy_lints/src/slow_vector_initialization.rs
@@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::macros::root_macro_call;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local,
@@ -148,6 +149,15 @@ impl SlowVectorInit {
/// - `Some(InitializedSize::Uninitialized)` for `Vec::new()`
/// - `None` for other, unrelated kinds of expressions
fn as_vec_initializer<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<InitializedSize<'tcx>> {
+ // Generally don't warn if the vec initializer comes from an expansion, except for the vec! macro.
+ // This lets us still warn on `vec![]`, while ignoring other kinds of macros that may output an
+ // empty vec
+ if expr.span.from_expansion()
+ && root_macro_call(expr.span).map(|m| m.def_id) != cx.tcx.get_diagnostic_item(sym::vec_macro)
+ {
+ return None;
+ }
+
if let ExprKind::Call(func, [len_expr]) = expr.kind
&& is_expr_path_def_path(cx, func, &paths::VEC_WITH_CAPACITY)
{
@@ -205,7 +215,7 @@ impl SlowVectorInit {
span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
diag.span_suggestion(
- vec_alloc.allocation_expr.span,
+ vec_alloc.allocation_expr.span.source_callsite(),
"consider replacing this with",
format!("vec![0; {len_expr}]"),
Applicability::Unspecified,
diff --git a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
index f23916527..5f54a10d1 100644
--- a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
+++ b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
@@ -1,4 +1,5 @@
-use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::def_id::DefId;
use rustc_hir::{HirId, Path, PathSegment};
@@ -99,17 +100,17 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
&& let Some(first_segment) = get_first_segment(path)
&& is_stable(cx, def_id)
{
- let (lint, msg, help) = match first_segment.ident.name {
+ let (lint, used_mod, replace_with) = match first_segment.ident.name {
sym::std => match cx.tcx.crate_name(def_id.krate) {
sym::core => (
STD_INSTEAD_OF_CORE,
- "used import from `std` instead of `core`",
- "consider importing the item from `core`",
+ "std",
+ "core",
),
sym::alloc => (
STD_INSTEAD_OF_ALLOC,
- "used import from `std` instead of `alloc`",
- "consider importing the item from `alloc`",
+ "std",
+ "alloc",
),
_ => {
self.prev_span = path.span;
@@ -120,8 +121,8 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
if cx.tcx.crate_name(def_id.krate) == sym::core {
(
ALLOC_INSTEAD_OF_CORE,
- "used import from `alloc` instead of `core`",
- "consider importing the item from `core`",
+ "alloc",
+ "core",
)
} else {
self.prev_span = path.span;
@@ -131,7 +132,14 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
_ => return,
};
if path.span != self.prev_span {
- span_lint_and_help(cx, lint, path.span, msg, None, help);
+ span_lint_and_sugg(
+ cx,
+ lint,
+ first_segment.ident.span,
+ &format!("used import from `{used_mod}` instead of `{replace_with}`"),
+ &format!("consider importing the item from `{replace_with}`"),
+ replace_with.to_string(),
+ Applicability::MachineApplicable);
self.prev_span = path.span;
}
}
diff --git a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
index 23d6e2a84..d10f10ef8 100644
--- a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
+++ b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
@@ -586,7 +586,7 @@ fn ident_difference_expr_with_base_location(
| (ForLoop(_, _, _, _), ForLoop(_, _, _, _))
| (While(_, _, _), While(_, _, _))
| (If(_, _, _), If(_, _, _))
- | (Let(_, _, _), Let(_, _, _))
+ | (Let(_, _, _, _), Let(_, _, _, _))
| (Type(_, _), Type(_, _))
| (Cast(_, _), Cast(_, _))
| (Lit(_), Lit(_))
diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_null_to_fn.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_null_to_fn.rs
index 4944381da..b26365e34 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/transmute_null_to_fn.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_null_to_fn.rs
@@ -28,35 +28,43 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
return false;
}
- match arg.kind {
+ let casts_peeled = peel_casts(arg);
+ match casts_peeled.kind {
// Catching:
// transmute over constants that resolve to `null`.
- ExprKind::Path(ref _qpath) if matches!(constant(cx, cx.typeck_results(), arg), Some(Constant::RawPtr(0))) => {
+ ExprKind::Path(ref _qpath)
+ if matches!(
+ constant(cx, cx.typeck_results(), casts_peeled),
+ Some(Constant::RawPtr(0))
+ ) =>
+ {
lint_expr(cx, expr);
true
},
-
- // Catching:
- // `std::mem::transmute(0 as *const i32)`
- ExprKind::Cast(inner_expr, _cast_ty) if is_integer_literal(inner_expr, 0) => {
- lint_expr(cx, expr);
- true
- },
-
// Catching:
// `std::mem::transmute(std::ptr::null::<i32>())`
ExprKind::Call(func1, []) if is_path_diagnostic_item(cx, func1, sym::ptr_null) => {
lint_expr(cx, expr);
true
},
-
_ => {
// FIXME:
// Also catch transmutations of variables which are known nulls.
// To do this, MIR const propagation seems to be the better tool.
// Whenever MIR const prop routines are more developed, this will
// become available. As of this writing (25/03/19) it is not yet.
+ if is_integer_literal(casts_peeled, 0) {
+ lint_expr(cx, expr);
+ return true;
+ }
false
},
}
}
+
+fn peel_casts<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+ match &expr.kind {
+ ExprKind::Cast(inner_expr, _) => peel_casts(inner_expr),
+ _ => expr,
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs b/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs
index 78ad52d8a..c12519d72 100644
--- a/src/tools/clippy/clippy_lints/src/tuple_array_conversions.rs
+++ b/src/tools/clippy/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,
}
diff --git a/src/tools/clippy/clippy_lints/src/types/mod.rs b/src/tools/clippy/clippy_lints/src/types/mod.rs
index 79f9d45d5..71a4b3fba 100644
--- a/src/tools/clippy/clippy_lints/src/types/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/types/mod.rs
@@ -315,7 +315,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
fn check_fn(
&mut self,
cx: &LateContext<'_>,
- _: FnKind<'_>,
+ fn_kind: FnKind<'_>,
decl: &FnDecl<'_>,
_: &Body<'_>,
_: Span,
@@ -340,6 +340,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
CheckTyContext {
is_in_trait_impl,
is_exported,
+ in_body: matches!(fn_kind, FnKind::Closure),
..CheckTyContext::default()
},
);
@@ -427,7 +428,7 @@ impl<'tcx> LateLintPass<'tcx> for Types {
cx,
ty,
CheckTyContext {
- is_local: true,
+ in_body: true,
..CheckTyContext::default()
},
);
@@ -481,7 +482,7 @@ impl Types {
}
match hir_ty.kind {
- TyKind::Path(ref qpath) if !context.is_local => {
+ TyKind::Path(ref qpath) if !context.in_body => {
let hir_id = hir_ty.hir_id;
let res = cx.qpath_res(qpath, hir_id);
if let Some(def_id) = res.opt_def_id() {
@@ -581,8 +582,8 @@ impl Types {
#[derive(Clone, Copy, Default)]
struct CheckTyContext {
is_in_trait_impl: bool,
- /// `true` for types on local variables.
- is_local: bool,
+ /// `true` for types on local variables and in closure signatures.
+ in_body: bool,
/// `true` for types that are part of the public API.
is_exported: bool,
is_nested_call: bool,
diff --git a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
index f2ef60201..6193fdeb4 100644
--- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
+++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
@@ -12,7 +12,7 @@ use rustc_lexer::{tokenize, TokenKind};
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::{BytePos, Pos, Span, SyntaxContext};
+use rustc_span::{BytePos, Pos, RelativeBytePos, Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@@ -341,44 +341,21 @@ fn block_parents_have_safety_comment(
id: hir::HirId,
) -> bool {
if let Some(node) = get_parent_node(cx.tcx, id) {
- return match node {
- Node::Expr(expr) => {
- if let Some(
- Node::Local(hir::Local { span, .. })
- | Node::Item(hir::Item {
- kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
- span,
- ..
- }),
- ) = get_parent_node(cx.tcx, expr.hir_id)
- {
- let hir_id = match get_parent_node(cx.tcx, expr.hir_id) {
- Some(Node::Local(hir::Local { hir_id, .. })) => *hir_id,
- Some(Node::Item(hir::Item { owner_id, .. })) => {
- cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id)
- },
- _ => unreachable!(),
- };
-
- // if unsafe block is part of a let/const/static statement,
- // and accept_comment_above_statement is set to true
- // we accept the safety comment in the line the precedes this statement.
- accept_comment_above_statement
- && span_with_attrs_in_body_has_safety_comment(
- cx,
- *span,
- hir_id,
- accept_comment_above_attributes,
- )
- } else {
- !is_branchy(expr)
- && span_with_attrs_in_body_has_safety_comment(
- cx,
- expr.span,
- expr.hir_id,
- accept_comment_above_attributes,
- )
- }
+ let (span, hir_id) = match node {
+ Node::Expr(expr) => match get_parent_node(cx.tcx, expr.hir_id) {
+ Some(Node::Local(hir::Local { span, hir_id, .. })) => (*span, *hir_id),
+ Some(Node::Item(hir::Item {
+ kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
+ span,
+ owner_id,
+ ..
+ })) => (*span, cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id)),
+ _ => {
+ if is_branchy(expr) {
+ return false;
+ }
+ (expr.span, expr.hir_id)
+ },
},
Node::Stmt(hir::Stmt {
kind:
@@ -387,28 +364,27 @@ fn block_parents_have_safety_comment(
| hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
..
})
- | Node::Local(hir::Local { span, hir_id, .. }) => {
- span_with_attrs_in_body_has_safety_comment(cx, *span, *hir_id, accept_comment_above_attributes)
- },
+ | Node::Local(hir::Local { span, hir_id, .. }) => (*span, *hir_id),
Node::Item(hir::Item {
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
span,
owner_id,
..
- }) => span_with_attrs_in_body_has_safety_comment(
- cx,
- *span,
- cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id),
- accept_comment_above_attributes,
- ),
- _ => false,
+ }) => (*span, cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id)),
+ _ => return false,
};
+ // if unsafe block is part of a let/const/static statement,
+ // and accept_comment_above_statement is set to true
+ // we accept the safety comment in the line the precedes this statement.
+ accept_comment_above_statement
+ && span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attributes)
+ } else {
+ false
}
- false
}
/// Extends `span` to also include its attributes, then checks if that span has a safety comment.
-fn span_with_attrs_in_body_has_safety_comment(
+fn span_with_attrs_has_safety_comment(
cx: &LateContext<'_>,
span: Span,
hir_id: HirId,
@@ -420,7 +396,7 @@ fn span_with_attrs_in_body_has_safety_comment(
span
};
- span_in_body_has_safety_comment(cx, span)
+ span_has_safety_comment(cx, span)
}
/// Checks if an expression is "branchy", e.g. loop, match/if/etc.
@@ -444,7 +420,7 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
matches!(
span_from_macro_expansion_has_safety_comment(cx, span),
HasSafetyComment::Yes(_)
- ) || span_in_body_has_safety_comment(cx, span)
+ ) || span_has_safety_comment(cx, span)
}
fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span {
@@ -507,20 +483,18 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
- return unsafe_line.sf.lines(|lines| {
- if comment_start_line.line >= unsafe_line.line {
- HasSafetyComment::No
- } else {
- match text_has_safety_comment(
- src,
- &lines[comment_start_line.line + 1..=unsafe_line.line],
- unsafe_line.sf.start_pos.to_usize(),
- ) {
- Some(b) => HasSafetyComment::Yes(b),
- None => HasSafetyComment::No,
- }
+ return if comment_start_line.line >= unsafe_line.line {
+ HasSafetyComment::No
+ } else {
+ match text_has_safety_comment(
+ src,
+ &unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos,
+ ) {
+ Some(b) => HasSafetyComment::Yes(b),
+ None => HasSafetyComment::No,
}
- });
+ };
}
}
HasSafetyComment::Maybe
@@ -551,20 +525,18 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
- return unsafe_line.sf.lines(|lines| {
- if comment_start_line.line >= unsafe_line.line {
- HasSafetyComment::No
- } else {
- match text_has_safety_comment(
- src,
- &lines[comment_start_line.line + 1..=unsafe_line.line],
- unsafe_line.sf.start_pos.to_usize(),
- ) {
- Some(b) => HasSafetyComment::Yes(b),
- None => HasSafetyComment::No,
- }
+ return if comment_start_line.line >= unsafe_line.line {
+ HasSafetyComment::No
+ } else {
+ match text_has_safety_comment(
+ src,
+ &unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos,
+ ) {
+ Some(b) => HasSafetyComment::Yes(b),
+ None => HasSafetyComment::No,
}
- });
+ };
}
}
HasSafetyComment::Maybe
@@ -614,20 +586,18 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span
&& Lrc::ptr_eq(&unsafe_line.sf, &macro_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
- unsafe_line.sf.lines(|lines| {
- if macro_line.line < unsafe_line.line {
- match text_has_safety_comment(
- src,
- &lines[macro_line.line + 1..=unsafe_line.line],
- unsafe_line.sf.start_pos.to_usize(),
- ) {
- Some(b) => HasSafetyComment::Yes(b),
- None => HasSafetyComment::No,
- }
- } else {
- HasSafetyComment::No
+ if macro_line.line < unsafe_line.line {
+ match text_has_safety_comment(
+ src,
+ &unsafe_line.sf.lines()[macro_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos,
+ ) {
+ Some(b) => HasSafetyComment::Yes(b),
+ None => HasSafetyComment::No,
}
- })
+ } else {
+ HasSafetyComment::No
+ }
} else {
// Problem getting source text. Pretend a comment was found.
HasSafetyComment::Maybe
@@ -639,29 +609,36 @@ fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
let body = cx.enclosing_body?;
let map = cx.tcx.hir();
let mut span = map.body(body).value.span;
+ let mut maybe_global_var = false;
for (_, node) in map.parent_iter(body.hir_id) {
match node {
Node::Expr(e) => span = e.span,
- Node::Block(_)
- | Node::Arm(_)
- | Node::Stmt(_)
- | Node::Local(_)
- | Node::Item(hir::Item {
+ Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::Local(_) => (),
+ Node::Item(hir::Item {
kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
..
- }) => (),
+ }) => maybe_global_var = true,
+ Node::Item(hir::Item {
+ kind: hir::ItemKind::Mod(_),
+ span: item_span,
+ ..
+ }) => {
+ span = *item_span;
+ break;
+ },
+ Node::Crate(mod_) if maybe_global_var => {
+ span = mod_.spans.inner_span;
+ },
_ => break,
}
}
Some(span)
}
-fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
+fn span_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
let source_map = cx.sess().source_map();
let ctxt = span.ctxt();
- if ctxt == SyntaxContext::root()
- && let Some(search_span) = get_body_search_span(cx)
- {
+ if ctxt.is_root() && let Some(search_span) = get_body_search_span(cx) {
if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
&& let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root())
&& let Ok(body_line) = source_map.lookup_line(body_span.lo())
@@ -671,13 +648,11 @@ fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
// Get the text from the start of function body to the unsafe block.
// fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
// ^-------------^
- unsafe_line.sf.lines(|lines| {
- body_line.line < unsafe_line.line && text_has_safety_comment(
- src,
- &lines[body_line.line + 1..=unsafe_line.line],
- unsafe_line.sf.start_pos.to_usize(),
- ).is_some()
- })
+ body_line.line < unsafe_line.line && text_has_safety_comment(
+ src,
+ &unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line],
+ unsafe_line.sf.start_pos,
+ ).is_some()
} else {
// Problem getting source text. Pretend a comment was found.
true
@@ -688,13 +663,13 @@ fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
}
/// Checks if the given text has a safety comment for the immediately proceeding line.
-fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> Option<BytePos> {
+fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos: BytePos) -> Option<BytePos> {
let mut lines = line_starts
.array_windows::<2>()
.rev()
.map_while(|[start, end]| {
- let start = start.to_usize() - offset;
- let end = end.to_usize() - offset;
+ let start = start.to_usize();
+ let end = end.to_usize();
let text = src.get(start..end)?;
let trimmed = text.trim_start();
Some((start + (text.len() - trimmed.len()), trimmed))
@@ -709,9 +684,7 @@ fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) ->
let (mut line, mut line_start) = (line, line_start);
loop {
if line.to_ascii_uppercase().contains("SAFETY:") {
- return Some(BytePos(
- u32::try_from(line_start).unwrap() + u32::try_from(offset).unwrap(),
- ));
+ return Some(start_pos + BytePos(u32::try_from(line_start).unwrap()));
}
match lines.next() {
Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s),
@@ -724,15 +697,13 @@ fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) ->
let (mut line_start, mut line) = (line_start, line);
loop {
if line.starts_with("/*") {
- let src = &src[line_start..line_starts.last().unwrap().to_usize() - offset];
+ let src = &src[line_start..line_starts.last().unwrap().to_usize()];
let mut tokens = tokenize(src);
return (src[..tokens.next().unwrap().len as usize]
.to_ascii_uppercase()
.contains("SAFETY:")
&& tokens.all(|t| t.kind == TokenKind::Whitespace))
- .then_some(BytePos(
- u32::try_from(line_start).unwrap() + u32::try_from(offset).unwrap(),
- ));
+ .then_some(start_pos + BytePos(u32::try_from(line_start).unwrap()));
}
match lines.next() {
Some(x) => (line_start, line) = x,
diff --git a/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs b/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs
index dd829ded0..de4b8738e 100644
--- a/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs
+++ b/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs
@@ -26,7 +26,7 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
- /// let mut twins = vec!((1, 1), (2, 2));
+ /// let mut twins = vec![(1, 1), (2, 2)];
/// twins.sort_by_key(|x| { x.1; });
/// ```
#[clippy::version = "1.47.0"]
diff --git a/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs b/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
index 704d7abd7..e7915953d 100644
--- a/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
+++ b/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
@@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind};
use rustc_lint::{LateContext, LintContext};
-use rustc_middle::lint::in_external_macro;
+use rustc_middle::lint::{in_external_macro, is_from_async_await};
use rustc_middle::ty;
use super::LET_UNIT_VALUE;
@@ -16,6 +16,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
if let Some(init) = local.init
&& !local.pat.span.from_expansion()
&& !in_external_macro(cx.sess(), local.span)
+ && !is_from_async_await(local.span)
&& cx.typeck_results().pat_ty(local.pat).is_unit()
{
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs b/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs
new file mode 100644
index 000000000..5aa057580
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/unnecessary_map_on_constructor.rs
@@ -0,0 +1,93 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::get_type_diagnostic_name;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggest removing the use of a may (or map_err) method when an Option or Result is being construted.
+ ///
+ /// ### Why is this bad?
+ /// It introduces unnecessary complexity. In this case the function can be used directly and
+ /// construct the Option or Result from the output.
+ ///
+ /// ### Example
+ /// ```rust
+ /// Some(4).map(i32::swap_bytes);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// Some(i32::swap_bytes(4));
+ /// ```
+ #[clippy::version = "1.73.0"]
+ pub UNNECESSARY_MAP_ON_CONSTRUCTOR,
+ complexity,
+ "using `map`/`map_err` on `Option` or `Result` constructors"
+}
+declare_lint_pass!(UnnecessaryMapOnConstructor => [UNNECESSARY_MAP_ON_CONSTRUCTOR]);
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+ if let hir::ExprKind::MethodCall(path, recv, args, ..) = expr.kind
+ && let Some(sym::Option | sym::Result) = get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(recv)){
+ let (constructor_path, constructor_item) =
+ if let hir::ExprKind::Call(constructor, constructor_args) = recv.kind
+ && let hir::ExprKind::Path(constructor_path) = constructor.kind
+ && let Some(arg) = constructor_args.get(0)
+ {
+ if constructor.span.from_expansion() || arg.span.from_expansion() {
+ return;
+ }
+ (constructor_path, arg)
+ } else {
+ return;
+ };
+ let constructor_symbol = match constructor_path {
+ hir::QPath::Resolved(_, path) => {
+ if let Some(path_segment) = path.segments.last() {
+ path_segment.ident.name
+ } else {
+ return;
+ }
+ },
+ hir::QPath::TypeRelative(_, path) => path.ident.name,
+ hir::QPath::LangItem(_, _, _) => return,
+ };
+ match constructor_symbol {
+ sym::Some | sym::Ok if path.ident.name == rustc_span::sym::map => (),
+ sym::Err if path.ident.name == sym!(map_err) => (),
+ _ => return,
+ }
+
+ if let Some(map_arg) = args.get(0)
+ && let hir::ExprKind::Path(fun) = map_arg.kind
+ {
+ if map_arg.span.from_expansion() {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut applicability);
+ let constructor_snippet =
+ snippet_with_applicability(cx, constructor_path.span(), "_", &mut applicability);
+ let constructor_arg_snippet =
+ snippet_with_applicability(cx, constructor_item.span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_MAP_ON_CONSTRUCTOR,
+ expr.span,
+ &format!("unnecessary {} on constructor {constructor_snippet}(_)", path.ident.name),
+ "try",
+ format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"),
+ applicability,
+ );
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
index 9cf595772..766a54814 100644
--- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
+++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
@@ -68,7 +68,7 @@ impl EarlyLintPass for UnnestedOrPatterns {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
if self.msrv.meets(msrvs::OR_PATTERNS) {
- if let ast::ExprKind::Let(pat, _, _) = &e.kind {
+ if let ast::ExprKind::Let(pat, _, _, _) = &e.kind {
lint_unnested_or_patterns(cx, pat);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/unwrap.rs b/src/tools/clippy/clippy_lints/src/unwrap.rs
index c99b0290c..9a0d83d83 100644
--- a/src/tools/clippy/clippy_lints/src/unwrap.rs
+++ b/src/tools/clippy/clippy_lints/src/unwrap.rs
@@ -1,15 +1,18 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
-use clippy_utils::usage::is_potentially_mutated;
+use clippy_utils::usage::is_potentially_local_place;
use clippy_utils::{higher, path_to_local};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
-use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, UnOp};
+use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, PathSegment, UnOp};
+use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceWithHirId};
+use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::Ty;
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@@ -192,6 +195,55 @@ fn collect_unwrap_info<'tcx>(
Vec::new()
}
+/// A HIR visitor delegate that checks if a local variable of type `Option<_>` is mutated,
+/// *except* for if `Option::as_mut` is called.
+/// The reason for why we allow that one specifically is that `.as_mut()` cannot change
+/// the option to `None`, and that is important because this lint relies on the fact that
+/// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if
+/// the option is changed to None between `is_some` and `unwrap`.
+/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
+struct MutationVisitor<'tcx> {
+ is_mutated: bool,
+ local_id: HirId,
+ tcx: TyCtxt<'tcx>,
+}
+
+/// Checks if the parent of the expression pointed at by the given `HirId` is a call to
+/// `Option::as_mut`.
+///
+/// Used by the mutation visitor to specifically allow `.as_mut()` calls.
+/// In particular, the `HirId` that the visitor receives is the id of the local expression
+/// (i.e. the `x` in `x.as_mut()`), and that is the reason for why we care about its parent
+/// expression: that will be where the actual method call is.
+fn is_option_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
+ if let Node::Expr(mutating_expr) = tcx.hir().get_parent(expr_id)
+ && let ExprKind::MethodCall(path, ..) = mutating_expr.kind
+ {
+ path.ident.name.as_str() == "as_mut"
+ } else {
+ false
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> {
+ fn borrow(&mut self, cat: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
+ if let ty::BorrowKind::MutBorrow = bk
+ && is_potentially_local_place(self.local_id, &cat.place)
+ && !is_option_as_mut_use(self.tcx, diag_expr_id)
+ {
+ self.is_mutated = true;
+ }
+ }
+
+ fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {
+ self.is_mutated = true;
+ }
+
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
fn visit_branch(
&mut self,
@@ -202,10 +254,26 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
) {
let prev_len = self.unwrappables.len();
for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
- if is_potentially_mutated(unwrap_info.local_id, cond, self.cx)
- || is_potentially_mutated(unwrap_info.local_id, branch, self.cx)
- {
- // if the variable is mutated, we don't know whether it can be unwrapped:
+ let mut delegate = MutationVisitor {
+ tcx: self.cx.tcx,
+ is_mutated: false,
+ local_id: unwrap_info.local_id,
+ };
+
+ let infcx = self.cx.tcx.infer_ctxt().build();
+ let mut vis = ExprUseVisitor::new(
+ &mut delegate,
+ &infcx,
+ cond.hir_id.owner.def_id,
+ self.cx.param_env,
+ self.cx.typeck_results(),
+ );
+ vis.walk_expr(cond);
+ vis.walk_expr(branch);
+
+ if delegate.is_mutated {
+ // if the variable is mutated, we don't know whether it can be unwrapped.
+ // it might have been changed to `None` in between `is_some` + `unwrap`.
continue;
}
self.unwrappables.push(unwrap_info);
@@ -215,6 +283,27 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
}
}
+enum AsRefKind {
+ AsRef,
+ AsMut,
+}
+
+/// Checks if the expression is a method call to `as_{ref,mut}` and returns the receiver of it.
+/// If it isn't, the expression itself is returned.
+fn consume_option_as_ref<'tcx>(expr: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, Option<AsRefKind>) {
+ if let ExprKind::MethodCall(path, recv, ..) = expr.kind {
+ if path.ident.name == sym::as_ref {
+ (recv, Some(AsRefKind::AsRef))
+ } else if path.ident.name.as_str() == "as_mut" {
+ (recv, Some(AsRefKind::AsMut))
+ } else {
+ (expr, None)
+ }
+ } else {
+ (expr, None)
+ }
+}
+
impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
@@ -233,6 +322,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
// find `unwrap[_err]()` calls:
if_chain! {
if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind;
+ let (self_arg, as_ref_kind) = consume_option_as_ref(self_arg);
if let Some(id) = path_to_local(self_arg);
if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name);
let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name);
@@ -268,7 +358,12 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()),
"try",
format!(
- "if let {suggested_pattern} = {unwrappable_variable_name}",
+ "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name}",
+ borrow_prefix = match as_ref_kind {
+ Some(AsRefKind::AsRef) => "&",
+ Some(AsRefKind::AsMut) => "&mut ",
+ None => "",
+ },
),
// We don't track how the unwrapped value is used inside the
// block or suggest deleting the unwrap, so we can't offer a
diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs
index 5ac4f0aa4..f32e7edad 100644
--- a/src/tools/clippy/clippy_lints/src/useless_conversion.rs
+++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs
@@ -8,10 +8,14 @@ use rustc_errors::Applicability;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, MatchSource, Node, PatKind};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::traits::Obligation;
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty;
+use rustc_middle::traits::ObligationCause;
+use rustc_middle::ty::{self, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
+use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
declare_clippy_lint! {
/// ### What it does
@@ -61,22 +65,69 @@ impl MethodOrFunction {
}
}
-/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`
-fn into_iter_bound(cx: &LateContext<'_>, fn_did: DefId, into_iter_did: DefId, param_index: u32) -> Option<Span> {
- cx.tcx
- .predicates_of(fn_did)
- .predicates
- .iter()
- .find_map(|&(ref pred, span)| {
- if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder()
- && tr.def_id() == into_iter_did
- && tr.self_ty().is_param(param_index)
- {
- Some(span)
- } else {
- None
+/// Returns the span of the `IntoIterator` trait bound in the function pointed to by `fn_did`,
+/// iff all of the bounds also hold for the type of the `.into_iter()` receiver.
+/// ```ignore
+/// pub fn foo<I>(i: I)
+/// where I: IntoIterator<Item=i32> + ExactSizeIterator
+/// ^^^^^^^^^^^^^^^^^ this extra bound stops us from suggesting to remove `.into_iter()` ...
+/// {
+/// assert_eq!(i.len(), 3);
+/// }
+///
+/// pub fn bar() {
+/// foo([1, 2, 3].into_iter());
+/// ^^^^^^^^^^^^ ... here, because `[i32; 3]` is not `ExactSizeIterator`
+/// }
+/// ```
+fn into_iter_bound<'tcx>(
+ cx: &LateContext<'tcx>,
+ fn_did: DefId,
+ into_iter_did: DefId,
+ into_iter_receiver: Ty<'tcx>,
+ param_index: u32,
+ node_args: GenericArgsRef<'tcx>,
+) -> Option<Span> {
+ let param_env = cx.tcx.param_env(fn_did);
+ let mut into_iter_span = None;
+
+ for (pred, span) in cx.tcx.explicit_predicates_of(fn_did).predicates {
+ if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() {
+ if tr.self_ty().is_param(param_index) {
+ if tr.def_id() == into_iter_did {
+ into_iter_span = Some(*span);
+ } else {
+ let tr = cx.tcx.erase_regions(tr);
+ if tr.has_escaping_bound_vars() {
+ return None;
+ }
+
+ // Substitute generics in the predicate and replace the IntoIterator type parameter with the
+ // `.into_iter()` receiver to see if the bound also holds for that type.
+ let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| {
+ if i == param_index as usize {
+ GenericArg::from(into_iter_receiver)
+ } else {
+ arg
+ }
+ }));
+
+ let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args);
+ let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), param_env, predicate);
+ if !cx
+ .tcx
+ .infer_ctxt()
+ .build()
+ .predicate_must_hold_modulo_regions(&obligation)
+ {
+ return None;
+ }
+ }
}
- })
+ }
+ }
+
+ into_iter_span
}
/// Extracts the receiver of a `.into_iter()` method call.
@@ -160,22 +211,41 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
// `fn_sig` does not ICE. (see #11065)
&& cx.tcx.opt_def_kind(did).is_some_and(DefKind::is_fn_like) =>
{
- Some((did, args, MethodOrFunction::Function))
+ Some((
+ did,
+ args,
+ cx.typeck_results().node_args(recv.hir_id),
+ MethodOrFunction::Function
+ ))
}
ExprKind::MethodCall(.., args, _) => {
cx.typeck_results().type_dependent_def_id(parent.hir_id)
- .map(|did| (did, args, MethodOrFunction::Method))
+ .map(|did| {
+ return (
+ did,
+ args,
+ cx.typeck_results().node_args(parent.hir_id),
+ MethodOrFunction::Method
+ );
+ })
}
_ => None,
};
- if let Some((parent_fn_did, args, kind)) = parent_fn
+ if let Some((parent_fn_did, args, node_args, kind)) = parent_fn
&& let Some(into_iter_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
&& let sig = cx.tcx.fn_sig(parent_fn_did).skip_binder().skip_binder()
&& let Some(arg_pos) = args.iter().position(|x| x.hir_id == e.hir_id)
&& let Some(&into_iter_param) = sig.inputs().get(kind.param_pos(arg_pos))
&& let ty::Param(param) = into_iter_param.kind()
- && let Some(span) = into_iter_bound(cx, parent_fn_did, into_iter_did, param.index)
+ && let Some(span) = into_iter_bound(
+ cx,
+ parent_fn_did,
+ into_iter_did,
+ cx.typeck_results().expr_ty(into_iter_recv),
+ param.index,
+ node_args
+ )
&& self.expn_depth == 0
{
// Get the "innermost" `.into_iter()` call, e.g. given this expression:
diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs
index 58ae0656d..75c3c7a95 100644
--- a/src/tools/clippy/clippy_lints/src/utils/conf.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs
@@ -542,11 +542,11 @@ define_Conf! {
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
///
/// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
- (accept_comment_above_statement: bool = false),
+ (accept_comment_above_statement: bool = true),
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
///
/// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
- (accept_comment_above_attributes: bool = false),
+ (accept_comment_above_attributes: bool = true),
/// Lint: UNNECESSARY_RAW_STRING_HASHES.
///
/// Whether to allow `r#""#` when `r""` can be used
@@ -561,6 +561,31 @@ define_Conf! {
/// Which crates to allow absolute paths from
(absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet<String> =
rustc_data_structures::fx::FxHashSet::default()),
+ /// Lint: PATH_ENDS_WITH_EXT.
+ ///
+ /// Additional dotfiles (files or directories starting with a dot) to allow
+ (allowed_dotfiles: rustc_data_structures::fx::FxHashSet<String> =
+ rustc_data_structures::fx::FxHashSet::default()),
+ /// Lint: EXPLICIT_ITER_LOOP
+ ///
+ /// Whether to recommend using implicit into iter for reborrowed values.
+ ///
+ /// #### Example
+ /// ```
+ /// let mut vec = vec![1, 2, 3];
+ /// let rmvec = &mut vec;
+ /// for _ in rmvec.iter() {}
+ /// for _ in rmvec.iter_mut() {}
+ /// ```
+ ///
+ /// Use instead:
+ /// ```
+ /// let mut vec = vec![1, 2, 3];
+ /// let rmvec = &mut vec;
+ /// for _ in &*rmvec {}
+ /// for _ in &mut *rmvec {}
+ /// ```
+ (enforce_iter_loop_reborrow: bool = false),
}
/// Search for the configuration file.
diff --git a/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs b/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs
index 6d3493523..94a9a7c24 100644
--- a/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs
@@ -1,12 +1,15 @@
-use clippy_utils::macros::collect_ast_format_args;
+use clippy_utils::macros::AST_FORMAT_ARGS;
use clippy_utils::source::snippet_opt;
use itertools::Itertools;
-use rustc_ast::{Expr, ExprKind, FormatArgs};
+use rustc_ast::{Crate, Expr, ExprKind, FormatArgs};
+use rustc_data_structures::fx::FxHashMap;
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
-use rustc_span::hygiene;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{hygiene, Span};
use std::iter::once;
+use std::mem;
+use std::rc::Rc;
declare_clippy_lint! {
/// ### What it does
@@ -17,7 +20,12 @@ declare_clippy_lint! {
"collects `format_args` AST nodes for use in later lints"
}
-declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
+#[derive(Default)]
+pub struct FormatArgsCollector {
+ format_args: FxHashMap<Span, Rc<FormatArgs>>,
+}
+
+impl_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
impl EarlyLintPass for FormatArgsCollector {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
@@ -26,9 +34,17 @@ impl EarlyLintPass for FormatArgsCollector {
return;
}
- collect_ast_format_args(expr.span, args);
+ self.format_args
+ .insert(expr.span.with_parent(None), Rc::new((**args).clone()));
}
}
+
+ fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) {
+ AST_FORMAT_ARGS.with(|ast_format_args| {
+ let result = ast_format_args.set(mem::take(&mut self.format_args));
+ debug_assert!(result.is_ok());
+ });
+ }
}
/// Detects if the format string or an argument has its span set by a proc macro to something inside
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs
index da8654d93..82f9d4e41 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs
@@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::mir::ConstValue;
use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol;
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/invalid_paths.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/invalid_paths.rs
index 4ed985f54..250772238 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/invalid_paths.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/invalid_paths.rs
@@ -5,10 +5,9 @@ use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::Item;
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::fast_reject::SimplifiedType;
-use rustc_middle::ty::{self, FloatTy};
+use rustc_middle::ty::FloatTy;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
@@ -34,25 +33,20 @@ impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
if_chain! {
if mod_name.as_str() == "paths";
- if let hir::ItemKind::Const(ty, _, body_id) = item.kind;
- let ty = hir_ty_to_ty(cx.tcx, ty);
- if let ty::Array(el_ty, _) = &ty.kind();
- if let ty::Ref(_, el_ty, _) = &el_ty.kind();
- if el_ty.is_str();
+ if let hir::ItemKind::Const(.., body_id) = item.kind;
let body = cx.tcx.hir().body(body_id);
let typeck_results = cx.tcx.typeck_body(body_id);
if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, body.value);
- let path: Vec<&str> = path
+ if let Some(path) = path
.iter()
.map(|x| {
if let Constant::Str(s) = x {
- s.as_str()
+ Some(s.as_str())
} else {
- // We checked the type of the constant above
- unreachable!()
+ None
}
})
- .collect();
+ .collect::<Option<Vec<&str>>>();
if !check_path(cx, &path[..]);
then {
span_lint(cx, INVALID_PATHS, item.span, "invalid path");
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
index 87380f14f..bbb5ade8b 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs
@@ -28,8 +28,8 @@ declare_clippy_lint! {
/// know the name of the lint.
///
/// ### Known problems
- /// Only checks for lints associated using the
- /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
+ /// Only checks for lints associated using the `declare_lint_pass!` and
+ /// `impl_lint_pass!` macros.
///
/// ### Example
/// ```rust,ignore
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
index f49c3fadb..c38a3e81b 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
@@ -31,7 +31,7 @@ use serde::{Serialize, Serializer};
use std::collections::{BTreeSet, BinaryHeap};
use std::fmt;
use std::fmt::Write as _;
-use std::fs::{self, OpenOptions};
+use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -229,25 +229,10 @@ impl Drop for MetadataCollector {
collect_renames(&mut lints);
// Outputting json
- if Path::new(JSON_OUTPUT_FILE).exists() {
- fs::remove_file(JSON_OUTPUT_FILE).unwrap();
- }
- let mut file = OpenOptions::new()
- .write(true)
- .create(true)
- .open(JSON_OUTPUT_FILE)
- .unwrap();
- writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
+ fs::write(JSON_OUTPUT_FILE, serde_json::to_string_pretty(&lints).unwrap()).unwrap();
// Outputting markdown
- if Path::new(MARKDOWN_OUTPUT_FILE).exists() {
- fs::remove_file(MARKDOWN_OUTPUT_FILE).unwrap();
- }
- let mut file = OpenOptions::new()
- .write(true)
- .create(true)
- .open(MARKDOWN_OUTPUT_FILE)
- .unwrap();
+ let mut file = File::create(MARKDOWN_OUTPUT_FILE).unwrap();
writeln!(
file,
"<!--
@@ -261,17 +246,15 @@ Please use that command to update the file and do not edit it by hand.
.unwrap();
// Write configuration links to CHANGELOG.md
- let mut changelog = std::fs::read_to_string(CHANGELOG_PATH).unwrap();
- let mut changelog_file = OpenOptions::new().read(true).write(true).open(CHANGELOG_PATH).unwrap();
-
- if let Some(position) = changelog.find("<!-- begin autogenerated links to configuration documentation -->") {
- // I know this is kinda wasteful, we just don't have regex on `clippy_lints` so... this is the best
- // we can do AFAIK.
- changelog = changelog[..position].to_string();
- }
+ let changelog = std::fs::read_to_string(CHANGELOG_PATH).unwrap();
+ let mut changelog_file = File::create(CHANGELOG_PATH).unwrap();
+ let position = changelog
+ .find("<!-- begin autogenerated links to configuration documentation -->")
+ .unwrap();
writeln!(
changelog_file,
- "{changelog}<!-- begin autogenerated links to configuration documentation -->\n{}\n<!-- end autogenerated links to configuration documentation -->",
+ "{}<!-- begin autogenerated links to configuration documentation -->\n{}\n<!-- end autogenerated links to configuration documentation -->",
+ &changelog[..position],
self.configs_to_markdown(ClippyConfiguration::to_markdown_link)
)
.unwrap();
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
index bf835f89c..86b77a77f 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs
@@ -5,9 +5,8 @@ use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
-use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext};
-use rustc_middle::ty::{self, GenericArgKind};
+use rustc_middle::ty::{self, EarlyBinder, GenericArgKind};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
@@ -25,16 +24,14 @@ impl LateLintPass<'_> for MsrvAttrImpl {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! {
if let hir::ItemKind::Impl(hir::Impl {
- of_trait: Some(lint_pass_trait_ref),
- self_ty,
+ of_trait: Some(_),
items,
..
}) = &item.kind;
- if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
- let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
- if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
- let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
- if let ty::Adt(self_ty_def, _) = self_ty.kind();
+ if let Some(trait_ref) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::instantiate_identity);
+ let is_late_pass = match_def_path(cx, trait_ref.def_id, &paths::LATE_LINT_PASS);
+ if is_late_pass || match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS);
+ if let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind();
if self_ty_def.is_struct();
if self_ty_def.all_fields().any(|f| {
cx.tcx
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
index f66f33fee..a3acb8f17 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
@@ -10,7 +10,8 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
+use rustc_middle::mir::interpret::{Allocation, GlobalAlloc};
+use rustc_middle::mir::ConstValue;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol;
@@ -232,7 +233,8 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
cx.tcx.type_of(def_id).instantiate_identity(),
),
Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? {
- ConstValue::ByRef { alloc, offset } if offset.bytes() == 0 => {
+ ConstValue::Indirect { alloc_id, offset } if offset.bytes() == 0 => {
+ let alloc = cx.tcx.global_alloc(alloc_id).unwrap_memory();
read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).instantiate_identity())
},
_ => None,
diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs
index a9957b18a..da083fb14 100644
--- a/src/tools/clippy/clippy_lints/src/write.rs
+++ b/src/tools/clippy/clippy_lints/src/write.rs
@@ -304,7 +304,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
_ => return,
}
- find_format_args(cx, expr, macro_call.expn, |format_args| {
+ if let Some(format_args) = find_format_args(cx, expr, macro_call.expn) {
// ignore `writeln!(w)` and `write!(v, some_macro!())`
if format_args.span.from_expansion() {
return;
@@ -312,15 +312,15 @@ impl<'tcx> LateLintPass<'tcx> for Write {
match diag_name {
sym::print_macro | sym::eprint_macro | sym::write_macro => {
- check_newline(cx, format_args, &macro_call, name);
+ check_newline(cx, &format_args, &macro_call, name);
},
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
- check_empty_string(cx, format_args, &macro_call, name);
+ check_empty_string(cx, &format_args, &macro_call, name);
},
_ => {},
}
- check_literal(cx, format_args, name);
+ check_literal(cx, &format_args, name);
if !self.in_debug_impl {
for piece in &format_args.template {
@@ -334,7 +334,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
}
}
}
- });
+ }
}
}