Merge commit '9e3e9649cb' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-12-11 19:13:34 +01:00
parent d196db781e
commit 19f0c81b3f
97 changed files with 4089 additions and 1111 deletions

View file

@ -6,7 +6,83 @@ document.
## Unreleased / Beta / In Rust Nightly
[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master)
[d9fb15c...master](https://github.com/rust-lang/rust-clippy/compare/d9fb15c...master)
## Rust 1.92
Current stable, released 2025-12-11
[View all 124 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-09-05T18%3A24%3A03Z..2025-10-16T14%3A13%3A43Z+base%3Amaster)
### New Lints
* Added [`unnecessary_option_map_or_else`] to `suspicious`
[#14662](https://github.com/rust-lang/rust-clippy/pull/14662)
* Added [`replace_box`] to `perf`
[#14953](https://github.com/rust-lang/rust-clippy/pull/14953)
* Added [`volatile_composites`] to `nursery`
[#15686](https://github.com/rust-lang/rust-clippy/pull/15686)
* Added [`self_only_used_in_recursion`] to `pedantic`
[#14787](https://github.com/rust-lang/rust-clippy/pull/14787)
* Added [`redundant_iter_cloned`] to `perf`
[#15277](https://github.com/rust-lang/rust-clippy/pull/15277)
### Moves and Deprecations
* Renamed [`unchecked_duration_subtraction`] to [`unchecked_time_subtraction`]
[#13800](https://github.com/rust-lang/rust-clippy/pull/13800)
### Enhancements
* [`mutex_atomic`] and [`mutex_integer`] overhauled to only lint definitions, not uses; added suggestions
and better help messages
[#15632](https://github.com/rust-lang/rust-clippy/pull/15632)
* [`manual_rotate`] now recognizes non-const rotation amounts
[#15402](https://github.com/rust-lang/rust-clippy/pull/15402)
* [`multiple_inherent_impl`] added `inherent-impl-lint-scope` config option (`module`, `file`,
or `crate`)
[#15843](https://github.com/rust-lang/rust-clippy/pull/15843)
* [`use_self`] now checks structs and enums
[#15566](https://github.com/rust-lang/rust-clippy/pull/15566)
* [`while_let_loop`] extended to lint on `loop { let else }`
[#15701](https://github.com/rust-lang/rust-clippy/pull/15701)
* [`mut_mut`] overhauled with structured suggestions and improved documentation
[#15417](https://github.com/rust-lang/rust-clippy/pull/15417)
* [`nonstandard_macro_braces`] now suggests trailing semicolon when needed
[#15593](https://github.com/rust-lang/rust-clippy/pull/15593)
* [`ptr_offset_with_cast`] now respects MSRV when suggesting fix, and lints more cases
[#15613](https://github.com/rust-lang/rust-clippy/pull/15613)
* [`cast_sign_loss`] and [`cast_possible_wrap`] added suggestions using `cast_{un,}signed()` methods
(MSRV 1.87+)
[#15384](https://github.com/rust-lang/rust-clippy/pull/15384)
* [`unchecked_time_subtraction`] extended to include `Duration - Duration` operations
[#13800](https://github.com/rust-lang/rust-clippy/pull/13800)
* [`filter_next`] now suggests replacing `filter().next_back()` with `rfind()` for
`DoubleEndedIterator`
[#15748](https://github.com/rust-lang/rust-clippy/pull/15748)
### False Positive Fixes
* [`unnecessary_safety_comment`] fixed FPs with comments above attributes
[#15678](https://github.com/rust-lang/rust-clippy/pull/15678)
* [`manual_unwrap_or`] fixed FP edge case
[#15812](https://github.com/rust-lang/rust-clippy/pull/15812)
* [`needless_continue`] fixed FP when match type is not unit or never
[#15547](https://github.com/rust-lang/rust-clippy/pull/15547)
* [`if_then_some_else_none`] fixed FP when return exists in block expr
[#15783](https://github.com/rust-lang/rust-clippy/pull/15783)
* [`new_without_default`] fixed to copy `#[cfg]` onto `impl Default` and fixed FP on private type
with trait impl
[#15720](https://github.com/rust-lang/rust-clippy/pull/15720)
[#15782](https://github.com/rust-lang/rust-clippy/pull/15782)
* [`question_mark`] fixed FP on variables used after
[#15644](https://github.com/rust-lang/rust-clippy/pull/15644)
* [`needless_return`] fixed FP with `cfg`d code after `return`
[#15669](https://github.com/rust-lang/rust-clippy/pull/15669)
* [`useless_attribute`] fixed FP on `deprecated_in_future`
[#15645](https://github.com/rust-lang/rust-clippy/pull/15645)
* [`double_parens`] fixed FP when macros are involved
[#15420](https://github.com/rust-lang/rust-clippy/pull/15420)
## Rust 1.91
@ -6280,6 +6356,7 @@ Released 2018-09-13
[`cyclomatic_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cyclomatic_complexity
[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
[`decimal_bitwise_operands`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_bitwise_operands
[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
[`default_constructed_unit_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs
@ -6541,6 +6618,7 @@ Released 2018-09-13
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp
[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
@ -6686,6 +6764,7 @@ Released 2018-09-13
[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
[`needless_return_with_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return_with_question_mark
[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
[`needless_type_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_type_cast
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
@ -6765,6 +6844,7 @@ Released 2018-09-13
[`ptr_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr
[`ptr_cast_constness`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_cast_constness
[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
[`ptr_offset_by_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_by_literal
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
[`pub_underscore_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields
@ -7081,6 +7161,7 @@ Released 2018-09-13
[`allow-expect-in-consts`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-consts
[`allow-expect-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-tests
[`allow-indexing-slicing-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-indexing-slicing-in-tests
[`allow-large-stack-frames-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-large-stack-frames-in-tests
[`allow-mixed-uninlined-format-args`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-mixed-uninlined-format-args
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
[`allow-panic-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-panic-in-tests

View file

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.93"
version = "0.1.94"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -4,7 +4,7 @@
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 800 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

View file

@ -5,7 +5,7 @@
A collection of lints to catch common mistakes and improve your
[Rust](https://github.com/rust-lang/rust) code.
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are over 800 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

View file

@ -111,6 +111,16 @@ Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
* [`indexing_slicing`](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing)
## `allow-large-stack-frames-in-tests`
Whether functions inside `#[cfg(test)]` modules or test functions should be checked.
**Default Value:** `true`
---
**Affected lints:**
* [`large_stack_frames`](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_frames)
## `allow-mixed-uninlined-format-args`
Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
publish = false

View file

@ -373,6 +373,9 @@ define_Conf! {
/// Whether `indexing_slicing` should be allowed in test functions or `#[cfg(test)]`
#[lints(indexing_slicing)]
allow_indexing_slicing_in_tests: bool = false,
/// Whether functions inside `#[cfg(test)]` modules or test functions should be checked.
#[lints(large_stack_frames)]
allow_large_stack_frames_in_tests: bool = true,
/// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
#[lints(uninlined_format_args)]
allow_mixed_uninlined_format_args: bool = true,

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.93"
version = "0.1.94"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -19,6 +19,7 @@ mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod manual_dangling_ptr;
mod needless_type_cast;
mod ptr_as_ptr;
mod ptr_cast_constness;
mod ref_as_ptr;
@ -813,6 +814,32 @@ declare_clippy_lint! {
"casting a primitive method pointer to any integer type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for bindings (constants, statics, or let bindings) that are defined
/// with one numeric type but are consistently cast to a different type in all usages.
///
/// ### Why is this bad?
/// If a binding is always cast to a different type when used, it would be clearer
/// and more efficient to define it with the target type from the start.
///
/// ### Example
/// ```no_run
/// const SIZE: u16 = 15;
/// let arr: [u8; SIZE as usize] = [0; SIZE as usize];
/// ```
///
/// Use instead:
/// ```no_run
/// const SIZE: usize = 15;
/// let arr: [u8; SIZE] = [0; SIZE];
/// ```
#[clippy::version = "1.93.0"]
pub NEEDLESS_TYPE_CAST,
pedantic,
"binding defined with one type but always cast to another"
}
pub struct Casts {
msrv: Msrv,
}
@ -851,6 +878,7 @@ impl_lint_pass!(Casts => [
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
CONFUSING_METHOD_TO_NUMERIC_CAST,
NEEDLESS_TYPE_CAST,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -920,4 +948,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_slice_different_sizes::check(cx, expr, self.msrv);
ptr_cast_constness::check_null_ptr_cast_method(cx, expr);
}
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &rustc_hir::Body<'tcx>) {
needless_type_cast::check(cx, body);
}
}

View file

@ -0,0 +1,289 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::visitors::{Descend, for_each_expr, for_each_expr_without_closures};
use core::ops::ControlFlow;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BlockCheckMode, Body, Expr, ExprKind, HirId, LetStmt, PatKind, StmtKind, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty::{Ty, TypeVisitableExt};
use rustc_span::Span;
use super::NEEDLESS_TYPE_CAST;
struct BindingInfo<'a> {
source_ty: Ty<'a>,
ty_span: Span,
}
struct UsageInfo<'a> {
cast_to: Option<Ty<'a>>,
in_generic_context: bool,
}
pub(super) fn check<'a>(cx: &LateContext<'a>, body: &Body<'a>) {
let mut bindings: FxHashMap<HirId, BindingInfo<'a>> = FxHashMap::default();
for_each_expr_without_closures(body.value, |expr| {
match expr.kind {
ExprKind::Block(block, _) => {
for stmt in block.stmts {
if let StmtKind::Let(let_stmt) = stmt.kind {
collect_binding_from_local(cx, let_stmt, &mut bindings);
}
}
},
ExprKind::Let(let_expr) => {
collect_binding_from_let(cx, let_expr, &mut bindings);
},
_ => {},
}
ControlFlow::<()>::Continue(())
});
#[allow(rustc::potential_query_instability)]
let mut binding_vec: Vec<_> = bindings.into_iter().collect();
binding_vec.sort_by_key(|(_, info)| info.ty_span.lo());
for (hir_id, binding_info) in binding_vec {
check_binding_usages(cx, body, hir_id, &binding_info);
}
}
fn collect_binding_from_let<'a>(
cx: &LateContext<'a>,
let_expr: &rustc_hir::LetExpr<'a>,
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
) {
if let_expr.ty.is_none()
|| let_expr.span.from_expansion()
|| has_generic_return_type(cx, let_expr.init)
|| contains_unsafe(let_expr.init)
{
return;
}
if let PatKind::Binding(_, hir_id, _, _) = let_expr.pat.kind
&& let Some(ty_hir) = let_expr.ty
{
let ty = cx.typeck_results().pat_ty(let_expr.pat);
if ty.is_numeric() {
bindings.insert(
hir_id,
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
},
);
}
}
}
fn collect_binding_from_local<'a>(
cx: &LateContext<'a>,
let_stmt: &LetStmt<'a>,
bindings: &mut FxHashMap<HirId, BindingInfo<'a>>,
) {
if let_stmt.ty.is_none()
|| let_stmt.span.from_expansion()
|| let_stmt
.init
.is_some_and(|init| has_generic_return_type(cx, init) || contains_unsafe(init))
{
return;
}
if let PatKind::Binding(_, hir_id, _, _) = let_stmt.pat.kind
&& let Some(ty_hir) = let_stmt.ty
{
let ty = cx.typeck_results().pat_ty(let_stmt.pat);
if ty.is_numeric() {
bindings.insert(
hir_id,
BindingInfo {
source_ty: ty,
ty_span: ty_hir.span,
},
);
}
}
}
fn contains_unsafe(expr: &Expr<'_>) -> bool {
for_each_expr_without_closures(expr, |e| {
if let ExprKind::Block(block, _) = e.kind
&& let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
})
.is_some()
}
fn has_generic_return_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match &expr.kind {
ExprKind::Block(block, _) => {
if let Some(tail_expr) = block.expr {
return has_generic_return_type(cx, tail_expr);
}
false
},
ExprKind::If(_, then_block, else_expr) => {
has_generic_return_type(cx, then_block) || else_expr.is_some_and(|e| has_generic_return_type(cx, e))
},
ExprKind::Match(_, arms, _) => arms.iter().any(|arm| has_generic_return_type(cx, arm.body)),
ExprKind::Loop(block, label, ..) => for_each_expr_without_closures(*block, |e| {
match e.kind {
ExprKind::Loop(..) => {
// Unlabeled breaks inside nested loops target the inner loop, not ours
return ControlFlow::Continue(Descend::No);
},
ExprKind::Break(dest, Some(break_expr)) => {
let targets_this_loop =
dest.label.is_none() || dest.label.map(|l| l.ident) == label.map(|l| l.ident);
if targets_this_loop && has_generic_return_type(cx, break_expr) {
return ControlFlow::Break(());
}
},
_ => {},
}
ControlFlow::Continue(Descend::Yes)
})
.is_some(),
ExprKind::MethodCall(..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
let ret_ty = sig.output().skip_binder();
return ret_ty.has_param();
}
false
},
ExprKind::Call(callee, _) => {
if let ExprKind::Path(qpath) = &callee.kind {
let res = cx.qpath_res(qpath, callee.hir_id);
if let Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) = res {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity();
let ret_ty = sig.output().skip_binder();
return ret_ty.has_param();
}
}
false
},
_ => false,
}
}
fn is_generic_res(cx: &LateContext<'_>, res: Res) -> bool {
let has_type_params = |def_id| {
cx.tcx
.generics_of(def_id)
.own_params
.iter()
.any(|p| p.kind.is_ty_or_const())
};
match res {
Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => has_type_params(def_id),
// Ctor → Variant → ADT: constructor's parent is variant, variant's parent is the ADT
Res::Def(DefKind::Ctor(..), def_id) => has_type_params(cx.tcx.parent(cx.tcx.parent(def_id))),
_ => false,
}
}
fn is_cast_in_generic_context<'a>(cx: &LateContext<'a>, cast_expr: &Expr<'a>) -> bool {
let mut current_id = cast_expr.hir_id;
loop {
let parent_id = cx.tcx.parent_hir_id(current_id);
if parent_id == current_id {
return false;
}
let parent = cx.tcx.hir_node(parent_id);
match parent {
rustc_hir::Node::Expr(parent_expr) => {
match &parent_expr.kind {
ExprKind::Closure(_) => return false,
ExprKind::Call(callee, _) => {
if let ExprKind::Path(qpath) = &callee.kind {
let res = cx.qpath_res(qpath, callee.hir_id);
if is_generic_res(cx, res) {
return true;
}
}
},
ExprKind::MethodCall(..) => {
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
&& cx
.tcx
.generics_of(def_id)
.own_params
.iter()
.any(|p| p.kind.is_ty_or_const())
{
return true;
}
},
_ => {},
}
current_id = parent_id;
},
_ => return false,
}
}
}
fn check_binding_usages<'a>(cx: &LateContext<'a>, body: &Body<'a>, hir_id: HirId, binding_info: &BindingInfo<'a>) {
let mut usages = Vec::new();
for_each_expr(cx, body.value, |expr| {
if let ExprKind::Path(ref qpath) = expr.kind
&& !expr.span.from_expansion()
&& let Res::Local(id) = cx.qpath_res(qpath, expr.hir_id)
&& id == hir_id
{
let parent_id = cx.tcx.parent_hir_id(expr.hir_id);
let parent = cx.tcx.hir_node(parent_id);
let usage = if let rustc_hir::Node::Expr(parent_expr) = parent
&& let ExprKind::Cast(..) = parent_expr.kind
&& !parent_expr.span.from_expansion()
{
UsageInfo {
cast_to: Some(cx.typeck_results().expr_ty(parent_expr)),
in_generic_context: is_cast_in_generic_context(cx, parent_expr),
}
} else {
UsageInfo {
cast_to: None,
in_generic_context: false,
}
};
usages.push(usage);
}
ControlFlow::<()>::Continue(())
});
let Some(first_target) = usages
.first()
.and_then(|u| u.cast_to)
.filter(|&t| t != binding_info.source_ty)
.filter(|&t| usages.iter().all(|u| u.cast_to == Some(t) && !u.in_generic_context))
else {
return;
};
span_lint_and_sugg(
cx,
NEEDLESS_TYPE_CAST,
binding_info.ty_span,
format!(
"this binding is defined as `{}` but is always cast to `{}`",
binding_info.source_ty, first_target
),
"consider defining it as",
first_target.to_string(),
Applicability::MaybeIncorrect,
);
}

View file

@ -70,6 +70,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::MANUAL_DANGLING_PTR_INFO,
crate::casts::NEEDLESS_TYPE_CAST_INFO,
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
@ -245,8 +246,8 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::large_stack_frames::LARGE_STACK_FRAMES_INFO,
crate::legacy_numeric_constants::LEGACY_NUMERIC_CONSTANTS_INFO,
crate::len_without_is_empty::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
crate::len_zero::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::LEN_ZERO_INFO,
crate::let_if_seq::USELESS_LET_IF_SEQ_INFO,
crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO,
@ -300,6 +301,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO,
crate::manual_ilog2::MANUAL_ILOG2_INFO,
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
@ -444,6 +446,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::OR_THEN_UNWRAP_INFO,
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
crate::methods::PATH_ENDS_WITH_EXT_INFO,
crate::methods::PTR_OFFSET_BY_LITERAL_INFO,
crate::methods::PTR_OFFSET_WITH_CAST_INFO,
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
crate::methods::READONLY_WRITE_LOCK_INFO,
@ -578,6 +581,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::operators::ASSIGN_OP_PATTERN_INFO,
crate::operators::BAD_BIT_MASK_INFO,
crate::operators::CMP_OWNED_INFO,
crate::operators::DECIMAL_BITWISE_OPERANDS_INFO,
crate::operators::DOUBLE_COMPARISONS_INFO,
crate::operators::DURATION_SUBSEC_INFO,
crate::operators::EQ_OP_INFO,

View file

@ -34,7 +34,7 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [
#[clippy::version = "pre 1.29.0"]
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
#[clippy::version = "1.91.0"]
("clippy::string_to_string", "`clippy:implicit_clone` covers those cases"),
("clippy::string_to_string", "`clippy::implicit_clone` covers those cases"),
#[clippy::version = "pre 1.29.0"]
("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"),
#[clippy::version = "pre 1.29.0"]

View file

@ -3,11 +3,12 @@ use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_appl
use clippy_utils::ty::is_copy;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
SpanlessEq, can_move_expr_to_closure_no_visit, higher, is_expr_final_block_expr, is_expr_used_or_unified,
peel_hir_expr_while,
SpanlessEq, can_move_expr_to_closure_no_visit, desugar_await, higher, is_expr_final_block_expr,
is_expr_used_or_unified, paths, peel_hir_expr_while,
};
use core::fmt::{self, Write};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::HirIdSet;
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
use rustc_hir::{Block, Expr, ExprKind, HirId, Pat, Stmt, StmtKind, UnOp};
@ -382,6 +383,8 @@ struct InsertSearcher<'cx, 'tcx> {
loops: Vec<HirId>,
/// Local variables created in the expression. These don't need to be captured.
locals: HirIdSet,
/// Whether the map is a non-async-aware `MutexGuard`.
map_is_mutex_guard: bool,
}
impl<'tcx> InsertSearcher<'_, 'tcx> {
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
@ -524,15 +527,22 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
self.is_single_insert = false;
self.visit_non_tail_expr(cond_expr);
// Each branch may contain it's own insert expression.
// Each branch may contain its own insert expression.
let mut is_map_used = self.visit_cond_arm(then_expr);
is_map_used |= self.visit_cond_arm(else_expr);
self.is_map_used = is_map_used;
},
ExprKind::Match(scrutinee_expr, arms, _) => {
// If the map is a non-async-aware `MutexGuard` and
// `.await` expression appears alongside map insertion in the same `then` or `else` block,
// we cannot suggest using `entry()` because it would hold the lock across the await point,
// triggering `await_holding_lock` and risking deadlock.
if self.map_is_mutex_guard && desugar_await(expr).is_some() {
self.can_use_entry = false;
}
self.is_single_insert = false;
self.visit_non_tail_expr(scrutinee_expr);
// Each branch may contain it's own insert expression.
// Each branch may contain its own insert expression.
let mut is_map_used = self.is_map_used;
for arm in arms {
self.visit_pat(arm.pat);
@ -725,16 +735,32 @@ fn find_insert_calls<'tcx>(
edits: Vec::new(),
loops: Vec::new(),
locals: HirIdSet::default(),
map_is_mutex_guard: false,
};
// Check if the map is a non-async-aware `MutexGuard`
if let rustc_middle::ty::Adt(adt, _) = cx.typeck_results().expr_ty(contains_expr.map).kind()
&& is_mutex_guard(cx, adt.did())
{
s.map_is_mutex_guard = true;
}
s.visit_expr(expr);
let allow_insert_closure = s.allow_insert_closure;
let is_single_insert = s.is_single_insert;
if !s.can_use_entry {
return None;
}
let is_key_used_and_no_copy = s.is_key_used && !is_copy(cx, cx.typeck_results().expr_ty(contains_expr.key));
let edits = s.edits;
s.can_use_entry.then_some(InsertSearchResults {
edits,
allow_insert_closure,
is_single_insert,
Some(InsertSearchResults {
edits: s.edits,
allow_insert_closure: s.allow_insert_closure,
is_single_insert: s.is_single_insert,
is_key_used_and_no_copy,
})
}
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
match cx.tcx.get_diagnostic_name(def_id) {
Some(name) => matches!(name, sym::MutexGuard | sym::RwLockReadGuard | sym::RwLockWriteGuard),
None => paths::PARKING_LOT_GUARDS.iter().any(|guard| guard.matches(cx, def_id)),
}
}

View file

@ -2,15 +2,16 @@ use std::{fmt, ops};
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::fn_has_unsatisfiable_preds;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::source::{HasSession, SpanRangeExt};
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_in_test};
use rustc_errors::Diag;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl};
use rustc_lexer::is_ident;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -83,12 +84,14 @@ declare_clippy_lint! {
pub struct LargeStackFrames {
maximum_allowed_size: u64,
allow_large_stack_frames_in_tests: bool,
}
impl LargeStackFrames {
pub fn new(conf: &'static Conf) -> Self {
Self {
maximum_allowed_size: conf.stack_size_threshold,
allow_large_stack_frames_in_tests: conf.allow_large_stack_frames_in_tests,
}
}
}
@ -152,67 +155,122 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
let mir = cx.tcx.optimized_mir(def_id);
let typing_env = mir.typing_env(cx.tcx);
let sizes_of_locals = || {
mir.local_decls.iter().filter_map(|local| {
let sizes_of_locals = mir
.local_decls
.iter()
.filter_map(|local| {
let layout = cx.tcx.layout_of(typing_env.as_query_input(local.ty)).ok()?;
Some((local, layout.size.bytes()))
})
};
.collect::<Vec<_>>();
let frame_size = sizes_of_locals().fold(Space::Used(0), |sum, (_, size)| sum + size);
let frame_size = sizes_of_locals
.iter()
.fold(Space::Used(0), |sum, (_, size)| sum + *size);
let limit = self.maximum_allowed_size;
if frame_size.exceeds_limit(limit) {
// Point at just the function name if possible, because lints that span
// the entire body and don't have to are less legible.
let fn_span = match fn_kind {
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
FnKind::Closure => entire_fn_span,
let (fn_span, fn_name) = match fn_kind {
FnKind::ItemFn(ident, _, _) => (ident.span, format!("function `{}`", ident.name)),
FnKind::Method(ident, _) => (ident.span, format!("method `{}`", ident.name)),
FnKind::Closure => (entire_fn_span, "closure".to_string()),
};
// Don't lint inside tests if configured to not do so.
if self.allow_large_stack_frames_in_tests && is_in_test(cx.tcx, cx.tcx.local_def_id_to_hir_id(local_def_id))
{
return;
}
let explain_lint = |diag: &mut Diag<'_, ()>, ctxt: SyntaxContext| {
// Point out the largest individual contribution to this size, because
// it is the most likely to be unintentionally large.
if let Some((local, size)) = sizes_of_locals.iter().max_by_key(|&(_, size)| size)
&& let local_span = local.source_info.span
&& local_span.ctxt() == ctxt
{
let size = Space::Used(*size); // pluralizes for us
let ty = local.ty;
// TODO: Is there a cleaner, robust way to ask this question?
// The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
// and that doesn't get us the true name in scope rather than the span text either.
if let Some(name) = local_span.get_source_text(cx)
&& is_ident(&name)
{
// If the local is an ordinary named variable,
// print its name rather than relying solely on the span.
diag.span_label(
local_span,
format!("`{name}` is the largest part, at {size} for type `{ty}`"),
);
} else {
diag.span_label(
local_span,
format!("this is the largest part, at {size} for type `{ty}`"),
);
}
}
// Explain why we are linting this and not other functions.
diag.note(format!(
"{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
));
// Explain why the user should care, briefly.
diag.note_once(
"allocating large amounts of stack space can overflow the stack \
and cause the program to abort",
);
};
if fn_span.from_expansion() {
// Don't lint on the main function generated by `--test` target
if cx.sess().is_test_crate() && is_entrypoint_fn(cx, local_def_id.to_def_id()) {
return;
}
let is_from_external_macro = fn_span.in_external_macro(cx.sess().source_map());
span_lint_and_then(
cx,
LARGE_STACK_FRAMES,
fn_span.source_callsite(),
format!(
"{} generated by this macro may allocate a lot of stack space",
if is_from_external_macro {
cx.tcx.def_descr(local_def_id.into())
} else {
fn_name.as_str()
}
),
|diag| {
if is_from_external_macro {
return;
}
diag.span_label(
fn_span,
format!(
"this {} has a stack frame size of {frame_size}",
cx.tcx.def_descr(local_def_id.into())
),
);
explain_lint(diag, fn_span.ctxt());
},
);
return;
}
span_lint_and_then(
cx,
LARGE_STACK_FRAMES,
fn_span,
format!("this function may allocate {frame_size} on the stack"),
|diag| {
// Point out the largest individual contribution to this size, because
// it is the most likely to be unintentionally large.
if let Some((local, size)) = sizes_of_locals().max_by_key(|&(_, size)| size) {
let local_span: Span = local.source_info.span;
let size = Space::Used(size); // pluralizes for us
let ty = local.ty;
// TODO: Is there a cleaner, robust way to ask this question?
// The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
// and that doesn't get us the true name in scope rather than the span text either.
if let Some(name) = local_span.get_source_text(cx)
&& is_ident(&name)
{
// If the local is an ordinary named variable,
// print its name rather than relying solely on the span.
diag.span_label(
local_span,
format!("`{name}` is the largest part, at {size} for type `{ty}`"),
);
} else {
diag.span_label(
local_span,
format!("this is the largest part, at {size} for type `{ty}`"),
);
}
}
// Explain why we are linting this and not other functions.
diag.note(format!(
"{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
));
// Explain why the user should care, briefly.
diag.note_once(
"allocating large amounts of stack space can overflow the stack \
and cause the program to abort",
);
explain_lint(diag, SyntaxContext::root());
},
);
}

View file

@ -0,0 +1,342 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::res::MaybeDef;
use clippy_utils::{fulfill_or_allowed, get_parent_as_impl, sym};
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::{
FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability,
Node, OpaqueTyOrigin, PathSegment, PrimTy, QPath, TraitItemId, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FnSig, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::kw;
use rustc_span::{Ident, Span, Symbol};
use rustc_trait_selection::traits::supertrait_def_ids;
declare_clippy_lint! {
/// ### What it does
/// Checks for items that implement `.len()` but not
/// `.is_empty()`.
///
/// ### Why is this bad?
/// It is good custom to have both methods, because for
/// some data structures, asking about the length will be a costly operation,
/// whereas `.is_empty()` can usually answer in constant time. Also it used to
/// lead to false positives on the [`len_zero`](#len_zero) lint currently that
/// lint will ignore such entities.
///
/// ### Example
/// ```ignore
/// impl X {
/// pub fn len(&self) -> usize {
/// ..
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub LEN_WITHOUT_IS_EMPTY,
style,
"traits or impls with a public `len` method but no corresponding `is_empty` method"
}
declare_lint_pass!(LenWithoutIsEmpty => [LEN_WITHOUT_IS_EMPTY]);
impl<'tcx> LateLintPass<'tcx> for LenWithoutIsEmpty {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Trait(_, _, _, ident, _, _, trait_items) = item.kind
&& !item.span.from_expansion()
{
check_trait_items(cx, item, ident, trait_items);
}
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if item.ident.name == sym::len
&& let ImplItemKind::Fn(sig, _) = &item.kind
&& sig.decl.implicit_self.has_implicit_self()
&& sig.decl.inputs.len() == 1
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& matches!(sig.decl.output, FnRetTy::Return(_))
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
&& imp.of_trait.is_none()
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
&& let Some(local_id) = ty_id.as_local()
&& let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
&& let Some(output) = LenOutput::new(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
{
let (name, kind) = match cx.tcx.hir_node(ty_hir_id) {
Node::ForeignItem(x) => (x.ident.name, "extern type"),
Node::Item(x) => match x.kind {
ItemKind::Struct(ident, ..) => (ident.name, "struct"),
ItemKind::Enum(ident, ..) => (ident.name, "enum"),
ItemKind::Union(ident, ..) => (ident.name, "union"),
_ => (x.kind.ident().unwrap().name, "type"),
},
_ => return,
};
check_for_is_empty(
cx,
sig.span,
sig.decl.implicit_self,
output,
ty_id,
name,
kind,
item.hir_id(),
ty_hir_id,
);
}
}
}
fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) {
fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool {
cx.tcx.item_name(item.owner_id) == name
&& matches!(
cx.tcx.fn_arg_idents(item.owner_id),
[Some(Ident {
name: kw::SelfLower,
..
})],
)
}
// fill the set with current and super traits
fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) {
if set.insert(traitt) {
for supertrait in supertrait_def_ids(cx.tcx, traitt) {
fill_trait_set(supertrait, set, cx);
}
}
}
if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id)
&& trait_items.iter().any(|&i| is_named_self(cx, i, sym::len))
{
let mut current_and_super_traits = DefIdSet::default();
fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
let is_empty_method_found = current_and_super_traits
.items()
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(sym::is_empty))
.any(|i| i.is_method() && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1);
if !is_empty_method_found {
span_lint(
cx,
LEN_WITHOUT_IS_EMPTY,
visited_trait.span,
format!(
"trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
ident.name
),
);
}
}
}
fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
if let ty::Alias(_, alias_ty) = ty.kind()
&& let Some(Node::OpaqueTy(opaque)) = cx.tcx.hir_get_if_local(alias_ty.def_id)
&& let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin
&& let [GenericBound::Trait(trait_ref)] = &opaque.bounds
&& let Some(segment) = trait_ref.trait_ref.path.segments.last()
&& let Some(generic_args) = segment.args
&& let [constraint] = generic_args.constraints
&& let Some(ty) = constraint.ty()
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment] = path.segments
{
return Some(segment);
}
None
}
fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
if let Some(generic_args) = segment.args
&& let [GenericArg::Type(ty), ..] = &generic_args.args
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment, ..] = &path.segments
&& matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_)))
{
true
} else {
false
}
}
#[derive(Debug, Clone, Copy)]
enum LenOutput {
Integral,
Option(DefId),
Result(DefId),
}
impl LenOutput {
fn new<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Self> {
if let Some(segment) = extract_future_output(cx, sig.output()) {
let res = segment.res;
if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) {
return Some(Self::Integral);
}
if let Res::Def(_, def_id) = res
&& let Some(res) = match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::Option) => Some(Self::Option(def_id)),
Some(sym::Result) => Some(Self::Result(def_id)),
_ => None,
}
&& is_first_generic_integral(segment)
{
return Some(res);
}
return None;
}
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(Self::Integral),
ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => subs.type_at(0).is_integral().then(|| Self::Option(adt.did())),
Some(sym::Result) => subs.type_at(0).is_integral().then(|| Self::Result(adt.did())),
_ => None,
},
_ => None,
}
}
fn matches_is_empty_output<'tcx>(self, cx: &LateContext<'tcx>, is_empty_output: Ty<'tcx>) -> bool {
if let Some(segment) = extract_future_output(cx, is_empty_output) {
return match (self, segment.res) {
(_, Res::PrimTy(PrimTy::Bool)) => true,
(Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
(Self::Result(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
_ => false,
};
}
match (self, is_empty_output.kind()) {
(_, &ty::Bool) => true,
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
(Self::Result(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
_ => false,
}
}
}
/// The expected signature of `is_empty`, based on that of `len`
fn expected_is_empty_sig(len_output: LenOutput, len_self_kind: ImplicitSelfKind) -> String {
let self_ref = match len_self_kind {
ImplicitSelfKind::RefImm => "&",
ImplicitSelfKind::RefMut => "&(mut) ",
_ => "",
};
match len_output {
LenOutput::Integral => format!("expected signature: `({self_ref}self) -> bool`"),
LenOutput::Option(_) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Option<bool>")
},
LenOutput::Result(..) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Result<bool>")
},
}
}
/// Checks if the given signature matches the expectations for `is_empty`
fn check_is_empty_sig<'tcx>(
cx: &LateContext<'tcx>,
is_empty_sig: FnSig<'tcx>,
len_self_kind: ImplicitSelfKind,
len_output: LenOutput,
) -> bool {
if let [is_empty_self_arg, is_empty_output] = &**is_empty_sig.inputs_and_output
&& len_output.matches_is_empty_output(cx, *is_empty_output)
{
match (is_empty_self_arg.kind(), len_self_kind) {
// if `len` takes `&self`, `is_empty` should do so as well
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::RefImm)
// if `len` takes `&mut self`, `is_empty` may take that _or_ `&self` (#16190)
| (ty::Ref(_, _, Mutability::Mut | Mutability::Not), ImplicitSelfKind::RefMut) => true,
// if len takes `self`, `is_empty` should do so as well
// XXX: we might want to relax this to allow `&self` and `&mut self`
(_, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut) if !is_empty_self_arg.is_ref() => true,
_ => false,
}
} else {
false
}
}
/// Checks if the given type has an `is_empty` method with the appropriate signature.
#[expect(clippy::too_many_arguments)]
fn check_for_is_empty(
cx: &LateContext<'_>,
len_span: Span,
len_self_kind: ImplicitSelfKind,
len_output: LenOutput,
impl_ty: DefId,
item_name: Symbol,
item_kind: &str,
len_method_hir_id: HirId,
ty_decl_hir_id: HirId,
) {
// 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 = cx
.tcx
.inherent_impls(impl_ty)
.iter()
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(sym::is_empty))
.find(|item| item.is_fn());
let (msg, is_empty_span, is_empty_expected_sig) = match is_empty {
None => (
format!("{item_kind} `{item_name}` has a public `len` method, but no `is_empty` method"),
None,
None,
),
Some(is_empty) if !cx.effective_visibilities.is_exported(is_empty.def_id.expect_local()) => (
format!("{item_kind} `{item_name}` has a public `len` method, but a private `is_empty` method"),
Some(cx.tcx.def_span(is_empty.def_id)),
None,
),
Some(is_empty)
if !(is_empty.is_method()
&& check_is_empty_sig(
cx,
cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(),
len_self_kind,
len_output,
)) =>
{
(
format!(
"{item_kind} `{item_name}` has a public `len` method, but the `is_empty` method has an unexpected signature",
),
Some(cx.tcx.def_span(is_empty.def_id)),
Some(expected_is_empty_sig(len_output, len_self_kind)),
)
},
Some(_) => return,
};
if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, len_span, msg, |db| {
if let Some(span) = is_empty_span {
db.span_note(span, "`is_empty` defined here");
}
if let Some(expected_sig) = is_empty_expected_sig {
db.note(expected_sig);
}
});
}
}

View file

@ -1,27 +1,20 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
use clippy_utils::ty::implements_trait;
use clippy_utils::{fulfill_or_allowed, get_parent_as_impl, parent_item_name, peel_ref_operators, sym};
use clippy_utils::{parent_item_name, peel_ref_operators, sym};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::{
BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind,
Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, RustcVersion,
StabilityLevel, StableSince, TraitItemId, TyKind,
};
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Expr, ExprKind, PatExprKind, PatKind, RustcVersion, StabilityLevel, StableSince};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FnSig, Ty};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::kw;
use rustc_span::{Ident, Span, Symbol};
use rustc_trait_selection::traits::supertrait_def_ids;
use rustc_span::{Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -58,32 +51,6 @@ declare_clippy_lint! {
"checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for items that implement `.len()` but not
/// `.is_empty()`.
///
/// ### Why is this bad?
/// It is good custom to have both methods, because for
/// some data structures, asking about the length will be a costly operation,
/// whereas `.is_empty()` can usually answer in constant time. Also it used to
/// lead to false positives on the [`len_zero`](#len_zero) lint currently that
/// lint will ignore such entities.
///
/// ### Example
/// ```ignore
/// impl X {
/// pub fn len(&self) -> usize {
/// ..
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub LEN_WITHOUT_IS_EMPTY,
style,
"traits or impls with a public `len` method but no corresponding `is_empty` method"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for comparing to an empty slice such as `""` or `[]`,
@ -126,7 +93,7 @@ pub struct LenZero {
msrv: Msrv,
}
impl_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
impl_lint_pass!(LenZero => [LEN_ZERO, COMPARISON_TO_EMPTY]);
impl LenZero {
pub fn new(conf: &'static Conf) -> Self {
@ -135,54 +102,6 @@ impl LenZero {
}
impl<'tcx> LateLintPass<'tcx> for LenZero {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Trait(_, _, _, ident, _, _, trait_items) = item.kind
&& !item.span.from_expansion()
{
check_trait_items(cx, item, ident, trait_items);
}
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if item.ident.name == sym::len
&& let ImplItemKind::Fn(sig, _) = &item.kind
&& sig.decl.implicit_self.has_implicit_self()
&& sig.decl.inputs.len() == 1
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& matches!(sig.decl.output, FnRetTy::Return(_))
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
&& imp.of_trait.is_none()
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
&& let Some(local_id) = ty_id.as_local()
&& let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
&& let Some(output) =
parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
{
let (name, kind) = match cx.tcx.hir_node(ty_hir_id) {
Node::ForeignItem(x) => (x.ident.name, "extern type"),
Node::Item(x) => match x.kind {
ItemKind::Struct(ident, ..) => (ident.name, "struct"),
ItemKind::Enum(ident, ..) => (ident.name, "enum"),
ItemKind::Union(ident, ..) => (ident.name, "union"),
_ => (x.kind.ident().unwrap().name, "type"),
},
_ => return,
};
check_for_is_empty(
cx,
sig.span,
sig.decl.implicit_self,
output,
ty_id,
name,
kind,
item.hir_id(),
ty_hir_id,
);
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Let(lt) = expr.kind
&& match lt.pat.kind {
@ -356,256 +275,6 @@ fn span_without_enclosing_paren(cx: &LateContext<'_>, span: Span) -> Span {
}
}
fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Ident, trait_items: &[TraitItemId]) {
fn is_named_self(cx: &LateContext<'_>, item: TraitItemId, name: Symbol) -> bool {
cx.tcx.item_name(item.owner_id) == name
&& matches!(
cx.tcx.fn_arg_idents(item.owner_id),
[Some(Ident {
name: kw::SelfLower,
..
})],
)
}
// fill the set with current and super traits
fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) {
if set.insert(traitt) {
for supertrait in supertrait_def_ids(cx.tcx, traitt) {
fill_trait_set(supertrait, set, cx);
}
}
}
if cx.effective_visibilities.is_exported(visited_trait.owner_id.def_id)
&& trait_items.iter().any(|&i| is_named_self(cx, i, sym::len))
{
let mut current_and_super_traits = DefIdSet::default();
fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
let is_empty_method_found = current_and_super_traits
.items()
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(sym::is_empty))
.any(|i| i.is_method() && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1);
if !is_empty_method_found {
span_lint(
cx,
LEN_WITHOUT_IS_EMPTY,
visited_trait.span,
format!(
"trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
ident.name
),
);
}
}
}
#[derive(Debug, Clone, Copy)]
enum LenOutput {
Integral,
Option(DefId),
Result(DefId),
}
fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
if let ty::Alias(_, alias_ty) = ty.kind()
&& let Some(Node::OpaqueTy(opaque)) = cx.tcx.hir_get_if_local(alias_ty.def_id)
&& let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin
&& let [GenericBound::Trait(trait_ref)] = &opaque.bounds
&& let Some(segment) = trait_ref.trait_ref.path.segments.last()
&& let Some(generic_args) = segment.args
&& let [constraint] = generic_args.constraints
&& let Some(ty) = constraint.ty()
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment] = path.segments
{
return Some(segment);
}
None
}
fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
if let Some(generic_args) = segment.args
&& let [GenericArg::Type(ty), ..] = &generic_args.args
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
&& let [segment, ..] = &path.segments
&& matches!(segment.res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_)))
{
true
} else {
false
}
}
fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput> {
if let Some(segment) = extract_future_output(cx, sig.output()) {
let res = segment.res;
if matches!(res, Res::PrimTy(PrimTy::Uint(_) | PrimTy::Int(_))) {
return Some(LenOutput::Integral);
}
if let Res::Def(_, def_id) = res
&& let Some(res) = match cx.tcx.get_diagnostic_name(def_id) {
Some(sym::Option) => Some(LenOutput::Option(def_id)),
Some(sym::Result) => Some(LenOutput::Result(def_id)),
_ => None,
}
&& is_first_generic_integral(segment)
{
return Some(res);
}
return None;
}
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())),
Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())),
_ => None,
},
_ => None,
}
}
impl LenOutput {
fn matches_is_empty_output<'tcx>(self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
if let Some(segment) = extract_future_output(cx, ty) {
return match (self, segment.res) {
(_, Res::PrimTy(PrimTy::Bool)) => true,
(Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
(Self::Result(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
_ => false,
};
}
match (self, ty.kind()) {
(_, &ty::Bool) => true,
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
(Self::Result(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
_ => false,
}
}
fn expected_sig(self, self_kind: ImplicitSelfKind) -> String {
let self_ref = match self_kind {
ImplicitSelfKind::RefImm => "&",
ImplicitSelfKind::RefMut => "&mut ",
_ => "",
};
match self {
Self::Integral => format!("expected signature: `({self_ref}self) -> bool`"),
Self::Option(_) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Option<bool>")
},
Self::Result(..) => {
format!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Result<bool>")
},
}
}
}
/// Checks if the given signature matches the expectations for `is_empty`
fn check_is_empty_sig<'tcx>(
cx: &LateContext<'tcx>,
sig: FnSig<'tcx>,
self_kind: ImplicitSelfKind,
len_output: LenOutput,
) -> bool {
match &**sig.inputs_and_output {
[arg, res] if len_output.matches_is_empty_output(cx, *res) => {
matches!(
(arg.kind(), self_kind),
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::RefImm)
| (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::RefMut)
) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut))
},
_ => false,
}
}
/// Checks if the given type has an `is_empty` method with the appropriate signature.
#[expect(clippy::too_many_arguments)]
fn check_for_is_empty(
cx: &LateContext<'_>,
span: Span,
self_kind: ImplicitSelfKind,
output: LenOutput,
impl_ty: DefId,
item_name: Symbol,
item_kind: &str,
len_method_hir_id: HirId,
ty_decl_hir_id: HirId,
) {
// 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 = cx
.tcx
.inherent_impls(impl_ty)
.iter()
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(sym::is_empty))
.find(|item| item.is_fn());
let (msg, is_empty_span, self_kind) = match is_empty {
None => (
format!(
"{item_kind} `{}` has a public `len` method, but no `is_empty` method",
item_name.as_str(),
),
None,
None,
),
Some(is_empty) if !cx.effective_visibilities.is_exported(is_empty.def_id.expect_local()) => (
format!(
"{item_kind} `{}` has a public `len` method, but a private `is_empty` method",
item_name.as_str(),
),
Some(cx.tcx.def_span(is_empty.def_id)),
None,
),
Some(is_empty)
if !(is_empty.is_method()
&& check_is_empty_sig(
cx,
cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(),
self_kind,
output,
)) =>
{
(
format!(
"{item_kind} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
item_name.as_str(),
),
Some(cx.tcx.def_span(is_empty.def_id)),
Some(self_kind),
)
},
Some(_) => return,
};
if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
if let Some(span) = is_empty_span {
db.span_note(span, "`is_empty` defined here");
}
if let Some(self_kind) = self_kind {
db.note(output.expected_sig(self_kind));
}
});
}
}
fn is_empty_string(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Str(lit, _) = lit.node

View file

@ -182,6 +182,7 @@ mod large_include_file;
mod large_stack_arrays;
mod large_stack_frames;
mod legacy_numeric_constants;
mod len_without_is_empty;
mod len_zero;
mod let_if_seq;
mod let_underscore;
@ -201,6 +202,7 @@ mod manual_clamp;
mod manual_float_methods;
mod manual_hash_one;
mod manual_ignore_case_cmp;
mod manual_ilog2;
mod manual_is_ascii_check;
mod manual_is_power_of_two;
mod manual_let_else;
@ -538,6 +540,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)),
Box::new(|_| Box::<significant_drop_tightening::SignificantDropTightening<'_>>::default()),
Box::new(move |_| Box::new(len_zero::LenZero::new(conf))),
Box::new(|_| Box::new(len_without_is_empty::LenWithoutIsEmpty)),
Box::new(move |_| Box::new(attrs::Attributes::new(conf))),
Box::new(|_| Box::new(blocks_in_conditions::BlocksInConditions)),
Box::new(|_| Box::new(unicode::Unicode)),
@ -848,6 +851,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);

View file

@ -43,26 +43,26 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
};
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// passed by reference. TODO: If the struct can be partially moved from and the struct isn't used
// afterwards a mutable borrow of a field isn't necessary.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
let iterator_by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
{
".by_ref()"
make_iterator_snippet(cx, iter_expr, &iterator)
} else {
""
iterator.into_owned()
};
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(let_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("{loop_label}for {loop_var} in {iterator}{by_ref}"),
format!("{loop_label}for {loop_var} in {iterator_by_ref}"),
applicability,
);
}
@ -355,3 +355,22 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
.is_break()
}
}
/// Constructs the transformed iterator expression for the suggestion.
/// Returns `iterator.by_ref()` unless the last deref adjustment targets an unsized type,
/// in which case it applies all derefs (e.g., `&mut **iterator` or `&mut ***iterator`).
fn make_iterator_snippet<'tcx>(cx: &LateContext<'tcx>, iter_expr: &Expr<'tcx>, iterator: &str) -> String {
if let Some((n, adjust)) = cx
.typeck_results()
.expr_adjustments(iter_expr)
.iter()
.take_while(|x| matches!(x.kind, Adjust::Deref(_)))
.enumerate()
.last()
&& !adjust.target.is_sized(cx.tcx, cx.typing_env())
{
format!("&mut {:*<n$}{iterator}", '*', n = n + 1)
} else {
format!("{iterator}.by_ref()")
}
}

View file

@ -0,0 +1,115 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_from_proc_macro, sym};
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions like `N - x.leading_zeros()` (where `N` is one less than bit width
/// of `x`) or `x.ilog(2)`, which are manual reimplementations of `x.ilog2()`
///
/// ### Why is this bad?
/// Manual reimplementations of `ilog2` increase code complexity for little benefit.
///
/// ### Example
/// ```no_run
/// let x: u32 = 5;
/// let log = 31 - x.leading_zeros();
/// let log = x.ilog(2);
/// ```
/// Use instead:
/// ```no_run
/// let x: u32 = 5;
/// let log = x.ilog2();
/// let log = x.ilog2();
/// ```
#[clippy::version = "1.93.0"]
pub MANUAL_ILOG2,
pedantic,
"manually reimplementing `ilog2`"
}
pub struct ManualIlog2 {
msrv: Msrv,
}
impl ManualIlog2 {
pub fn new(conf: &Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
impl LateLintPass<'_> for ManualIlog2 {
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if expr.span.in_external_macro(cx.sess().source_map()) {
return;
}
match expr.kind {
// `BIT_WIDTH - 1 - n.leading_zeros()`
ExprKind::Binary(op, left, right)
if left.span.eq_ctxt(right.span)
&& op.node == BinOpKind::Sub
&& let ExprKind::Lit(lit) = left.kind
&& let LitKind::Int(Pu128(val), _) = lit.node
&& let ExprKind::MethodCall(leading_zeros, recv, [], _) = right.kind
&& leading_zeros.ident.name == sym::leading_zeros
&& let ty = cx.typeck_results().expr_ty(recv)
&& let Some(bit_width) = match ty.kind() {
ty::Uint(uint_ty) => uint_ty.bit_width(),
ty::Int(_) => {
// On non-positive integers, `ilog2` would panic, which might be a sign that the author does
// in fact want to calculate something different, so stay on the safer side and don't
// suggest anything.
return;
},
_ => return,
}
&& val == u128::from(bit_width) - 1
&& self.msrv.meets(cx, msrvs::ILOG2)
&& !is_from_proc_macro(cx, expr) =>
{
emit(cx, recv, expr);
},
// `n.ilog(2)`
ExprKind::MethodCall(ilog, recv, [two], _)
if expr.span.eq_ctxt(two.span)
&& ilog.ident.name == sym::ilog
&& let ExprKind::Lit(lit) = two.kind
&& let LitKind::Int(Pu128(2), _) = lit.node
&& cx.typeck_results().expr_ty_adjusted(recv).is_integral()
/* no need to check MSRV here, as `ilog` and `ilog2` were introduced simultaneously */
&& !is_from_proc_macro(cx, expr) =>
{
emit(cx, recv, expr);
},
_ => {},
}
}
}
fn emit(cx: &LateContext<'_>, recv: &Expr<'_>, full_expr: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
span_lint_and_sugg(
cx,
MANUAL_ILOG2,
full_expr.span,
"manually reimplementing `ilog2`",
"try",
format!("{recv}.ilog2()"),
app,
);
}

View file

@ -1,7 +1,8 @@
//! Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::has_let_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_lint_allowed, is_wild, span_contains_comment};
use rustc_ast::LitKind;
@ -43,18 +44,23 @@ pub(crate) fn check_if_let<'tcx>(
{
ex_new = ex_inner;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
"if let .. else expression looks like `matches!` macro",
"try",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
"`if let .. else` expression looks like `matches!` macro",
|diag| {
diag.span_suggestion_verbose(
expr.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
},
);
}
}
@ -87,7 +93,10 @@ pub(super) fn check_match<'tcx>(
// ```rs
// matches!(e, Either::Left $(if $guard)|+)
// ```
middle_arms.is_empty()
//
// But if the guard _is_ present, it may not be an `if-let` guard, as `matches!` doesn't
// support these (currently?)
(middle_arms.is_empty() && first_arm.guard.is_none_or(|g| !has_let_expr(g)))
// - (added in #6216) There are middle arms
//
@ -169,18 +178,23 @@ pub(super) fn check_match<'tcx>(
{
ex_new = ex_inner;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
MATCH_LIKE_MATCHES_MACRO,
e.span,
"match expression looks like `matches!` macro",
"try",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
|diag| {
diag.span_suggestion_verbose(
e.span,
"use `matches!` directly",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
},
);
true
} else {

View file

@ -4,18 +4,20 @@ use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sym;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::{self as hir, Expr};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_middle::ty::layout::LayoutOf;
use rustc_span::Symbol;
pub fn check(
pub fn check_unwrap_or(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
arith_lhs: &hir::Expr<'_>,
arith_rhs: &hir::Expr<'_>,
unwrap_arg: &hir::Expr<'_>,
arith: &str,
expr: &Expr<'_>,
arith_lhs: &Expr<'_>,
arith_rhs: &Expr<'_>,
unwrap_arg: &Expr<'_>,
arith: Symbol,
) {
let ty = cx.typeck_results().expr_ty(arith_lhs);
if !ty.is_integral() {
@ -26,35 +28,75 @@ pub fn check(
return;
};
if ty.is_signed() {
use self::MinMax::{Max, Min};
use self::Sign::{Neg, Pos};
let Some(checked_arith) = CheckedArith::new(arith) else {
return;
};
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
}
pub(super) fn check_sub_unwrap_or_default(
cx: &LateContext<'_>,
expr: &Expr<'_>,
arith_lhs: &Expr<'_>,
arith_rhs: &Expr<'_>,
) {
let ty = cx.typeck_results().expr_ty(arith_lhs);
if !ty.is_integral() {
return;
}
let mm = if ty.is_signed() {
return; // iN::default() is 0, which is neither MIN nor MAX
} else {
MinMax::Min // uN::default() is 0, which is also the MIN
};
let checked_arith = CheckedArith::Sub;
check(cx, expr, arith_lhs, arith_rhs, ty, mm, checked_arith);
}
fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
arith_lhs: &Expr<'_>,
arith_rhs: &Expr<'_>,
ty: Ty<'_>,
mm: MinMax,
checked_arith: CheckedArith,
) {
use self::MinMax::{Max, Min};
use self::Sign::{Neg, Pos};
use CheckedArith::{Add, Mul, Sub};
if ty.is_signed() {
let Some(sign) = lit_sign(arith_rhs) else {
return;
};
match (arith, sign, mm) {
("add", Pos, Max) | ("add", Neg, Min) | ("sub", Neg, Max) | ("sub", Pos, Min) => (),
match (checked_arith, sign, mm) {
(Add, Pos, Max) | (Add, Neg, Min) | (Sub, Neg, Max) | (Sub, Pos, Min) => (),
// "mul" is omitted because lhs can be negative.
_ => return,
}
} else {
match (mm, arith) {
(MinMax::Max, "add" | "mul") | (MinMax::Min, "sub") => (),
match (mm, checked_arith) {
(Max, Add | Mul) | (Min, Sub) => (),
_ => return,
}
}
let mut applicability = Applicability::MachineApplicable;
let saturating_arith = checked_arith.as_saturating();
span_lint_and_sugg(
cx,
super::MANUAL_SATURATING_ARITHMETIC,
expr.span,
"manual saturating arithmetic",
format!("consider using `saturating_{arith}`"),
format!("consider using `{saturating_arith}`"),
format!(
"{}.saturating_{arith}({})",
"{}.{saturating_arith}({})",
snippet_with_applicability(cx, arith_lhs.span, "..", &mut applicability),
snippet_with_applicability(cx, arith_rhs.span, "..", &mut applicability),
),
@ -62,13 +104,40 @@ pub fn check(
);
}
#[derive(Clone, Copy)]
enum CheckedArith {
Add,
Sub,
Mul,
}
impl CheckedArith {
fn new(sym: Symbol) -> Option<Self> {
let res = match sym {
sym::checked_add => Self::Add,
sym::checked_sub => Self::Sub,
sym::checked_mul => Self::Mul,
_ => return None,
};
Some(res)
}
fn as_saturating(self) -> &'static str {
match self {
Self::Add => "saturating_add",
Self::Sub => "saturating_sub",
Self::Mul => "saturating_mul",
}
}
}
#[derive(PartialEq, Eq)]
enum MinMax {
Min,
Max,
}
fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
fn is_min_or_max(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<MinMax> {
// `T::max_value()` `T::min_value()` inherent methods
if let hir::ExprKind::Call(func, []) = &expr.kind
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind
@ -106,7 +175,7 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
(0, if bits == 128 { !0 } else { (1 << bits) - 1 })
};
let check_lit = |expr: &hir::Expr<'_>, check_min: bool| {
let check_lit = |expr: &Expr<'_>, check_min: bool| {
if let hir::ExprKind::Lit(lit) = &expr.kind
&& let ast::LitKind::Int(value, _) = lit.node
{
@ -141,7 +210,7 @@ enum Sign {
Neg,
}
fn lit_sign(expr: &hir::Expr<'_>) -> Option<Sign> {
fn lit_sign(expr: &Expr<'_>) -> Option<Sign> {
if let hir::ExprKind::Unary(hir::UnOp::Neg, inner) = &expr.kind {
if let hir::ExprKind::Lit(..) = &inner.kind {
return Some(Sign::Neg);

View file

@ -94,6 +94,7 @@ mod or_fun_call;
mod or_then_unwrap;
mod path_buf_push_overwrite;
mod path_ends_with_ext;
mod ptr_offset_by_literal;
mod ptr_offset_with_cast;
mod range_zip_with_len;
mod read_line_without_trim;
@ -1728,6 +1729,40 @@ declare_clippy_lint! {
"Check for offset calculations on raw pointers to zero-sized types"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of the `offset` pointer method with an integer
/// literal.
///
/// ### Why is this bad?
/// The `add` and `sub` methods more accurately express the intent.
///
/// ### Example
/// ```no_run
/// let vec = vec![b'a', b'b', b'c'];
/// let ptr = vec.as_ptr();
///
/// unsafe {
/// ptr.offset(-8);
/// }
/// ```
///
/// Could be written:
///
/// ```no_run
/// let vec = vec![b'a', b'b', b'c'];
/// let ptr = vec.as_ptr();
///
/// unsafe {
/// ptr.sub(8);
/// }
/// ```
#[clippy::version = "1.92.0"]
pub PTR_OFFSET_BY_LITERAL,
pedantic,
"unneeded pointer offset"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of the `offset` pointer method with a `usize` casted to an
@ -4635,7 +4670,7 @@ declare_clippy_lint! {
/// let x = vec![String::new()];
/// let _ = x.iter().map(|x| x.len());
/// ```
#[clippy::version = "1.90.0"]
#[clippy::version = "1.92.0"]
pub REDUNDANT_ITER_CLONED,
perf,
"detects redundant calls to `Iterator::cloned`"
@ -4659,7 +4694,7 @@ declare_clippy_lint! {
/// let x: Option<u32> = Some(4);
/// let y = x.unwrap_or_else(|| 2 * k);
/// ```
#[clippy::version = "1.88.0"]
#[clippy::version = "1.92.0"]
pub UNNECESSARY_OPTION_MAP_OR_ELSE,
suspicious,
"making no use of the \"map closure\" when calling `.map_or_else(|| 2 * k, |n| n)`"
@ -4803,6 +4838,7 @@ impl_lint_pass!(Methods => [
UNINIT_ASSUMED_INIT,
MANUAL_SATURATING_ARITHMETIC,
ZST_OFFSET,
PTR_OFFSET_BY_LITERAL,
PTR_OFFSET_WITH_CAST,
FILETYPE_IS_FILE,
OPTION_AS_REF_DEREF,
@ -5426,6 +5462,7 @@ impl Methods {
zst_offset::check(cx, expr, recv);
ptr_offset_with_cast::check(cx, name, expr, recv, arg, self.msrv);
ptr_offset_by_literal::check(cx, expr, self.msrv);
},
(sym::ok_or_else, [arg]) => {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or");
@ -5567,14 +5604,7 @@ impl Methods {
(sym::unwrap_or, [u_arg]) => {
match method_call(recv) {
Some((arith @ (sym::checked_add | sym::checked_sub | sym::checked_mul), lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check(
cx,
expr,
lhs,
rhs,
u_arg,
&arith.as_str()[const { "checked_".len() }..],
);
manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith);
},
Some((sym::map, m_recv, [m_arg], span, _)) => {
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
@ -5595,6 +5625,9 @@ impl Methods {
},
(sym::unwrap_or_default, []) => {
match method_call(recv) {
Some((sym::checked_sub, lhs, [rhs], _, _)) => {
manual_saturating_arithmetic::check_sub_unwrap_or_default(cx, expr, lhs, rhs);
},
Some((sym::map, m_recv, [arg], span, _)) => {
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
},

View file

@ -0,0 +1,138 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::sym;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Lit, UnOp};
use rustc_lint::LateContext;
use std::cmp::Ordering;
use std::fmt;
use super::PTR_OFFSET_BY_LITERAL;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Msrv) {
// `pointer::add` and `pointer::wrapping_add` are only stable since 1.26.0. These functions
// became const-stable in 1.61.0, the same version that `pointer::offset` became const-stable.
if !msrv.meets(cx, msrvs::POINTER_ADD_SUB_METHODS) {
return;
}
let ExprKind::MethodCall(method_name, recv, [arg_expr], _) = expr.kind else {
return;
};
let method = match method_name.ident.name {
sym::offset => Method::Offset,
sym::wrapping_offset => Method::WrappingOffset,
_ => return,
};
if !cx.typeck_results().expr_ty_adjusted(recv).is_raw_ptr() {
return;
}
// Check if the argument to the method call is a (negated) literal.
let Some((literal, literal_text)) = expr_as_literal(cx, arg_expr) else {
return;
};
match method.suggestion(literal) {
None => {
let msg = format!("use of `{method}` with zero");
span_lint_and_then(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg, |diag| {
diag.span_suggestion(
expr.span.with_lo(recv.span.hi()),
format!("remove the call to `{method}`"),
String::new(),
Applicability::MachineApplicable,
);
});
},
Some(method_suggestion) => {
let msg = format!("use of `{method}` with a literal");
span_lint_and_then(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg, |diag| {
diag.multipart_suggestion(
format!("use `{method_suggestion}` instead"),
vec![
(method_name.ident.span, method_suggestion.to_string()),
(arg_expr.span, literal_text),
],
Applicability::MachineApplicable,
);
});
},
}
}
fn get_literal_bits<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<u128> {
match expr.kind {
ExprKind::Lit(Lit {
node: LitKind::Int(packed_u128, _),
..
}) => Some(packed_u128.get()),
_ => None,
}
}
// If the given expression is a (negated) literal, return its value.
fn expr_as_literal<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<(i128, String)> {
if let Some(literal_bits) = get_literal_bits(expr) {
// The value must fit in a isize, so we can't have overflow here.
return Some((literal_bits.cast_signed(), format_isize_literal(cx, expr)?));
}
if let ExprKind::Unary(UnOp::Neg, inner) = expr.kind
&& let Some(literal_bits) = get_literal_bits(inner)
{
return Some((-(literal_bits.cast_signed()), format_isize_literal(cx, inner)?));
}
None
}
fn format_isize_literal<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<String> {
let text = expr.span.get_source_text(cx)?;
let text = peel_parens_str(&text);
Some(text.trim_end_matches("isize").trim_end_matches('_').to_string())
}
fn peel_parens_str(snippet: &str) -> &str {
let mut s = snippet.trim();
while let Some(next) = s.strip_prefix("(").and_then(|suf| suf.strip_suffix(")")) {
s = next.trim();
}
s
}
#[derive(Copy, Clone)]
enum Method {
Offset,
WrappingOffset,
}
impl Method {
fn suggestion(self, literal: i128) -> Option<&'static str> {
match Ord::cmp(&literal, &0) {
Ordering::Greater => match self {
Method::Offset => Some("add"),
Method::WrappingOffset => Some("wrapping_add"),
},
// `ptr.offset(0)` is equivalent to `ptr`, so no adjustment is needed
Ordering::Equal => None,
Ordering::Less => match self {
Method::Offset => Some("sub"),
Method::WrappingOffset => Some("wrapping_sub"),
},
}
}
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Offset => write!(f, "offset"),
Self::WrappingOffset => write!(f, "wrapping_offset"),
}
}
}

View file

@ -5,7 +5,7 @@ use clippy_utils::comparisons::{Rel, normalize_comparison};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::{If, Range};
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace, root_macro_call};
use clippy_utils::source::snippet;
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{eq_expr_value, hash_expr};
use rustc_ast::{BinOpKind, LitKind, RangeLimits};
@ -67,16 +67,13 @@ declare_clippy_lint! {
}
declare_lint_pass!(MissingAssertsForIndexing => [MISSING_ASSERTS_FOR_INDEXING]);
fn report_lint<F>(cx: &LateContext<'_>, full_span: Span, msg: &'static str, indexes: &[Span], f: F)
fn report_lint<F>(cx: &LateContext<'_>, index_spans: Vec<Span>, msg: &'static str, f: F)
where
F: FnOnce(&mut Diag<'_, ()>),
{
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, full_span, msg, |diag| {
span_lint_and_then(cx, MISSING_ASSERTS_FOR_INDEXING, index_spans, 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");
diag.note_once("asserting the length before indexing will elide bounds checks");
});
}
@ -213,15 +210,6 @@ impl<'hir> IndexEntry<'hir> {
| 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.
@ -354,63 +342,47 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
/// 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: &UnindexMap<u64, Vec<IndexEntry<'_>>>) {
for bucket in map.values() {
fn report_indexes(cx: &LateContext<'_>, map: UnindexMap<u64, Vec<IndexEntry<'_>>>) {
for bucket in map.into_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 {
match entry {
IndexEntry::AssertWithIndex {
highest_index,
is_first_highest,
asserted_len,
ref indexes,
indexes,
comparison,
assert_span,
slice,
macro_call,
} if indexes.len() > 1 && !is_first_highest => {
let mut app = Applicability::MachineApplicable;
let slice_str = snippet_with_applicability(cx, slice.span, "_", &mut app);
// if we have found an `assert!`, let's also check that it's actually right
// and if it covers 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, "..")),
),
LengthComparison::LengthLessThanInt | LengthComparison::LengthLessThanOrEqualInt => {
Some(format!("assert!({slice_str}.len() > {highest_index})",))
},
// `5 < v.len()` == `v.len() > 5`
LengthComparison::IntLessThanLength if asserted_len < highest_index => Some(format!(
"assert!({}.len() > {highest_index})",
snippet(cx, slice.span, "..")
)),
LengthComparison::IntLessThanLength if asserted_len < highest_index => {
Some(format!("assert!({slice_str}.len() > {highest_index})",))
},
// `5 <= v.len() == `v.len() >= 5`
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => Some(format!(
"assert!({}.len() > {highest_index})",
snippet(cx, slice.span, "..")
)),
LengthComparison::IntLessThanOrEqualLength if asserted_len <= highest_index => {
Some(format!("assert!({slice_str}.len() > {highest_index})",))
},
// `highest_index` here is rather a length, so we need to add 1 to it
LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => match macro_call {
sym::assert_eq_macro => Some(format!(
"assert_eq!({}.len(), {})",
snippet(cx, slice.span, ".."),
highest_index + 1
)),
sym::debug_assert_eq_macro => Some(format!(
"debug_assert_eq!({}.len(), {})",
snippet(cx, slice.span, ".."),
highest_index + 1
)),
_ => Some(format!(
"assert!({}.len() == {})",
snippet(cx, slice.span, ".."),
highest_index + 1
)),
sym::assert_eq_macro => {
Some(format!("assert_eq!({slice_str}.len(), {})", highest_index + 1))
},
sym::debug_assert_eq_macro => {
Some(format!("debug_assert_eq!({slice_str}.len(), {})", highest_index + 1))
},
_ => Some(format!("assert!({slice_str}.len() == {})", highest_index + 1)),
},
_ => None,
};
@ -418,22 +390,21 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>
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,
"indexing into a slice multiple times with an `assert` that does not cover the highest index",
|diag| {
diag.span_suggestion(
diag.span_suggestion_verbose(
assert_span,
"provide the highest index that is indexed with",
sugg,
Applicability::MachineApplicable,
app,
);
},
);
}
},
IndexEntry::IndexWithoutAssert {
ref indexes,
indexes,
highest_index,
is_first_highest,
slice,
@ -442,9 +413,8 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>
// adding an `assert!` that covers the highest index
report_lint(
cx,
full_span,
"indexing into a slice multiple times without an `assert`",
indexes,
"indexing into a slice multiple times without an `assert`",
|diag| {
diag.help(format!(
"consider asserting the length before indexing: `assert!({}.len() > {highest_index});`",
@ -469,6 +439,6 @@ impl LateLintPass<'_> for MissingAssertsForIndexing {
ControlFlow::<!, ()>::Continue(())
});
report_indexes(cx, &map);
report_indexes(cx, map);
}
}

View file

@ -115,35 +115,41 @@ impl EarlyLintPass for MacroBraces {
}
fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBraces) -> Option<MacroInfo> {
let unnested_or_local = || {
!span.ctxt().outer_expn_data().call_site.from_expansion()
let unnested_or_local = |span: Span| {
!span.from_expansion()
|| span
.macro_backtrace()
.last()
.is_some_and(|e| e.macro_def_id.is_some_and(DefId::is_local))
};
let callsite_span = span.ctxt().outer_expn_data().call_site;
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind
let mut ctxt = span.ctxt();
while !ctxt.is_root() {
let expn_data = ctxt.outer_expn_data();
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = expn_data.kind
&& let name = mac_name.as_str()
&& let Some(&braces) = mac_braces.macro_braces.get(name)
&& let Some(snip) = callsite_span.get_source_text(cx)
&& let Some(snip) = expn_data.call_site.get_source_text(cx)
// we must check only invocation sites
// https://github.com/rust-lang/rust-clippy/issues/7422
&& let Some(macro_args_str) = snip.strip_prefix(name).and_then(|snip| snip.strip_prefix('!'))
&& let Some(old_open_brace @ ('{' | '(' | '[')) = macro_args_str.trim_start().chars().next()
&& old_open_brace != braces.0
&& unnested_or_local()
&& !mac_braces.done.contains(&callsite_span)
{
Some(MacroInfo {
callsite_span,
callsite_snippet: snip,
old_open_brace,
braces,
})
} else {
None
&& unnested_or_local(expn_data.call_site)
&& !mac_braces.done.contains(&expn_data.call_site)
{
return Some(MacroInfo {
callsite_span: expn_data.call_site,
callsite_snippet: snip,
old_open_brace,
braces,
});
}
ctxt = expn_data.call_site.ctxt();
}
None
}
fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span, add_semi: bool) {

View file

@ -0,0 +1,76 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::numeric_literal;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::SpanRangeExt;
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use super::DECIMAL_BITWISE_OPERANDS;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, left: &'tcx Expr<'_>, right: &'tcx Expr<'_>) {
if !matches!(op, BinOpKind::BitAnd | BinOpKind::BitOr | BinOpKind::BitXor) {
return;
}
for expr in [left, right] {
check_expr(cx, expr);
}
}
fn check_expr(cx: &LateContext<'_>, expr: &Expr<'_>) {
match &expr.kind {
ExprKind::Block(block, _) => {
if let Some(block_expr) = block.expr {
check_expr(cx, block_expr);
}
},
ExprKind::Cast(cast_expr, _) => {
check_expr(cx, cast_expr);
},
ExprKind::Unary(_, unary_expr) => {
check_expr(cx, unary_expr);
},
ExprKind::AddrOf(_, _, addr_of_expr) => {
check_expr(cx, addr_of_expr);
},
ExprKind::Lit(lit) => {
if let LitKind::Int(Pu128(val), _) = lit.node
&& !is_single_digit(val)
&& !is_power_of_twoish(val)
&& let Some(src) = lit.span.get_source_text(cx)
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
&& num_lit.is_decimal()
{
emit_lint(cx, lit.span, num_lit.suffix, val);
}
},
_ => (),
}
}
fn is_power_of_twoish(val: u128) -> bool {
val.is_power_of_two() || val.wrapping_add(1).is_power_of_two()
}
fn is_single_digit(val: u128) -> bool {
val <= 9
}
fn emit_lint(cx: &LateContext<'_>, span: Span, suffix: Option<&str>, val: u128) {
span_lint_and_help(
cx,
DECIMAL_BITWISE_OPERANDS,
span,
"using decimal literal for bitwise operation",
None,
format!(
"use binary ({}), hex ({}), or octal ({}) notation for better readability",
numeric_literal::format(&format!("{val:#b}"), suffix, false),
numeric_literal::format(&format!("{val:#x}"), suffix, false),
numeric_literal::format(&format!("{val:#o}"), suffix, false),
),
);
}

View file

@ -3,6 +3,7 @@ mod assign_op_pattern;
mod bit_mask;
mod cmp_owned;
mod const_comparisons;
mod decimal_bitwise_operands;
mod double_comparison;
mod duration_subsec;
mod eq_op;
@ -935,6 +936,28 @@ declare_clippy_lint! {
"use of disallowed default division and remainder operations"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for decimal literals used as bit masks in bitwise operations.
///
/// ### Why is this bad?
/// Using decimal literals for bit masks can make the code less readable and obscure the intended bit pattern.
/// Binary, hexadecimal, or octal literals make the bit pattern more explicit and easier to understand at a glance.
///
/// ### Example
/// ```rust,no_run
/// let a = 14 & 6; // Bit pattern is not immediately clear
/// ```
/// Use instead:
/// ```rust,no_run
/// let a = 0b1110 & 0b0110;
/// ```
#[clippy::version = "1.93.0"]
pub DECIMAL_BITWISE_OPERANDS,
pedantic,
"use binary, hex, or octal literals for bitwise operations"
}
pub struct Operators {
arithmetic_context: numeric_arithmetic::Context,
verbose_bit_mask_threshold: u64,
@ -984,6 +1007,7 @@ impl_lint_pass!(Operators => [
MANUAL_IS_MULTIPLE_OF,
MANUAL_DIV_CEIL,
INVALID_UPCAST_COMPARISONS,
DECIMAL_BITWISE_OPERANDS
]);
impl<'tcx> LateLintPass<'tcx> for Operators {
@ -1003,6 +1027,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
needless_bitwise_bool::check(cx, e, op.node, lhs, rhs);
manual_midpoint::check(cx, e, op.node, lhs, rhs, self.msrv);
manual_is_multiple_of::check(cx, e, op.node, lhs, rhs, self.msrv);
decimal_bitwise_operands::check(cx, op.node, lhs, rhs);
}
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
bit_mask::check(cx, e, op.node, lhs, rhs);
@ -1028,6 +1053,9 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
},
ExprKind::AssignOp(op, lhs, rhs) => {
let bin_op = op.node.into();
if !e.span.from_expansion() {
decimal_bitwise_operands::check(cx, bin_op, lhs, rhs);
}
self.arithmetic_context.check_binary(cx, e, bin_op, lhs, rhs);
misrefactored_assign_op::check(cx, e, bin_op, lhs, rhs);
modulo_arithmetic::check(cx, e, bin_op, lhs, rhs, false);

View file

@ -18,8 +18,11 @@ mod wrong_transmute;
use clippy_config::Conf;
use clippy_utils::is_in_const_context;
use clippy_utils::msrvs::Msrv;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::symbol::sym;
@ -490,6 +493,32 @@ impl Transmute {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
/// When transmuting, a struct containing a single field works like the field.
/// This function extracts the field type and the expression to get the field.
fn extract_struct_field<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
outer_type: Ty<'tcx>,
outer: &'tcx Expr<'tcx>,
) -> (Ty<'tcx>, Sugg<'tcx>) {
let mut applicability = Applicability::MachineApplicable;
let outer_sugg = Sugg::hir_with_context(cx, outer, e.span.ctxt(), "..", &mut applicability);
if let ty::Adt(struct_def, struct_args) = *outer_type.kind()
&& struct_def.is_struct()
&& let mut fields = struct_def.all_fields()
&& let Some(first) = fields.next()
&& fields.next().is_none()
&& first.vis.is_accessible_from(cx.tcx.parent_module(outer.hir_id), cx.tcx)
{
(
first.ty(cx.tcx, struct_args),
Sugg::NonParen(format!("{}.{}", outer_sugg.maybe_paren(), first.name).into()),
)
} else {
(outer_type, outer_sugg)
}
}
}
impl<'tcx> LateLintPass<'tcx> for Transmute {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
@ -516,14 +545,17 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
return;
}
// A struct having a single pointer can be treated like a pointer.
let (from_field_ty, from_field_expr) = Self::extract_struct_field(cx, e, from_ty, arg);
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
| transmuting_null::check(cx, e, arg, to_ty)
| transmute_null_to_fn::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| transmute_ptr_to_ref::check(cx, e, from_field_ty, to_ty, from_field_expr.clone(), path, self.msrv)
| missing_transmute_annotations::check(cx, path, arg, from_ty, to_ty, e.hir_id)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv)
| transmute_ptr_to_ptr::check(cx, e, from_field_ty, to_ty, from_field_expr, self.msrv)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_non_zero::check(cx, e, from_ty, to_ty, arg)
| (unsound_collection_transmute::check(cx, e, from_ty, to_ty)

View file

@ -14,11 +14,9 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
arg: sugg::Sugg<'_>,
msrv: Msrv,
) -> bool {
let mut applicability = Applicability::MachineApplicable;
let arg_sugg = sugg::Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut applicability);
match (from_ty.kind(), to_ty.kind()) {
(ty::RawPtr(from_pointee_ty, from_mutbl), ty::RawPtr(to_pointee_ty, to_mutbl)) => {
span_lint_and_then(
@ -34,7 +32,7 @@ pub(super) fn check<'tcx>(
diag.span_suggestion_verbose(
e.span,
"use `pointer::cast` instead",
format!("{}.cast::<{to_pointee_ty}>()", arg_sugg.maybe_paren()),
format!("{}.cast::<{to_pointee_ty}>()", arg.maybe_paren()),
Applicability::MaybeIncorrect,
);
} else if from_pointee_ty == to_pointee_ty
@ -49,14 +47,14 @@ pub(super) fn check<'tcx>(
diag.span_suggestion_verbose(
e.span,
format!("use `pointer::{method}` instead"),
format!("{}.{method}()", arg_sugg.maybe_paren()),
format!("{}.{method}()", arg.maybe_paren()),
Applicability::MaybeIncorrect,
);
} else {
diag.span_suggestion_verbose(
e.span,
"use an `as` cast instead",
arg_sugg.as_ty(to_ty),
arg.as_ty(to_ty),
Applicability::MaybeIncorrect,
);
}

View file

@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
arg: sugg::Sugg<'_>,
path: &'tcx Path<'_>,
msrv: Msrv,
) -> bool {
@ -27,7 +27,6 @@ pub(super) fn check<'tcx>(
e.span,
format!("transmute from a pointer type (`{from_ty}`) to a reference type (`{to_ty}`)"),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
let (deref, cast) = match mutbl {
Mutability::Mut => ("&mut *", "*mut"),
Mutability::Not => ("&*", "*const"),

View file

@ -1,9 +1,9 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeResPath;
use clippy_utils::visitors::for_each_local_use_after_expr;
use clippy_utils::visitors::local_used_once;
use clippy_utils::{get_enclosing_block, is_from_proc_macro};
use itertools::Itertools;
use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind, Node, PatKind};
@ -11,7 +11,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use std::iter::once;
use std::ops::ControlFlow;
declare_clippy_lint! {
/// ### What it does
@ -86,7 +85,7 @@ fn check_array<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
ExprKind::Path(_) => Some(elements.iter().collect()),
_ => None,
})
&& all_bindings_are_for_conv(cx, &[ty], expr, elements, &locals, ToType::Array)
&& all_bindings_are_for_conv(cx, &[ty], elements, &locals, ToType::Array)
&& !is_from_proc_macro(cx, expr)
{
span_lint_and_help(
@ -123,7 +122,7 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
ExprKind::Path(_) => Some(elements.iter().collect()),
_ => None,
})
&& all_bindings_are_for_conv(cx, tys, expr, elements, &locals, ToType::Tuple)
&& all_bindings_are_for_conv(cx, tys, elements, &locals, ToType::Tuple)
&& !is_from_proc_macro(cx, expr)
{
span_lint_and_help(
@ -148,7 +147,6 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: &
fn all_bindings_are_for_conv<'tcx>(
cx: &LateContext<'tcx>,
final_tys: &[Ty<'tcx>],
expr: &Expr<'_>,
elements: &[Expr<'_>],
locals: &[&Expr<'_>],
kind: ToType,
@ -166,13 +164,30 @@ fn all_bindings_are_for_conv<'tcx>(
_ => None,
})
.all_equal()
// Fix #11124, very convenient utils function! ❤️
&& locals
.iter()
.all(|&l| for_each_local_use_after_expr(cx, l, expr.hir_id, |_| ControlFlow::Break::<()>(())).is_continue())
&& locals.iter().zip(local_parents.iter()).all(|(&l, &parent)| {
if let Node::LetStmt(_) = parent {
return true;
}
let Some(b) = get_enclosing_block(cx, l) else {
return true;
};
local_used_once(cx, b, l).is_some()
})
&& local_parents.first().is_some_and(|node| {
let Some(ty) = match node {
Node::Pat(pat) => Some(pat.hir_id),
Node::Pat(pat)
if let PatKind::Tuple(pats, _) | PatKind::Slice(pats, None, []) = &pat.kind
&& pats.iter().zip(locals.iter()).all(|(p, l)| {
if let PatKind::Binding(_, id, _, _) = p.kind {
id == *l
} else {
true
}
}) =>
{
Some(pat.hir_id)
},
Node::LetStmt(l) => Some(l.hir_id),
_ => None,
}
@ -186,7 +201,9 @@ 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)) => {
let Some(len) = len.try_to_target_usize(cx.tcx) else { return false };
let Some(len) = len.try_to_target_usize(cx.tcx) else {
return false;
};
len as usize == elements.len() && final_tys.iter().chain(once(ty)).all_equal()
},
_ => false,

View file

@ -180,14 +180,19 @@ impl Local {
field_indices,
..
} => {
let field_projections = place
.projections
.iter()
.filter(|proj| matches!(proj.kind, ProjectionKind::Field(_, _)))
.collect::<Vec<_>>();
is_potentially_local_place(*local_id, place)
// If there were projections other than field projections, err on the side of caution and say that they
// _might_ be mutating something.
//
// The reason we use `<=` and not `==` is that a mutation of `struct` or `struct.field1` should count as
// mutation of the child fields such as `struct.field1.field2`
&& place.projections.len() <= field_indices.len()
&& iter::zip(&place.projections, field_indices.iter().copied().rev()).all(|(proj, field_idx)| {
&& field_projections.len() <= field_indices.len()
&& iter::zip(&field_projections, field_indices.iter().copied().rev()).all(|(proj, field_idx)| {
match proj.kind {
ProjectionKind::Field(f_idx, _) => f_idx == field_idx,
// If this is a projection we don't expect, it _might_ be mutating something

View file

@ -354,7 +354,10 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
return;
}
let sugg = snippet(cx, recv.span, "<expr>").into_owned();
let mut applicability = Applicability::MachineApplicable;
let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "<expr>", &mut applicability)
.0
.into_owned();
span_lint_and_sugg(
cx,
USELESS_CONVERSION,

View file

@ -4,10 +4,11 @@ use clippy_utils::source::{snippet, snippet_indent};
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{ConstArgKind, ExprKind, Node};
use rustc_hir::{ConstArgKind, Expr, ExprKind, LetStmt, LocalSource, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::IsSuggestable;
use rustc_middle::ty::{IsSuggestable, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -44,7 +45,7 @@ declare_clippy_lint! {
declare_lint_pass!(ZeroRepeatSideEffects => [ZERO_REPEAT_SIDE_EFFECTS]);
impl LateLintPass<'_> for ZeroRepeatSideEffects {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &rustc_hir::Expr<'_>) {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(args) = VecArgs::hir(cx, expr)
&& let VecArgs::Repeat(inner_expr, len) = args
&& let ExprKind::Lit(l) = len.kind
@ -69,7 +70,7 @@ impl LateLintPass<'_> for ZeroRepeatSideEffects {
}
}
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
fn inner_check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, inner_expr: &'_ Expr<'_>, is_vec: bool) {
// check if expr is a call or has a call inside it
if inner_expr.can_have_side_effects() {
let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id);
@ -81,19 +82,22 @@ fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr:
let vec = if is_vec { "vec!" } else { "" };
let (span, sugg) = match parent_hir_node {
Node::LetStmt(l) => (
l.span,
format!(
"{inner_expr};\n{indent}let {var_name}: {return_type} = {vec}[];",
var_name = snippet(cx, l.pat.span.source_callsite(), "..")
),
),
Node::LetStmt(l)
if matches!(l.source, LocalSource::AssignDesugar)
&& let mut parent_iter = cx.tcx.hir_parent_iter(l.hir_id)
&& let Some((_, Node::Stmt(_))) = parent_iter.next()
&& let Some((_, Node::Block(_))) = parent_iter.next()
&& let Some((_, Node::Expr(x))) = parent_iter.next() =>
{
(
x.span,
assign_expr_suggestion(cx, x, l.pat.span, &inner_expr, return_type, vec),
)
},
Node::LetStmt(l) => (l.span, let_stmt_suggestion(cx, l, &inner_expr, return_type, vec)),
Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => (
x.span,
format!(
"{inner_expr};\n{indent}{var_name} = {vec}[] as {return_type}",
var_name = snippet(cx, l.span.source_callsite(), "..")
),
assign_expr_suggestion(cx, x, l.span, &inner_expr, return_type, vec),
),
// NOTE: don't use the stmt span to avoid touching the trailing semicolon
Node::Stmt(_) => (expr.span, format!("{inner_expr};\n{indent}{vec}[] as {return_type}")),
@ -131,3 +135,41 @@ fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr:
);
}
}
fn let_stmt_suggestion(
cx: &LateContext<'_>,
let_stmt: &LetStmt<'_>,
inner_expr: &str,
return_type: Ty<'_>,
vec_str: &str,
) -> String {
let indent = snippet_indent(cx, let_stmt.span).unwrap_or_default();
format!(
"{inner_expr};\n{}let {var_name}: {return_type} = {vec_str}[];",
indent,
var_name = snippet(cx, let_stmt.pat.span.source_callsite(), "..")
)
}
fn assign_expr_suggestion(
cx: &LateContext<'_>,
outer_expr: &Expr<'_>,
assign_expr_span: Span,
inner_expr: &str,
return_type: Ty<'_>,
vec_str: &str,
) -> String {
let mut parent_hir_node = cx.tcx.parent_hir_node(outer_expr.hir_id);
if let Node::Stmt(stmt) = parent_hir_node {
parent_hir_node = cx.tcx.parent_hir_node(stmt.hir_id);
}
let needs_curly = !matches!(parent_hir_node, Node::Block(_));
let indent = snippet_indent(cx, outer_expr.span).unwrap_or_default();
let var_name = snippet(cx, assign_expr_span.source_callsite(), "..");
if needs_curly {
format!("{{\n {indent}{inner_expr};\n {indent}{var_name} = {vec_str}[] as {return_type}\n{indent}}}",)
} else {
format!("{inner_expr};\n{indent}{var_name} = {vec_str}[] as {return_type}")
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
description = "Helpful tools for writing lints, provided as they are used in Clippy"
repository = "https://github.com/rust-lang/rust-clippy"
@ -16,6 +16,10 @@ itertools = "0.12"
rustc_apfloat = "0.2.0"
serde = { version = "1.0", features = ["derive"] }
[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(bootstrap)']
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]
rustc_private = true

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2025-11-28
nightly-2025-12-11
```
<!-- end autogenerated nightly -->

View file

@ -40,6 +40,7 @@ msrv_aliases! {
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
1,68,0 { PATH_MAIN_SEPARATOR_STR }
1,67,0 { ILOG2 }
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
1,63,0 { CLONE_INTO, CONST_SLICE_FROM_REF }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }

View file

@ -180,6 +180,7 @@ generate! {
has_significant_drop,
hidden_glob_reexports,
hygiene,
ilog,
insert,
insert_str,
inspect,
@ -207,6 +208,7 @@ generate! {
join,
kw,
lazy_static,
leading_zeros,
lint_vec,
ln,
lock,

View file

@ -1,6 +1,6 @@
[package]
name = "declare_clippy_lint"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
repository = "https://github.com/rust-lang/rust-clippy"
license = "MIT OR Apache-2.0"

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2025-11-28"
channel = "nightly-2025-12-11"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -0,0 +1 @@
stack-size-threshold = 0

View file

@ -0,0 +1,42 @@
//@ignore-target: i686
//@normalize-stderr-test: "\b10000(08|16|32)\b" -> "100$$PTR"
//@normalize-stderr-test: "\b2500(060|120)\b" -> "250$$PTR"
#![warn(clippy::large_stack_frames)]
extern crate serde;
use serde::{Deserialize, Serialize};
struct ArrayDefault<const N: usize>([u8; N]);
macro_rules! mac {
($name:ident) => {
fn foo() {
let $name = 1;
println!("macro_name called");
}
fn bar() {
let $name = ArrayDefault([0; 1000]);
}
};
}
mac!(something);
//~^ large_stack_frames
//~| large_stack_frames
#[derive(Deserialize, Serialize)]
//~^ large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
//~| large_stack_frames
struct S {
a: [u128; 31],
}
fn main() {}

View file

@ -0,0 +1,89 @@
error: function `foo` generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:25:1
|
LL | fn foo() {
| --- this function has a stack frame size of 20 bytes
...
LL | mac!(something);
| ^^^^^^^^^^^^^^^
|
= note: 20 bytes is larger than Clippy's configured `stack-size-threshold` of 0
= note: allocating large amounts of stack space can overflow the stack and cause the program to abort
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
error: function `bar` generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:25:1
|
LL | fn bar() {
| --- this function has a stack frame size of 2000 bytes
LL | let $name = ArrayDefault([0; 1000]);
| --------- this is the largest part, at 1000 bytes for type `[u8; 1000]`
...
LL | mac!(something);
| ^^^^^^^^^^^^^^^
|
= note: 2000 bytes is larger than Clippy's configured `stack-size-threshold` of 0
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:10
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: method generated by this macro may allocate a lot of stack space
--> tests/ui-toml/large_stack_frames_for_macros/large_stack_frames.rs:29:23
|
LL | #[derive(Deserialize, Serialize)]
| ^^^^^^^^^
error: aborting due to 10 previous errors

View file

@ -0,0 +1,2 @@
stack-size-threshold = 0
allow-large-stack-frames-in-tests = false

View file

@ -0,0 +1,13 @@
// This test checks if `clippy::large_stack_frames` is working correctly when encountering functions
// generated by special compiling targets like `--test`.
//@compile-flags: --test
//@check-pass
#![warn(clippy::large_stack_frames)]
#[cfg(test)]
#[expect(clippy::large_stack_frames)]
mod test {
#[test]
fn main_test() {}
}

View file

@ -1,6 +1,7 @@
//@aux-build:proc_macro_derive.rs
#![warn(clippy::nonstandard_macro_braces)]
#![allow(clippy::println_empty_string)]
extern crate proc_macro_derive;
extern crate quote;
@ -75,3 +76,16 @@ fn issue9913() {
[0]; // separate statement, not indexing into the result of println.
//~^^ nonstandard_macro_braces
}
fn issue15594() {
println!();
println!("");
println!();
//~^ nonstandard_macro_braces
println!("");
//~^ nonstandard_macro_braces
println!();
//~^ nonstandard_macro_braces
println!("");
//~^ nonstandard_macro_braces
}

View file

@ -1,6 +1,7 @@
//@aux-build:proc_macro_derive.rs
#![warn(clippy::nonstandard_macro_braces)]
#![allow(clippy::println_empty_string)]
extern crate proc_macro_derive;
extern crate quote;
@ -75,3 +76,16 @@ fn issue9913() {
[0]; // separate statement, not indexing into the result of println.
//~^^ nonstandard_macro_braces
}
fn issue15594() {
println!();
println!("");
println![];
//~^ nonstandard_macro_braces
println![""];
//~^ nonstandard_macro_braces
println! {};
//~^ nonstandard_macro_braces
println! {""};
//~^ nonstandard_macro_braces
}

View file

@ -1,5 +1,5 @@
error: use of irregular braces for `vec!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:44:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:45:13
|
LL | let _ = vec! {1, 2, 3};
| ^^^^^^^^^^^^^^ help: consider writing: `vec![1, 2, 3]`
@ -8,31 +8,31 @@ LL | let _ = vec! {1, 2, 3};
= help: to override `-D warnings` add `#[allow(clippy::nonstandard_macro_braces)]`
error: use of irregular braces for `format!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:46:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:47:13
|
LL | let _ = format!["ugh {} stop being such a good compiler", "hello"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `format!("ugh {} stop being such a good compiler", "hello")`
error: use of irregular braces for `matches!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:48:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:49:13
|
LL | let _ = matches!{{}, ()};
| ^^^^^^^^^^^^^^^^ help: consider writing: `matches!({}, ())`
error: use of irregular braces for `quote!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:50:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:51:13
|
LL | let _ = quote!(let x = 1;);
| ^^^^^^^^^^^^^^^^^^ help: consider writing: `quote!{let x = 1;}`
error: use of irregular braces for `quote::quote!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:52:13
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:53:13
|
LL | let _ = quote::quote!(match match match);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `quote::quote!{match match match}`
error: use of irregular braces for `vec!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:18:9
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:19:9
|
LL | vec!{0, 0, 0}
| ^^^^^^^^^^^^^ help: consider writing: `vec![0, 0, 0]`
@ -43,22 +43,46 @@ LL | let _ = test!(); // trigger when macro def is inside our own crate
= note: this error originates in the macro `test` (in Nightly builds, run with -Z macro-backtrace for more info)
error: use of irregular braces for `type_pos!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:62:12
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:63:12
|
LL | let _: type_pos!(usize) = vec![];
| ^^^^^^^^^^^^^^^^ help: consider writing: `type_pos![usize]`
error: use of irregular braces for `eprint!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:65:5
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:66:5
|
LL | eprint!("test if user config overrides defaults");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `eprint!["test if user config overrides defaults"]`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:74:5
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:75:5
|
LL | println! {"hello world"}
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider writing: `println!("hello world");`
error: aborting due to 9 previous errors
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:83:5
|
LL | println![];
| ^^^^^^^^^^ help: consider writing: `println!()`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:85:5
|
LL | println![""];
| ^^^^^^^^^^^^ help: consider writing: `println!("")`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:87:5
|
LL | println! {};
| ^^^^^^^^^^^ help: consider writing: `println!()`
error: use of irregular braces for `println!` macro
--> tests/ui-toml/nonstandard_macro_braces/conf_nonstandard_macro_braces.rs:89:5
|
LL | println! {""};
| ^^^^^^^^^^^^^ help: consider writing: `println!("")`
error: aborting due to 13 previous errors

View file

@ -9,6 +9,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
allow-expect-in-consts
allow-expect-in-tests
allow-indexing-slicing-in-tests
allow-large-stack-frames-in-tests
allow-mixed-uninlined-format-args
allow-one-hash-in-raw-strings
allow-panic-in-tests
@ -107,6 +108,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
allow-expect-in-consts
allow-expect-in-tests
allow-indexing-slicing-in-tests
allow-large-stack-frames-in-tests
allow-mixed-uninlined-format-args
allow-one-hash-in-raw-strings
allow-panic-in-tests
@ -205,6 +207,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
allow-expect-in-consts
allow-expect-in-tests
allow-indexing-slicing-in-tests
allow-large-stack-frames-in-tests
allow-mixed-uninlined-format-args
allow-one-hash-in-raw-strings
allow-panic-in-tests

View file

@ -1,6 +1,6 @@
//@aux-build:proc_macros.rs
#![warn(clippy::borrow_as_ptr)]
#![allow(clippy::useless_vec)]
#![allow(clippy::useless_vec, clippy::ptr_offset_by_literal)]
extern crate proc_macros;

View file

@ -1,6 +1,6 @@
//@aux-build:proc_macros.rs
#![warn(clippy::borrow_as_ptr)]
#![allow(clippy::useless_vec)]
#![allow(clippy::useless_vec, clippy::ptr_offset_by_literal)]
extern crate proc_macros;

View file

@ -472,3 +472,22 @@ fn issue15321() {
//~^ unnecessary_unwrap
}
}
mod issue16188 {
struct Foo {
value: Option<i32>,
}
impl Foo {
pub fn bar(&mut self) {
let print_value = |v: i32| {
println!("{}", v);
};
if self.value.is_none() {
self.value = Some(10);
print_value(self.value.unwrap());
}
}
}
}

View file

@ -1,6 +1,6 @@
//@ check-pass
#![allow(clippy::single_match)]
#![allow(clippy::single_match, clippy::ptr_offset_by_literal)]
use std::ptr;

View file

@ -0,0 +1,133 @@
#![allow(
clippy::erasing_op,
clippy::no_effect,
clippy::unnecessary_operation,
clippy::unnecessary_cast,
clippy::op_ref
)]
#![warn(clippy::decimal_bitwise_operands)]
macro_rules! bitwise_op {
($x:expr, $y:expr) => {
$x & $y;
};
}
pub const SOME_CONST: i32 = 12345;
fn main() {
let mut x = 0;
// BAD: Bitwise operation, decimal literal, one literal
x & 9_8765_4321; //~ decimal_bitwise_operands
x & 100_i32; //~ decimal_bitwise_operands
x | (/* comment */99); //~ decimal_bitwise_operands
x ^ (99); //~ decimal_bitwise_operands
x &= 99; //~ decimal_bitwise_operands
x |= { 99 }; //~ decimal_bitwise_operands
x |= { { 99 } }; //~ decimal_bitwise_operands
x |= {
0b1000;
99 //~ decimal_bitwise_operands
};
x ^= (99); //~ decimal_bitwise_operands
// BAD: Bitwise operation, decimal literal, two literals
0b1010 & 99; //~ decimal_bitwise_operands
0b1010 | (99); //~ decimal_bitwise_operands
0b1010 ^ (/* comment */99); //~ decimal_bitwise_operands
99 & 0b1010; //~ decimal_bitwise_operands
(99) | 0b1010; //~ decimal_bitwise_operands
(/* comment */99) ^ 0b1010; //~ decimal_bitwise_operands
0xD | { 99 }; //~ decimal_bitwise_operands
88 & 99;
//~^ decimal_bitwise_operands
//~| decimal_bitwise_operands
37 & 38 & 39;
//~^ decimal_bitwise_operands
//~| decimal_bitwise_operands
//~| decimal_bitwise_operands
// GOOD: Bitwise operation, binary/hex/octal literal, one literal
x & 0b1010;
x | 0b1010;
x ^ 0b1010;
x &= 0b1010;
x |= 0b1010;
x ^= 0b1010;
x & 0xD;
x & 0o77;
x | 0o123;
x ^ 0o377;
x &= 0o777;
x |= 0o7;
x ^= 0o70;
// GOOD: Bitwise operation, binary/hex/octal literal, two literals
0b1010 & 0b1101;
0xD ^ 0xF;
0o377 ^ 0o77;
0b1101 ^ 0xFF;
// GOOD: Numeric operation, any literal
x += 99;
x -= 0b1010;
x *= 0xD;
99 + 99;
0b1010 - 0b1101;
0xD * 0xD;
// BAD: Unary, cast and reference, decimal literal
x & !100; //~ decimal_bitwise_operands
x & -100; //~ decimal_bitwise_operands
x & (100 as i32); //~ decimal_bitwise_operands
x & &100; //~ decimal_bitwise_operands
// GOOD: Unary, cast and reference, non-decimal literal
x & !0b1101;
x & -0xD;
x & (0o333 as i32);
x & &0b1010;
// GOOD: Bitwise operation, variables only
let y = 0;
x & y;
x &= y;
x + y;
x += y;
// GOOD: Macro expansion (should be ignored)
bitwise_op!(x, 123);
bitwise_op!(0b1010, 123);
// GOOD: Using const (should be ignored)
x & SOME_CONST;
x |= SOME_CONST;
// GOOD: Parenthesized binary/hex literal (should not trigger lint)
x & (0b1111);
x |= (0b1010);
x ^ (/* comment */0b1100);
(0xFF) & x;
// GOOD: Power of two and power of two minus one
x & 16; // 2^4
x | (31); // 2^5 - 1
x ^ 0x40; // 2^6 (hex)
x ^= 7; // 2^3 - 1
// GOOD: Bitwise operation, single digit decimal literal
5 & 9;
x ^ 6;
x ^= 7;
// GOOD: More complex expressions
(x + 1) & 0xFF;
(x * 2) | (y & 0xF);
(x ^ y) & 0b11110000;
x | (1 << 9);
// GOOD: Special cases
x & 0; // All bits off
x | !0; // All bits on
x ^ 1; // Toggle LSB
}

View file

@ -0,0 +1,204 @@
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:21:9
|
LL | x & 9_8765_4321;
| ^^^^^^^^^^^
|
= help: use binary (0b11_1010_1101_1110_0110_1000_1011_0001), hex (0x3ade_68b1), or octal (0o7_267_464_261) notation for better readability
= note: `-D clippy::decimal-bitwise-operands` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::decimal_bitwise_operands)]`
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:22:9
|
LL | x & 100_i32;
| ^^^^^^^
|
= help: use binary (0b110_0100_i32), hex (0x0064_i32), or octal (0o144_i32) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:23:23
|
LL | x | (/* comment */99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:24:10
|
LL | x ^ (99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:25:10
|
LL | x &= 99;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:26:12
|
LL | x |= { 99 };
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:27:14
|
LL | x |= { { 99 } };
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:30:9
|
LL | 99
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:32:11
|
LL | x ^= (99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:35:14
|
LL | 0b1010 & 99;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:36:15
|
LL | 0b1010 | (99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:37:28
|
LL | 0b1010 ^ (/* comment */99);
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:38:5
|
LL | 99 & 0b1010;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:39:6
|
LL | (99) | 0b1010;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:40:19
|
LL | (/* comment */99) ^ 0b1010;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:41:13
|
LL | 0xD | { 99 };
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:42:5
|
LL | 88 & 99;
| ^^
|
= help: use binary (0b101_1000), hex (0x0058), or octal (0o130) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:42:10
|
LL | 88 & 99;
| ^^
|
= help: use binary (0b110_0011), hex (0x0063), or octal (0o143) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:45:15
|
LL | 37 & 38 & 39;
| ^^
|
= help: use binary (0b10_0111), hex (0x0027), or octal (0o47) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:45:5
|
LL | 37 & 38 & 39;
| ^^
|
= help: use binary (0b10_0101), hex (0x0025), or octal (0o45) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:45:10
|
LL | 37 & 38 & 39;
| ^^
|
= help: use binary (0b10_0110), hex (0x0026), or octal (0o46) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:80:10
|
LL | x & !100;
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:81:10
|
LL | x & -100;
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:82:10
|
LL | x & (100 as i32);
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: using decimal literal for bitwise operation
--> tests/ui/decimal_bitwise_operands.rs:83:10
|
LL | x & &100;
| ^^^
|
= help: use binary (0b110_0100), hex (0x0064), or octal (0o144) notation for better readability
error: aborting due to 25 previous errors

View file

@ -61,7 +61,7 @@ error: lint `clippy::should_assert_eq` has been removed: `assert!(a == b)` can n
LL | #![warn(clippy::should_assert_eq)]
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::string_to_string` has been removed: `clippy:implicit_clone` covers those cases
error: lint `clippy::string_to_string` has been removed: `clippy::implicit_clone` covers those cases
--> tests/ui/deprecated.rs:15:9
|
LL | #![warn(clippy::string_to_string)]

View file

@ -248,4 +248,28 @@ mod issue14449 {
}
}
// Don't suggest when it would cause `MutexGuard` to be held across an await point.
mod issue_16173 {
use std::collections::HashMap;
use std::sync::Mutex;
async fn f() {}
async fn foo() {
let mu_map = Mutex::new(HashMap::new());
if !mu_map.lock().unwrap().contains_key(&0) {
f().await;
mu_map.lock().unwrap().insert(0, 0);
}
if mu_map.lock().unwrap().contains_key(&1) {
todo!();
} else {
mu_map.lock().unwrap().insert(1, 42);
todo!();
f().await;
}
}
}
fn main() {}

View file

@ -254,4 +254,28 @@ mod issue14449 {
}
}
// Don't suggest when it would cause `MutexGuard` to be held across an await point.
mod issue_16173 {
use std::collections::HashMap;
use std::sync::Mutex;
async fn f() {}
async fn foo() {
let mu_map = Mutex::new(HashMap::new());
if !mu_map.lock().unwrap().contains_key(&0) {
f().await;
mu_map.lock().unwrap().insert(0, 0);
}
if mu_map.lock().unwrap().contains_key(&1) {
todo!();
} else {
mu_map.lock().unwrap().insert(1, 42);
todo!();
f().await;
}
}
}
fn main() {}

View file

@ -473,4 +473,16 @@ impl Alias2 {
}
}
// Issue #16190
pub struct RefMutLenButRefIsEmpty;
impl RefMutLenButRefIsEmpty {
pub fn len(&mut self) -> usize {
todo!()
}
pub fn is_empty(&self) -> bool {
todo!()
}
}
fn main() {}

View file

@ -0,0 +1,32 @@
//@aux-build:proc_macros.rs
#![warn(clippy::manual_ilog2)]
#![allow(clippy::unnecessary_operation)]
use proc_macros::{external, with_span};
fn foo(a: u32, b: u64) {
a.ilog2(); //~ manual_ilog2
a.ilog2(); //~ manual_ilog2
b.ilog2(); //~ manual_ilog2
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
// don't lint when macros are involved
macro_rules! two {
() => {
2
};
};
macro_rules! thirty_one {
() => {
31
};
};
a.ilog(two!());
thirty_one!() - a.leading_zeros();
external!($a.ilog(2));
with_span!(span; a.ilog(2));
}

32
tests/ui/manual_ilog2.rs Normal file
View file

@ -0,0 +1,32 @@
//@aux-build:proc_macros.rs
#![warn(clippy::manual_ilog2)]
#![allow(clippy::unnecessary_operation)]
use proc_macros::{external, with_span};
fn foo(a: u32, b: u64) {
31 - a.leading_zeros(); //~ manual_ilog2
a.ilog(2); //~ manual_ilog2
63 - b.leading_zeros(); //~ manual_ilog2
64 - b.leading_zeros(); // No lint because manual ilog2 is `BIT_WIDTH - 1 - x.leading_zeros()`
// don't lint when macros are involved
macro_rules! two {
() => {
2
};
};
macro_rules! thirty_one {
() => {
31
};
};
a.ilog(two!());
thirty_one!() - a.leading_zeros();
external!($a.ilog(2));
with_span!(span; a.ilog(2));
}

View file

@ -0,0 +1,23 @@
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:8:5
|
LL | 31 - a.leading_zeros();
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.ilog2()`
|
= note: `-D clippy::manual-ilog2` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::manual_ilog2)]`
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:9:5
|
LL | a.ilog(2);
| ^^^^^^^^^ help: try: `a.ilog2()`
error: manually reimplementing `ilog2`
--> tests/ui/manual_ilog2.rs:11:5
|
LL | 63 - b.leading_zeros();
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `b.ilog2()`
error: aborting due to 3 previous errors

View file

@ -58,3 +58,13 @@ fn main() {
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
}
fn issue15655() {
let _ = 5u32.saturating_sub(1u32); //~ manual_saturating_arithmetic
let _ = 5u32.checked_add(1u32).unwrap_or_default(); // ok
let _ = 5u32.checked_mul(1u32).unwrap_or_default(); // ok
let _ = 5i32.checked_sub(1i32).unwrap_or_default(); // ok
let _ = 5i32.checked_add(1i32).unwrap_or_default(); // ok
let _ = 5i32.checked_mul(1i32).unwrap_or_default(); // ok
}

View file

@ -73,3 +73,13 @@ fn main() {
let _ = 1i8.checked_sub(1).unwrap_or(127); // ok
let _ = 1i8.checked_sub(-1).unwrap_or(-128); // ok
}
fn issue15655() {
let _ = 5u32.checked_sub(1u32).unwrap_or_default(); //~ manual_saturating_arithmetic
let _ = 5u32.checked_add(1u32).unwrap_or_default(); // ok
let _ = 5u32.checked_mul(1u32).unwrap_or_default(); // ok
let _ = 5i32.checked_sub(1i32).unwrap_or_default(); // ok
let _ = 5i32.checked_add(1i32).unwrap_or_default(); // ok
let _ = 5i32.checked_mul(1i32).unwrap_or_default(); // ok
}

View file

@ -165,5 +165,11 @@ LL | | .checked_sub(-1)
LL | | .unwrap_or(170_141_183_460_469_231_731_687_303_715_884_105_727);
| |_______________________________________________________________________^ help: consider using `saturating_sub`: `1i128.saturating_sub(-1)`
error: aborting due to 24 previous errors
error: manual saturating arithmetic
--> tests/ui/manual_saturating_arithmetic.rs:78:13
|
LL | let _ = 5u32.checked_sub(1u32).unwrap_or_default();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `saturating_sub`: `5u32.saturating_sub(1u32)`
error: aborting due to 25 previous errors

View file

@ -223,3 +223,10 @@ fn msrv_1_42() {
let _y = matches!(Some(5), Some(0));
//~^^^^ match_like_matches_macro
}
#[expect(clippy::option_option)]
fn issue15841(opt: Option<Option<Option<i32>>>, value: i32) {
// Lint: no if-let _in the guard_
let _ = matches!(opt, Some(first) if (if let Some(second) = first { true } else { todo!() }));
//~^^^^ match_like_matches_macro
}

View file

@ -267,3 +267,13 @@ fn msrv_1_42() {
};
//~^^^^ match_like_matches_macro
}
#[expect(clippy::option_option)]
fn issue15841(opt: Option<Option<Option<i32>>>, value: i32) {
// Lint: no if-let _in the guard_
let _ = match opt {
Some(first) if (if let Some(second) = first { true } else { todo!() }) => true,
_ => false,
};
//~^^^^ match_like_matches_macro
}

View file

@ -6,10 +6,18 @@ LL | let _y = match x {
LL | | Some(0) => true,
LL | | _ => false,
LL | | };
| |_____^ help: try: `matches!(x, Some(0))`
| |_____^
|
= note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::match_like_matches_macro)]`
help: use `matches!` directly
|
LL - let _y = match x {
LL - Some(0) => true,
LL - _ => false,
LL - };
LL + let _y = matches!(x, Some(0));
|
error: redundant pattern matching, consider using `is_some()`
--> tests/ui/match_like_matches_macro.rs:20:14
@ -42,13 +50,28 @@ LL | let _zz = match x {
LL | | Some(r) if r == 0 => false,
LL | | _ => true,
LL | | };
| |_____^ help: try: `!matches!(x, Some(r) if r == 0)`
| |_____^
|
help: use `matches!` directly
|
LL - let _zz = match x {
LL - Some(r) if r == 0 => false,
LL - _ => true,
LL - };
LL + let _zz = !matches!(x, Some(r) if r == 0);
|
error: if let .. else expression looks like `matches!` macro
error: `if let .. else` expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:41:16
|
LL | let _zzz = if let Some(5) = x { true } else { false };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(x, Some(5))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `matches!` directly
|
LL - let _zzz = if let Some(5) = x { true } else { false };
LL + let _zzz = matches!(x, Some(5));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:66:20
@ -59,7 +82,17 @@ LL | | E::A(_) => true,
LL | | E::B(_) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(x, E::A(_) | E::B(_))`
| |_________^
|
help: use `matches!` directly
|
LL - let _ans = match x {
LL - E::A(_) => true,
LL - E::B(_) => true,
LL - _ => false,
LL - };
LL + let _ans = matches!(x, E::A(_) | E::B(_));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:77:20
@ -71,7 +104,19 @@ LL | | true
... |
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(x, E::A(_) | E::B(_))`
| |_________^
|
help: use `matches!` directly
|
LL - let _ans = match x {
LL - E::A(_) => {
LL - true
LL - }
LL - E::B(_) => true,
LL - _ => false,
LL - };
LL + let _ans = matches!(x, E::A(_) | E::B(_));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:88:20
@ -82,7 +127,17 @@ LL | | E::B(_) => false,
LL | | E::C => false,
LL | | _ => true,
LL | | };
| |_________^ help: try: `!matches!(x, E::B(_) | E::C)`
| |_________^
|
help: use `matches!` directly
|
LL - let _ans = match x {
LL - E::B(_) => false,
LL - E::C => false,
LL - _ => true,
LL - };
LL + let _ans = !matches!(x, E::B(_) | E::C);
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:149:18
@ -92,7 +147,16 @@ LL | let _z = match &z {
LL | | Some(3) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(z, Some(3))`
| |_________^
|
help: use `matches!` directly
|
LL - let _z = match &z {
LL - Some(3) => true,
LL - _ => false,
LL - };
LL + let _z = matches!(z, Some(3));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:159:18
@ -102,7 +166,16 @@ LL | let _z = match &z {
LL | | Some(3) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(&z, Some(3))`
| |_________^
|
help: use `matches!` directly
|
LL - let _z = match &z {
LL - Some(3) => true,
LL - _ => false,
LL - };
LL + let _z = matches!(&z, Some(3));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:177:21
@ -112,7 +185,16 @@ LL | let _ = match &z {
LL | | AnEnum::X => true,
LL | | _ => false,
LL | | };
| |_____________^ help: try: `matches!(&z, AnEnum::X)`
| |_____________^
|
help: use `matches!` directly
|
LL - let _ = match &z {
LL - AnEnum::X => true,
LL - _ => false,
LL - };
LL + let _ = matches!(&z, AnEnum::X);
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:192:20
@ -122,7 +204,16 @@ LL | let _res = match &val {
LL | | &Some(ref _a) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(&val, &Some(ref _a))`
| |_________^
|
help: use `matches!` directly
|
LL - let _res = match &val {
LL - &Some(ref _a) => true,
LL - _ => false,
LL - };
LL + let _res = matches!(&val, &Some(ref _a));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:205:20
@ -132,7 +223,16 @@ LL | let _res = match &val {
LL | | &Some(ref _a) => true,
LL | | _ => false,
LL | | };
| |_________^ help: try: `matches!(&val, &Some(ref _a))`
| |_________^
|
help: use `matches!` directly
|
LL - let _res = match &val {
LL - &Some(ref _a) => true,
LL - _ => false,
LL - };
LL + let _res = matches!(&val, &Some(ref _a));
|
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:264:14
@ -142,7 +242,35 @@ LL | let _y = match Some(5) {
LL | | Some(0) => true,
LL | | _ => false,
LL | | };
| |_____^ help: try: `matches!(Some(5), Some(0))`
| |_____^
|
help: use `matches!` directly
|
LL - let _y = match Some(5) {
LL - Some(0) => true,
LL - _ => false,
LL - };
LL + let _y = matches!(Some(5), Some(0));
|
error: aborting due to 14 previous errors
error: match expression looks like `matches!` macro
--> tests/ui/match_like_matches_macro.rs:274:13
|
LL | let _ = match opt {
| _____________^
LL | | Some(first) if (if let Some(second) = first { true } else { todo!() }) => true,
LL | | _ => false,
LL | | };
| |_____^
|
help: use `matches!` directly
|
LL - let _ = match opt {
LL - Some(first) if (if let Some(second) = first { true } else { todo!() }) => true,
LL - _ => false,
LL - };
LL + let _ = matches!(opt, Some(first) if (if let Some(second) = first { true } else { todo!() }));
|
error: aborting due to 15 previous errors

View file

@ -0,0 +1,51 @@
//@check-pass
#![warn(clippy::match_like_matches_macro)]
#![feature(if_let_guard)]
#[expect(clippy::option_option)]
fn issue15841(opt: Option<Option<Option<i32>>>, value: i32) {
let _ = match opt {
Some(first)
if let Some(second) = first
&& let Some(third) = second
&& third == value =>
{
true
},
_ => false,
};
// if-let is the second if
let _ = match opt {
Some(first)
if first.is_some()
&& let Some(second) = first =>
{
true
},
_ => false,
};
// if-let is the third if
let _ = match opt {
Some(first)
if first.is_some()
&& first.is_none()
&& let Some(second) = first =>
{
true
},
_ => false,
};
// don't get confused by `or`s
let _ = match opt {
Some(first)
if (first.is_some() || first.is_none())
&& let Some(second) = first =>
{
true
},
_ => false,
};
}

View file

@ -1,407 +1,191 @@
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:30:5
|
LL | assert!(v.len() < 5);
| -------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)`
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:30:5
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:30:12
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:30:19
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:30:26
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:30:33
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
= note: asserting the length before indexing will elide bounds checks
= note: `-D clippy::missing-asserts-for-indexing` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::missing_asserts_for_indexing)]`
help: provide the highest index that is indexed with
|
LL - assert!(v.len() < 5);
LL + assert!(v.len() > 4);
|
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:36:5
|
LL | assert!(v.len() <= 5);
| --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)`
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:36:5
help: provide the highest index that is indexed with
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:36:12
LL - assert!(v.len() <= 5);
LL + assert!(v.len() > 4);
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:36:19
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:36:26
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:36:33
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:42:5
|
LL | assert!(v.len() > 3);
| -------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)`
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:42:5
help: provide the highest index that is indexed with
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:42:12
LL - assert!(v.len() > 3);
LL + assert!(v.len() > 4);
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:42:19
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:42:26
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:42:33
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:48:5
|
LL | assert!(v.len() >= 4);
| --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)`
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:48:5
help: provide the highest index that is indexed with
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:48:12
LL - assert!(v.len() >= 4);
LL + assert!(v.len() > 4);
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:48:19
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:48:26
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:48:33
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:66:13
|
LL | assert!(v.len() >= 3);
| --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 3)`
LL | let _ = v[0];
| _____________^
... |
LL | | let _ = v[1..4];
| |___________________^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:66:13
|
LL | let _ = v[0];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:69:13
|
...
LL | let _ = v[1..4];
| ^^^^^^^
= note: asserting the length before indexing will elide bounds checks
|
help: provide the highest index that is indexed with
|
LL - assert!(v.len() >= 3);
LL + assert!(v.len() > 3);
|
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:81:13
|
LL | assert!(v.len() >= 4);
| --------------------- help: provide the highest index that is indexed with: `assert!(v.len() > 4)`
LL | let _ = v[0];
| _____________^
... |
LL | | let _ = v[1..=4];
| |____________________^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:81:13
|
LL | let _ = v[0];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:84:13
|
...
LL | let _ = v[1..=4];
| ^^^^^^^^
= note: asserting the length before indexing will elide bounds checks
|
help: provide the highest index that is indexed with
|
LL - assert!(v.len() >= 4);
LL + assert!(v.len() > 4);
|
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:97:13
|
LL | assert!(v1.len() >= 12);
| ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() > 12)`
LL | assert!(v2.len() >= 15);
LL | let _ = v1[0] + v1[12];
| ^^^^^^^^^^^^^^
| ^^^^^ ^^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:97:13
help: provide the highest index that is indexed with
|
LL | let _ = v1[0] + v1[12];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:97:21
LL - assert!(v1.len() >= 12);
LL + assert!(v1.len() > 12);
|
LL | let _ = v1[0] + v1[12];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:100:13
|
LL | assert!(v2.len() >= 15);
| ----------------------- help: provide the highest index that is indexed with: `assert!(v2.len() > 15)`
...
LL | let _ = v2[5] + v2[15];
| ^^^^^^^^^^^^^^
| ^^^^^ ^^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:100:13
help: provide the highest index that is indexed with
|
LL | let _ = v2[5] + v2[15];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:100:21
LL - assert!(v2.len() >= 15);
LL + assert!(v2.len() > 15);
|
LL | let _ = v2[5] + v2[15];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:106:13
|
LL | assert!(v1.len() >= 12);
| ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() > 12)`
LL | assert!(v2.len() > 15);
LL | let _ = v1[0] + v1[12];
| ^^^^^^^^^^^^^^
| ^^^^^ ^^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:106:13
help: provide the highest index that is indexed with
|
LL | let _ = v1[0] + v1[12];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:106:21
LL - assert!(v1.len() >= 12);
LL + assert!(v1.len() > 12);
|
LL | let _ = v1[0] + v1[12];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:131:13
|
LL | assert!(v1.len() == 2);
| ---------------------- help: provide the highest index that is indexed with: `assert!(v1.len() == 3)`
...
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^ ^^^^^ ^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:131:13
help: provide the highest index that is indexed with
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:131:21
LL - assert!(v1.len() == 2);
LL + assert!(v1.len() == 3);
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:131:29
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:136:13
|
LL | assert!(2 == v3.len());
| ---------------------- help: provide the highest index that is indexed with: `assert!(v3.len() == 3)`
...
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^ ^^^^^ ^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:136:13
help: provide the highest index that is indexed with
|
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:136:21
LL - assert!(2 == v3.len());
LL + assert!(v3.len() == 3);
|
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:136:29
|
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:158:13
|
LL | assert_eq!(v1.len(), 2);
| ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v1.len(), 3)`
...
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^ ^^^^^ ^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:158:13
help: provide the highest index that is indexed with
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:158:21
LL - assert_eq!(v1.len(), 2);
LL + assert_eq!(v1.len(), 3);
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:158:29
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:163:13
|
LL | assert_eq!(2, v3.len());
| ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v3.len(), 3)`
...
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^ ^^^^^ ^^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:163:13
help: provide the highest index that is indexed with
|
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:163:21
LL - assert_eq!(2, v3.len());
LL + assert_eq!(v3.len(), 3);
|
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:163:29
|
LL | let _ = v3[0] + v3[1] + v3[2];
| ^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:172:17
|
LL | assert_eq!(v.len(), 2);
| ---------------------- help: provide the highest index that is indexed with: `assert_eq!(v.len(), 3)`
LL | let _ = v[0] + v[1] + v[2];
| ^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:172:17
help: provide the highest index that is indexed with
|
LL | let _ = v[0] + v[1] + v[2];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:172:24
LL - assert_eq!(v.len(), 2);
LL + assert_eq!(v.len(), 3);
|
LL | let _ = v[0] + v[1] + v[2];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:172:31
|
LL | let _ = v[0] + v[1] + v[2];
| ^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
--> tests/ui/missing_asserts_for_indexing.rs:178:17
|
LL | debug_assert_eq!(v.len(), 2);
| ---------------------------- help: provide the highest index that is indexed with: `debug_assert_eq!(v.len(), 3)`
LL | let _ = v[0] + v[1] + v[2];
| ^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^
|
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:178:17
help: provide the highest index that is indexed with
|
LL | let _ = v[0] + v[1] + v[2];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:178:24
LL - debug_assert_eq!(v.len(), 2);
LL + debug_assert_eq!(v.len(), 3);
|
LL | let _ = v[0] + v[1] + v[2];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing.rs:178:31
|
LL | let _ = v[0] + v[1] + v[2];
| ^^^^
= note: asserting the length before indexing will elide bounds checks
error: aborting due to 15 previous errors

View file

@ -2,34 +2,9 @@ error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:5:5
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^ ^^^^ ^^^^ ^^^^ ^^^^
|
= help: consider asserting the length before indexing: `assert!(v.len() > 4);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:5:5
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:5:12
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:5:19
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:5:26
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:5:33
|
LL | v[0] + v[1] + v[2] + v[3] + v[4]
| ^^^^
= note: asserting the length before indexing will elide bounds checks
= note: `-D clippy::missing-asserts-for-indexing` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::missing_asserts_for_indexing)]`
@ -37,191 +12,82 @@ LL | v[0] + v[1] + v[2] + v[3] + v[4]
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:10:13
|
LL | let _ = v[0];
| _____________^
... |
LL | | let _ = v[1..4];
| |___________________^
|
= help: consider asserting the length before indexing: `assert!(v.len() > 3);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:10:13
|
LL | let _ = v[0];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:13:13
|
...
LL | let _ = v[1..4];
| ^^^^^^^
= note: asserting the length before indexing will elide bounds checks
|
= help: consider asserting the length before indexing: `assert!(v.len() > 3);`
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:17:13
|
LL | let a = v[0];
| _____________^
LL | |
LL | |
LL | | let b = v[1];
LL | | let c = v[2];
| |________________^
|
= help: consider asserting the length before indexing: `assert!(v.len() > 2);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:17:13
|
LL | let a = v[0];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:20:13
|
...
LL | let b = v[1];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:21:13
|
LL | let c = v[2];
| ^^^^
= note: asserting the length before indexing will elide bounds checks
|
= help: consider asserting the length before indexing: `assert!(v.len() > 2);`
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:26:13
|
LL | let _ = v1[0] + v1[12];
| ^^^^^^^^^^^^^^
| ^^^^^ ^^^^^^
|
= help: consider asserting the length before indexing: `assert!(v1.len() > 12);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:26:13
|
LL | let _ = v1[0] + v1[12];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:26:21
|
LL | let _ = v1[0] + v1[12];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:28:13
|
LL | let _ = v2[5] + v2[15];
| ^^^^^^^^^^^^^^
| ^^^^^ ^^^^^^
|
= help: consider asserting the length before indexing: `assert!(v2.len() > 15);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:28:13
|
LL | let _ = v2[5] + v2[15];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:28:21
|
LL | let _ = v2[5] + v2[15];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:35:13
|
LL | let _ = v2[5] + v2[15];
| ^^^^^^^^^^^^^^
| ^^^^^ ^^^^^^
|
= help: consider asserting the length before indexing: `assert!(v2.len() > 15);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:35:13
|
LL | let _ = v2[5] + v2[15];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:35:21
|
LL | let _ = v2[5] + v2[15];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:45:13
|
LL | let _ = f.v[0] + f.v[1];
| ^^^^^^^^^^^^^^^
| ^^^^^^ ^^^^^^
|
= help: consider asserting the length before indexing: `assert!(f.v.len() > 1);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:45:13
|
LL | let _ = f.v[0] + f.v[1];
| ^^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:45:22
|
LL | let _ = f.v[0] + f.v[1];
| ^^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:59:13
|
LL | let _ = x[0] + x[1];
| ^^^^^^^^^^^
| ^^^^ ^^^^
|
= help: consider asserting the length before indexing: `assert!(x.len() > 1);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:59:13
|
LL | let _ = x[0] + x[1];
| ^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:59:20
|
LL | let _ = x[0] + x[1];
| ^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:77:13
|
LL | let _ = v1[1] + v1[2];
| ^^^^^^^^^^^^^
| ^^^^^ ^^^^^
|
= help: consider asserting the length before indexing: `assert!(v1.len() > 2);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:77:13
|
LL | let _ = v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:77:21
|
LL | let _ = v1[1] + v1[2];
| ^^^^^
= note: asserting the length before indexing will elide bounds checks
error: indexing into a slice multiple times without an `assert`
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:13
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^^^^^^^^^^^^^^^^^
| ^^^^^ ^^^^^ ^^^^^
|
= help: consider asserting the length before indexing: `assert!(v1.len() > 2);`
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:13
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:21
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
note: slice indexed here
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:29
|
LL | let _ = v1[0] + v1[1] + v1[2];
| ^^^^^
= note: asserting the length before indexing will elide bounds checks
error: aborting due to 10 previous errors

View file

@ -0,0 +1,182 @@
#![warn(clippy::needless_type_cast)]
#![allow(clippy::no_effect, clippy::unnecessary_cast, unused)]
fn takes_i32(x: i32) -> i32 {
x
}
fn generic<T>(x: T) -> T {
x
}
fn main() {
let a: i32 = 10;
//~^ needless_type_cast
let _ = a as i32 + 5;
let _ = a as i32 * 2;
let b: u16 = 20;
let _ = b;
let _ = b as u32;
let c: u8 = 5;
let _ = c as u16;
let _ = c as u32;
let d: i32 = 100;
let _ = d + 1;
let e = 42u8;
let _ = e as i64;
let _ = e as i64 + 10;
let f: usize = 1;
//~^ needless_type_cast
let _ = f as usize;
}
fn test_function_call() {
let a: i32 = 10;
//~^ needless_type_cast
let _ = takes_i32(a as i32);
let _ = takes_i32(a as i32);
}
fn test_generic_call() {
let a: u8 = 10;
let _ = generic(a as i32);
let _ = generic(a as i32);
}
fn test_method_on_cast() {
let a: i32 = 10;
//~^ needless_type_cast
let _ = (a as i32).checked_add(5);
let _ = (a as i32).saturating_mul(2);
}
fn test_iterator_sum() {
let a: i32 = 10;
//~^ needless_type_cast
let arr = [a as i32, a as i32];
let _: i32 = arr.iter().copied().sum();
}
fn test_closure() {
let a: i32 = 10;
//~^ needless_type_cast
let _: i32 = [1i32, 2].iter().map(|x| x + a as i32).sum();
}
fn test_struct_field() {
struct S {
x: i32,
y: i32,
}
let a: i32 = 10;
//~^ needless_type_cast
let _ = S {
x: a as i32,
y: a as i32,
};
}
fn test_option() {
let a: u8 = 10;
let _: Option<i32> = Some(a as i32);
let _: Option<i32> = Some(a as i32);
}
fn test_mixed_context() {
let a: u8 = 10;
let _ = takes_i32(a as i32);
let _ = generic(a as i32);
}
fn test_nested_block() {
if true {
let a: i32 = 10;
//~^ needless_type_cast
let _ = a as i32 + 1;
let _ = a as i32 * 2;
}
}
fn test_match_expr() {
let a: i32 = 10;
//~^ needless_type_cast
let _ = match 1 {
1 => a as i32,
_ => a as i32,
};
}
fn test_return_expr() -> i32 {
let a: i32 = 10;
//~^ needless_type_cast
a as i32
}
fn test_closure_always_cast() {
let a: i32 = 10;
//~^ needless_type_cast
let _ = [1, 2].iter().map(|_| a as i32).sum::<i32>();
let _ = a as i32;
}
fn test_closure_mixed_usage() {
let a: u8 = 10;
let _ = [1, 2].iter().map(|_| a as i32).sum::<i32>();
let _ = a + 1;
}
fn test_nested_generic_call() {
let a: u8 = 10;
let _ = generic(takes_i32(a as i32));
let _ = generic(takes_i32(a as i32));
}
fn test_generic_initializer() {
// Should not lint: changing type would affect what generic() returns
let a: u8 = generic(10u8);
let _ = a as i32;
let _ = a as i32;
}
fn test_unsafe_transmute() {
// Should not lint: initializer contains unsafe block
#[allow(clippy::useless_transmute)]
let x: u32 = unsafe { std::mem::transmute(0u32) };
let _ = x as u64;
}
fn test_if_with_generic() {
// Should not lint: one branch has generic return type
let x: u8 = if true { generic(1) } else { 2 };
let _ = x as i32;
}
fn test_match_with_generic() {
// Should not lint: one branch has generic return type
let x: u8 = match 1 {
1 => generic(1),
_ => 2,
};
let _ = x as i32;
}
fn test_default() {
// Should not lint: Default::default() has generic return type
let x: u8 = Default::default();
let _ = x as i32;
}
fn test_loop_with_generic() {
// Should not lint: loop break has generic return type
#[allow(clippy::never_loop)]
let x: u8 = loop {
break generic(1);
};
let _ = x as i32;
}

View file

@ -0,0 +1,182 @@
#![warn(clippy::needless_type_cast)]
#![allow(clippy::no_effect, clippy::unnecessary_cast, unused)]
fn takes_i32(x: i32) -> i32 {
x
}
fn generic<T>(x: T) -> T {
x
}
fn main() {
let a: u8 = 10;
//~^ needless_type_cast
let _ = a as i32 + 5;
let _ = a as i32 * 2;
let b: u16 = 20;
let _ = b;
let _ = b as u32;
let c: u8 = 5;
let _ = c as u16;
let _ = c as u32;
let d: i32 = 100;
let _ = d + 1;
let e = 42u8;
let _ = e as i64;
let _ = e as i64 + 10;
let f: u8 = 1;
//~^ needless_type_cast
let _ = f as usize;
}
fn test_function_call() {
let a: u8 = 10;
//~^ needless_type_cast
let _ = takes_i32(a as i32);
let _ = takes_i32(a as i32);
}
fn test_generic_call() {
let a: u8 = 10;
let _ = generic(a as i32);
let _ = generic(a as i32);
}
fn test_method_on_cast() {
let a: u8 = 10;
//~^ needless_type_cast
let _ = (a as i32).checked_add(5);
let _ = (a as i32).saturating_mul(2);
}
fn test_iterator_sum() {
let a: u8 = 10;
//~^ needless_type_cast
let arr = [a as i32, a as i32];
let _: i32 = arr.iter().copied().sum();
}
fn test_closure() {
let a: u8 = 10;
//~^ needless_type_cast
let _: i32 = [1i32, 2].iter().map(|x| x + a as i32).sum();
}
fn test_struct_field() {
struct S {
x: i32,
y: i32,
}
let a: u8 = 10;
//~^ needless_type_cast
let _ = S {
x: a as i32,
y: a as i32,
};
}
fn test_option() {
let a: u8 = 10;
let _: Option<i32> = Some(a as i32);
let _: Option<i32> = Some(a as i32);
}
fn test_mixed_context() {
let a: u8 = 10;
let _ = takes_i32(a as i32);
let _ = generic(a as i32);
}
fn test_nested_block() {
if true {
let a: u8 = 10;
//~^ needless_type_cast
let _ = a as i32 + 1;
let _ = a as i32 * 2;
}
}
fn test_match_expr() {
let a: u8 = 10;
//~^ needless_type_cast
let _ = match 1 {
1 => a as i32,
_ => a as i32,
};
}
fn test_return_expr() -> i32 {
let a: u8 = 10;
//~^ needless_type_cast
a as i32
}
fn test_closure_always_cast() {
let a: u8 = 10;
//~^ needless_type_cast
let _ = [1, 2].iter().map(|_| a as i32).sum::<i32>();
let _ = a as i32;
}
fn test_closure_mixed_usage() {
let a: u8 = 10;
let _ = [1, 2].iter().map(|_| a as i32).sum::<i32>();
let _ = a + 1;
}
fn test_nested_generic_call() {
let a: u8 = 10;
let _ = generic(takes_i32(a as i32));
let _ = generic(takes_i32(a as i32));
}
fn test_generic_initializer() {
// Should not lint: changing type would affect what generic() returns
let a: u8 = generic(10u8);
let _ = a as i32;
let _ = a as i32;
}
fn test_unsafe_transmute() {
// Should not lint: initializer contains unsafe block
#[allow(clippy::useless_transmute)]
let x: u32 = unsafe { std::mem::transmute(0u32) };
let _ = x as u64;
}
fn test_if_with_generic() {
// Should not lint: one branch has generic return type
let x: u8 = if true { generic(1) } else { 2 };
let _ = x as i32;
}
fn test_match_with_generic() {
// Should not lint: one branch has generic return type
let x: u8 = match 1 {
1 => generic(1),
_ => 2,
};
let _ = x as i32;
}
fn test_default() {
// Should not lint: Default::default() has generic return type
let x: u8 = Default::default();
let _ = x as i32;
}
fn test_loop_with_generic() {
// Should not lint: loop break has generic return type
#[allow(clippy::never_loop)]
let x: u8 = loop {
break generic(1);
};
let _ = x as i32;
}

View file

@ -0,0 +1,71 @@
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:13:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
|
= note: `-D clippy::needless-type-cast` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::needless_type_cast)]`
error: this binding is defined as `u8` but is always cast to `usize`
--> tests/ui/needless_type_cast.rs:33:12
|
LL | let f: u8 = 1;
| ^^ help: consider defining it as: `usize`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:39:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:52:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:59:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:66:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:77:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:99:16
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:107:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:116:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: this binding is defined as `u8` but is always cast to `i32`
--> tests/ui/needless_type_cast.rs:122:12
|
LL | let a: u8 = 10;
| ^^ help: consider defining it as: `i32`
error: aborting due to 11 previous errors

View file

@ -0,0 +1,50 @@
#![warn(clippy::ptr_offset_by_literal)]
#![allow(clippy::inconsistent_digit_grouping)]
fn main() {
let arr = [b'a', b'b', b'c'];
let ptr = arr.as_ptr();
let var = 32;
const CONST: isize = 42;
unsafe {
let _ = ptr;
//~^ ptr_offset_by_literal
let _ = ptr;
//~^ ptr_offset_by_literal
let _ = ptr.add(5);
//~^ ptr_offset_by_literal
let _ = ptr.sub(5);
//~^ ptr_offset_by_literal
let _ = ptr.offset(var);
let _ = ptr.offset(CONST);
let _ = ptr.wrapping_add(5);
//~^ ptr_offset_by_literal
let _ = ptr.wrapping_sub(5);
//~^ ptr_offset_by_literal
let _ = ptr.sub(5);
//~^ ptr_offset_by_literal
let _ = ptr.wrapping_sub(5);
//~^ ptr_offset_by_literal
// isize::MAX and isize::MIN on 32-bit systems.
let _ = ptr.add(2_147_483_647);
//~^ ptr_offset_by_literal
let _ = ptr.sub(2_147_483_648);
//~^ ptr_offset_by_literal
let _ = ptr.add(5_0);
//~^ ptr_offset_by_literal
let _ = ptr.sub(5_0);
//~^ ptr_offset_by_literal
macro_rules! offs { { $e:expr, $offs:expr } => { $e.offset($offs) }; }
offs!(ptr, 6);
offs!(ptr, var);
}
}

View file

@ -0,0 +1,50 @@
#![warn(clippy::ptr_offset_by_literal)]
#![allow(clippy::inconsistent_digit_grouping)]
fn main() {
let arr = [b'a', b'b', b'c'];
let ptr = arr.as_ptr();
let var = 32;
const CONST: isize = 42;
unsafe {
let _ = ptr.offset(0);
//~^ ptr_offset_by_literal
let _ = ptr.offset(-0);
//~^ ptr_offset_by_literal
let _ = ptr.offset(5);
//~^ ptr_offset_by_literal
let _ = ptr.offset(-5);
//~^ ptr_offset_by_literal
let _ = ptr.offset(var);
let _ = ptr.offset(CONST);
let _ = ptr.wrapping_offset(5isize);
//~^ ptr_offset_by_literal
let _ = ptr.wrapping_offset(-5isize);
//~^ ptr_offset_by_literal
let _ = ptr.offset(-(5));
//~^ ptr_offset_by_literal
let _ = ptr.wrapping_offset(-(5));
//~^ ptr_offset_by_literal
// isize::MAX and isize::MIN on 32-bit systems.
let _ = ptr.offset(2_147_483_647isize);
//~^ ptr_offset_by_literal
let _ = ptr.offset(-2_147_483_648isize);
//~^ ptr_offset_by_literal
let _ = ptr.offset(5_0__isize);
//~^ ptr_offset_by_literal
let _ = ptr.offset(-5_0__isize);
//~^ ptr_offset_by_literal
macro_rules! offs { { $e:expr, $offs:expr } => { $e.offset($offs) }; }
offs!(ptr, 6);
offs!(ptr, var);
}
}

View file

@ -0,0 +1,141 @@
error: use of `offset` with zero
--> tests/ui/ptr_offset_by_literal.rs:12:17
|
LL | let _ = ptr.offset(0);
| ^^^----------
| |
| help: remove the call to `offset`
|
= note: `-D clippy::ptr-offset-by-literal` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::ptr_offset_by_literal)]`
error: use of `offset` with zero
--> tests/ui/ptr_offset_by_literal.rs:14:17
|
LL | let _ = ptr.offset(-0);
| ^^^-----------
| |
| help: remove the call to `offset`
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:17:17
|
LL | let _ = ptr.offset(5);
| ^^^^^^^^^^^^^
|
help: use `add` instead
|
LL - let _ = ptr.offset(5);
LL + let _ = ptr.add(5);
|
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:19:17
|
LL | let _ = ptr.offset(-5);
| ^^^^^^^^^^^^^^
|
help: use `sub` instead
|
LL - let _ = ptr.offset(-5);
LL + let _ = ptr.sub(5);
|
error: use of `wrapping_offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:25:17
|
LL | let _ = ptr.wrapping_offset(5isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `wrapping_add` instead
|
LL - let _ = ptr.wrapping_offset(5isize);
LL + let _ = ptr.wrapping_add(5);
|
error: use of `wrapping_offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:27:17
|
LL | let _ = ptr.wrapping_offset(-5isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `wrapping_sub` instead
|
LL - let _ = ptr.wrapping_offset(-5isize);
LL + let _ = ptr.wrapping_sub(5);
|
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:30:17
|
LL | let _ = ptr.offset(-(5));
| ^^^^^^^^^^^^^^^^
|
help: use `sub` instead
|
LL - let _ = ptr.offset(-(5));
LL + let _ = ptr.sub(5);
|
error: use of `wrapping_offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:32:17
|
LL | let _ = ptr.wrapping_offset(-(5));
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `wrapping_sub` instead
|
LL - let _ = ptr.wrapping_offset(-(5));
LL + let _ = ptr.wrapping_sub(5);
|
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:36:17
|
LL | let _ = ptr.offset(2_147_483_647isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `add` instead
|
LL - let _ = ptr.offset(2_147_483_647isize);
LL + let _ = ptr.add(2_147_483_647);
|
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:38:17
|
LL | let _ = ptr.offset(-2_147_483_648isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `sub` instead
|
LL - let _ = ptr.offset(-2_147_483_648isize);
LL + let _ = ptr.sub(2_147_483_648);
|
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:41:17
|
LL | let _ = ptr.offset(5_0__isize);
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: use `add` instead
|
LL - let _ = ptr.offset(5_0__isize);
LL + let _ = ptr.add(5_0);
|
error: use of `offset` with a literal
--> tests/ui/ptr_offset_by_literal.rs:43:17
|
LL | let _ = ptr.offset(-5_0__isize);
| ^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `sub` instead
|
LL - let _ = ptr.offset(-5_0__isize);
LL + let _ = ptr.sub(5_0);
|
error: aborting due to 12 previous errors

View file

@ -24,6 +24,13 @@ struct GenericParam<T> {
t: T,
}
#[derive(Clone, Copy)]
struct PtrNamed {
ptr: *const u32,
}
#[derive(Clone, Copy)]
struct Ptr(*const u32);
fn transmute_ptr_to_ptr() {
let ptr = &1u32 as *const u32;
let mut_ptr = &mut 1u32 as *mut u32;
@ -68,6 +75,18 @@ fn transmute_ptr_to_ptr() {
let _: &GenericParam<&LifetimeParam<'static>> = unsafe { transmute(&GenericParam { t: &lp }) };
}
fn issue1966() {
let ptr = &1u32 as *const u32;
unsafe {
let _: *const f32 = Ptr(ptr).0.cast::<f32>();
//~^ transmute_ptr_to_ptr
let _: *const f32 = PtrNamed { ptr }.ptr.cast::<f32>();
//~^ transmute_ptr_to_ptr
let _: *mut u32 = Ptr(ptr).0.cast_mut();
//~^ transmute_ptr_to_ptr
}
}
fn lifetime_to_static(v: *mut &()) -> *const &'static () {
unsafe { v as *const &() }
//~^ transmute_ptr_to_ptr
@ -81,11 +100,15 @@ const _: &() = {
unsafe { transmute::<&'static Zst, &'static ()>(zst) }
};
#[derive(Clone, Copy)]
struct Ptr8(*const u8);
#[clippy::msrv = "1.37"]
fn msrv_1_37(ptr: *const u8) {
unsafe {
let _: *const i8 = ptr as *const i8;
//~^ transmute_ptr_to_ptr
let _: *const i8 = Ptr8(ptr).0 as *const i8;
//~^ transmute_ptr_to_ptr
}
}

View file

@ -24,6 +24,13 @@ struct GenericParam<T> {
t: T,
}
#[derive(Clone, Copy)]
struct PtrNamed {
ptr: *const u32,
}
#[derive(Clone, Copy)]
struct Ptr(*const u32);
fn transmute_ptr_to_ptr() {
let ptr = &1u32 as *const u32;
let mut_ptr = &mut 1u32 as *mut u32;
@ -68,6 +75,18 @@ fn transmute_ptr_to_ptr() {
let _: &GenericParam<&LifetimeParam<'static>> = unsafe { transmute(&GenericParam { t: &lp }) };
}
fn issue1966() {
let ptr = &1u32 as *const u32;
unsafe {
let _: *const f32 = transmute(Ptr(ptr));
//~^ transmute_ptr_to_ptr
let _: *const f32 = transmute(PtrNamed { ptr });
//~^ transmute_ptr_to_ptr
let _: *mut u32 = transmute(Ptr(ptr));
//~^ transmute_ptr_to_ptr
}
}
fn lifetime_to_static(v: *mut &()) -> *const &'static () {
unsafe { transmute(v) }
//~^ transmute_ptr_to_ptr
@ -81,11 +100,15 @@ const _: &() = {
unsafe { transmute::<&'static Zst, &'static ()>(zst) }
};
#[derive(Clone, Copy)]
struct Ptr8(*const u8);
#[clippy::msrv = "1.37"]
fn msrv_1_37(ptr: *const u8) {
unsafe {
let _: *const i8 = transmute(ptr);
//~^ transmute_ptr_to_ptr
let _: *const i8 = transmute(Ptr8(ptr));
//~^ transmute_ptr_to_ptr
}
}

View file

@ -1,5 +1,5 @@
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:32:29
--> tests/ui/transmute_ptr_to_ptr.rs:39:29
|
LL | let _: *const f32 = transmute(ptr);
| ^^^^^^^^^^^^^^
@ -13,7 +13,7 @@ LL + let _: *const f32 = ptr.cast::<f32>();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:35:27
--> tests/ui/transmute_ptr_to_ptr.rs:42:27
|
LL | let _: *mut f32 = transmute(mut_ptr);
| ^^^^^^^^^^^^^^^^^^
@ -25,37 +25,37 @@ LL + let _: *mut f32 = mut_ptr.cast::<f32>();
|
error: transmute from a reference to a reference
--> tests/ui/transmute_ptr_to_ptr.rs:39:23
--> tests/ui/transmute_ptr_to_ptr.rs:46:23
|
LL | let _: &f32 = transmute(&1u32);
| ^^^^^^^^^^^^^^^^ help: try: `&*(&1u32 as *const u32 as *const f32)`
error: transmute from a reference to a reference
--> tests/ui/transmute_ptr_to_ptr.rs:42:23
--> tests/ui/transmute_ptr_to_ptr.rs:49:23
|
LL | let _: &f32 = transmute(&1f64);
| ^^^^^^^^^^^^^^^^ help: try: `&*(&1f64 as *const f64 as *const f32)`
error: transmute from a reference to a reference
--> tests/ui/transmute_ptr_to_ptr.rs:47:27
--> tests/ui/transmute_ptr_to_ptr.rs:54:27
|
LL | let _: &mut f32 = transmute(&mut 1u32);
| ^^^^^^^^^^^^^^^^^^^^ help: try: `&mut *(&mut 1u32 as *mut u32 as *mut f32)`
error: transmute from a reference to a reference
--> tests/ui/transmute_ptr_to_ptr.rs:50:37
--> tests/ui/transmute_ptr_to_ptr.rs:57:37
|
LL | let _: &GenericParam<f32> = transmute(&GenericParam { t: 1u32 });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(&GenericParam { t: 1u32 } as *const GenericParam<u32> as *const GenericParam<f32>)`
error: transmute from a reference to a reference
--> tests/ui/transmute_ptr_to_ptr.rs:54:27
--> tests/ui/transmute_ptr_to_ptr.rs:61:27
|
LL | let u8_ref: &u8 = transmute(u64_ref);
| ^^^^^^^^^^^^^^^^^^ help: try: `&*(u64_ref as *const u64 as *const u8)`
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:57:29
--> tests/ui/transmute_ptr_to_ptr.rs:64:29
|
LL | let _: *const u32 = transmute(mut_ptr);
| ^^^^^^^^^^^^^^^^^^
@ -67,7 +67,7 @@ LL + let _: *const u32 = mut_ptr.cast_const();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:60:27
--> tests/ui/transmute_ptr_to_ptr.rs:67:27
|
LL | let _: *mut u32 = transmute(ptr);
| ^^^^^^^^^^^^^^
@ -79,7 +79,43 @@ LL + let _: *mut u32 = ptr.cast_mut();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:72:14
--> tests/ui/transmute_ptr_to_ptr.rs:81:29
|
LL | let _: *const f32 = transmute(Ptr(ptr));
| ^^^^^^^^^^^^^^^^^^^
|
help: use `pointer::cast` instead
|
LL - let _: *const f32 = transmute(Ptr(ptr));
LL + let _: *const f32 = Ptr(ptr).0.cast::<f32>();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:83:29
|
LL | let _: *const f32 = transmute(PtrNamed { ptr });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use `pointer::cast` instead
|
LL - let _: *const f32 = transmute(PtrNamed { ptr });
LL + let _: *const f32 = PtrNamed { ptr }.ptr.cast::<f32>();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:85:27
|
LL | let _: *mut u32 = transmute(Ptr(ptr));
| ^^^^^^^^^^^^^^^^^^^
|
help: use `pointer::cast_mut` instead
|
LL - let _: *mut u32 = transmute(Ptr(ptr));
LL + let _: *mut u32 = Ptr(ptr).0.cast_mut();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:91:14
|
LL | unsafe { transmute(v) }
| ^^^^^^^^^^^^
@ -91,7 +127,7 @@ LL + unsafe { v as *const &() }
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:87:28
--> tests/ui/transmute_ptr_to_ptr.rs:108:28
|
LL | let _: *const i8 = transmute(ptr);
| ^^^^^^^^^^^^^^
@ -103,7 +139,19 @@ LL + let _: *const i8 = ptr as *const i8;
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:95:28
--> tests/ui/transmute_ptr_to_ptr.rs:110:28
|
LL | let _: *const i8 = transmute(Ptr8(ptr));
| ^^^^^^^^^^^^^^^^^^^^
|
help: use an `as` cast instead
|
LL - let _: *const i8 = transmute(Ptr8(ptr));
LL + let _: *const i8 = Ptr8(ptr).0 as *const i8;
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:118:28
|
LL | let _: *const i8 = transmute(ptr);
| ^^^^^^^^^^^^^^
@ -115,7 +163,7 @@ LL + let _: *const i8 = ptr.cast::<i8>();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:103:26
--> tests/ui/transmute_ptr_to_ptr.rs:126:26
|
LL | let _: *mut u8 = transmute(ptr);
| ^^^^^^^^^^^^^^
@ -127,7 +175,7 @@ LL + let _: *mut u8 = ptr as *mut u8;
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:105:28
--> tests/ui/transmute_ptr_to_ptr.rs:128:28
|
LL | let _: *const u8 = transmute(mut_ptr);
| ^^^^^^^^^^^^^^^^^^
@ -139,7 +187,7 @@ LL + let _: *const u8 = mut_ptr as *const u8;
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:113:26
--> tests/ui/transmute_ptr_to_ptr.rs:136:26
|
LL | let _: *mut u8 = transmute(ptr);
| ^^^^^^^^^^^^^^
@ -151,7 +199,7 @@ LL + let _: *mut u8 = ptr.cast_mut();
|
error: transmute from a pointer to a pointer
--> tests/ui/transmute_ptr_to_ptr.rs:115:28
--> tests/ui/transmute_ptr_to_ptr.rs:138:28
|
LL | let _: *const u8 = transmute(mut_ptr);
| ^^^^^^^^^^^^^^^^^^
@ -162,5 +210,5 @@ LL - let _: *const u8 = transmute(mut_ptr);
LL + let _: *const u8 = mut_ptr.cast_const();
|
error: aborting due to 16 previous errors
error: aborting due to 20 previous errors

View file

@ -55,6 +55,52 @@ fn issue1231() {
//~^ transmute_ptr_to_ref
}
#[derive(Clone, Copy)]
struct PtrRefNamed<'a> {
ptr: *const &'a u32,
}
#[derive(Clone, Copy)]
struct PtrRef<'a>(*const &'a u32);
#[derive(Clone, Copy)]
struct PtrSliceRef<'a>(*const [&'a str]);
#[derive(Clone, Copy)]
struct PtrSlice(*const [i32]);
#[derive(Clone, Copy)]
struct Ptr(*const u32);
impl std::ops::Add for Ptr {
type Output = Self;
fn add(self, _: Self) -> Self {
self
}
}
mod ptr_mod {
#[derive(Clone, Copy)]
pub struct Ptr(*const u32);
}
fn issue1966(u: PtrSlice, v: PtrSliceRef, w: Ptr, x: PtrRefNamed, y: PtrRef, z: ptr_mod::Ptr) {
unsafe {
let _: &i32 = &*(w.0 as *const i32);
//~^ transmute_ptr_to_ref
let _: &u32 = &*w.0;
//~^ transmute_ptr_to_ref
let _: &&u32 = &*x.ptr.cast::<&u32>();
//~^ transmute_ptr_to_ref
// The field is not accessible. The program should not generate code
// that accesses the field.
let _: &u32 = std::mem::transmute(z);
let _ = &*w.0.cast::<u32>();
//~^ transmute_ptr_to_ref
let _: &[&str] = &*(v.0 as *const [&str]);
//~^ transmute_ptr_to_ref
let _ = &*(u.0 as *const [i32]);
//~^ transmute_ptr_to_ref
let _: &&u32 = &*y.0.cast::<&u32>();
//~^ transmute_ptr_to_ref
let _: &u32 = &*(w + w).0;
//~^ transmute_ptr_to_ref
}
}
fn issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 {
unsafe {
match 0 {
@ -89,7 +135,7 @@ fn meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 {
}
#[clippy::msrv = "1.37"]
fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 {
fn under_msrv<'a, 'b, 'c>(x: *const &'a u32, y: PtrRef) -> &'c &'b u32 {
unsafe {
let a = 0u32;
let a = &a as *const u32;
@ -97,10 +143,16 @@ fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 {
//~^ transmute_ptr_to_ref
let _: &u32 = &*(a as *const u32);
//~^ transmute_ptr_to_ref
let _ = &*(Ptr(a).0 as *const u32);
//~^ transmute_ptr_to_ref
match 0 {
0 => &*(x as *const () as *const &u32),
//~^ transmute_ptr_to_ref
_ => &*(x as *const () as *const &'b u32),
1 => &*(x as *const () as *const &'b u32),
//~^ transmute_ptr_to_ref
2 => &*(y.0 as *const () as *const &u32),
//~^ transmute_ptr_to_ref
_ => &*(y.0 as *const () as *const &'b u32),
//~^ transmute_ptr_to_ref
}
}

View file

@ -55,6 +55,52 @@ fn issue1231() {
//~^ transmute_ptr_to_ref
}
#[derive(Clone, Copy)]
struct PtrRefNamed<'a> {
ptr: *const &'a u32,
}
#[derive(Clone, Copy)]
struct PtrRef<'a>(*const &'a u32);
#[derive(Clone, Copy)]
struct PtrSliceRef<'a>(*const [&'a str]);
#[derive(Clone, Copy)]
struct PtrSlice(*const [i32]);
#[derive(Clone, Copy)]
struct Ptr(*const u32);
impl std::ops::Add for Ptr {
type Output = Self;
fn add(self, _: Self) -> Self {
self
}
}
mod ptr_mod {
#[derive(Clone, Copy)]
pub struct Ptr(*const u32);
}
fn issue1966(u: PtrSlice, v: PtrSliceRef, w: Ptr, x: PtrRefNamed, y: PtrRef, z: ptr_mod::Ptr) {
unsafe {
let _: &i32 = std::mem::transmute(w);
//~^ transmute_ptr_to_ref
let _: &u32 = std::mem::transmute(w);
//~^ transmute_ptr_to_ref
let _: &&u32 = core::mem::transmute(x);
//~^ transmute_ptr_to_ref
// The field is not accessible. The program should not generate code
// that accesses the field.
let _: &u32 = std::mem::transmute(z);
let _ = std::mem::transmute::<_, &u32>(w);
//~^ transmute_ptr_to_ref
let _: &[&str] = core::mem::transmute(v);
//~^ transmute_ptr_to_ref
let _ = std::mem::transmute::<_, &[i32]>(u);
//~^ transmute_ptr_to_ref
let _: &&u32 = std::mem::transmute(y);
//~^ transmute_ptr_to_ref
let _: &u32 = std::mem::transmute(w + w);
//~^ transmute_ptr_to_ref
}
}
fn issue8924<'a, 'b, 'c>(x: *const &'a u32, y: *const &'b u32) -> &'c &'b u32 {
unsafe {
match 0 {
@ -89,7 +135,7 @@ fn meets_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 {
}
#[clippy::msrv = "1.37"]
fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 {
fn under_msrv<'a, 'b, 'c>(x: *const &'a u32, y: PtrRef) -> &'c &'b u32 {
unsafe {
let a = 0u32;
let a = &a as *const u32;
@ -97,10 +143,16 @@ fn under_msrv<'a, 'b, 'c>(x: *const &'a u32) -> &'c &'b u32 {
//~^ transmute_ptr_to_ref
let _: &u32 = std::mem::transmute::<_, &u32>(a);
//~^ transmute_ptr_to_ref
let _ = std::mem::transmute::<_, &u32>(Ptr(a));
//~^ transmute_ptr_to_ref
match 0 {
0 => std::mem::transmute(x),
//~^ transmute_ptr_to_ref
_ => std::mem::transmute::<_, &&'b u32>(x),
1 => std::mem::transmute::<_, &&'b u32>(x),
//~^ transmute_ptr_to_ref
2 => std::mem::transmute(y),
//~^ transmute_ptr_to_ref
_ => std::mem::transmute::<_, &&'b u32>(y),
//~^ transmute_ptr_to_ref
}
}

View file

@ -61,125 +61,191 @@ error: transmute from a pointer type (`*const i32`) to a reference type (`&u8`)
LL | unsafe { std::mem::transmute::<_, Bar>(raw) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(raw as *const u8)`
error: transmute from a pointer type (`*const u32`) to a reference type (`&i32`)
--> tests/ui/transmute_ptr_to_ref.rs:82:23
|
LL | let _: &i32 = std::mem::transmute(w);
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(w.0 as *const i32)`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:84:23
|
LL | let _: &u32 = std::mem::transmute(w);
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*w.0`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:61:18
--> tests/ui/transmute_ptr_to_ref.rs:86:24
|
LL | let _: &&u32 = core::mem::transmute(x);
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.ptr.cast::<&u32>()`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:91:17
|
LL | let _ = std::mem::transmute::<_, &u32>(w);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*w.0.cast::<u32>()`
error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`)
--> tests/ui/transmute_ptr_to_ref.rs:93:26
|
LL | let _: &[&str] = core::mem::transmute(v);
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(v.0 as *const [&str])`
error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`)
--> tests/ui/transmute_ptr_to_ref.rs:95:17
|
LL | let _ = std::mem::transmute::<_, &[i32]>(u);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(u.0 as *const [i32])`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:97:24
|
LL | let _: &&u32 = std::mem::transmute(y);
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.0.cast::<&u32>()`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:99:23
|
LL | let _: &u32 = std::mem::transmute(w + w);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(w + w).0`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:107:18
|
LL | 0 => std::mem::transmute(x),
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&u32>()`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:63:18
--> tests/ui/transmute_ptr_to_ref.rs:109:18
|
LL | 1 => std::mem::transmute(y),
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.cast::<&u32>()`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:65:18
--> tests/ui/transmute_ptr_to_ref.rs:111:18
|
LL | 2 => std::mem::transmute::<_, &&'b u32>(x),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&'b u32>()`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:67:18
--> tests/ui/transmute_ptr_to_ref.rs:113:18
|
LL | _ => std::mem::transmute::<_, &&'b u32>(y),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*y.cast::<&'b u32>()`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:78:23
--> tests/ui/transmute_ptr_to_ref.rs:124:23
|
LL | let _: &u32 = std::mem::transmute(a);
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:80:23
--> tests/ui/transmute_ptr_to_ref.rs:126:23
|
LL | let _: &u32 = std::mem::transmute::<_, &u32>(a);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a.cast::<u32>()`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:83:18
--> tests/ui/transmute_ptr_to_ref.rs:129:18
|
LL | 0 => std::mem::transmute(x),
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&u32>()`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:85:18
--> tests/ui/transmute_ptr_to_ref.rs:131:18
|
LL | _ => std::mem::transmute::<_, &&'b u32>(x),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*x.cast::<&'b u32>()`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:96:23
--> tests/ui/transmute_ptr_to_ref.rs:142:23
|
LL | let _: &u32 = std::mem::transmute(a);
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*a`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:98:23
--> tests/ui/transmute_ptr_to_ref.rs:144:23
|
LL | let _: &u32 = std::mem::transmute::<_, &u32>(a);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a as *const u32)`
error: transmute from a pointer type (`*const u32`) to a reference type (`&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:146:17
|
LL | let _ = std::mem::transmute::<_, &u32>(Ptr(a));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(Ptr(a).0 as *const u32)`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:101:18
--> tests/ui/transmute_ptr_to_ref.rs:149:18
|
LL | 0 => std::mem::transmute(x),
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &u32)`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:103:18
--> tests/ui/transmute_ptr_to_ref.rs:151:18
|
LL | _ => std::mem::transmute::<_, &&'b u32>(x),
LL | 1 => std::mem::transmute::<_, &&'b u32>(x),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(x as *const () as *const &'b u32)`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:153:18
|
LL | 2 => std::mem::transmute(y),
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(y.0 as *const () as *const &u32)`
error: transmute from a pointer type (`*const &u32`) to a reference type (`&&u32`)
--> tests/ui/transmute_ptr_to_ref.rs:155:18
|
LL | _ => std::mem::transmute::<_, &&'b u32>(y),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(y.0 as *const () as *const &'b u32)`
error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[u32]`)
--> tests/ui/transmute_ptr_to_ref.rs:113:17
--> tests/ui/transmute_ptr_to_ref.rs:165:17
|
LL | let _ = core::mem::transmute::<_, &[u32]>(ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [u32])`
error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[u32]`)
--> tests/ui/transmute_ptr_to_ref.rs:115:25
--> tests/ui/transmute_ptr_to_ref.rs:167:25
|
LL | let _: &[u32] = core::mem::transmute(ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [u32])`
error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&[u8]]`)
--> tests/ui/transmute_ptr_to_ref.rs:119:17
--> tests/ui/transmute_ptr_to_ref.rs:171:17
|
LL | let _ = core::mem::transmute::<_, &[&[u8]]>(a_s_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&[u8]])`
error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&[u8]]`)
--> tests/ui/transmute_ptr_to_ref.rs:121:27
--> tests/ui/transmute_ptr_to_ref.rs:173:27
|
LL | let _: &[&[u8]] = core::mem::transmute(a_s_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&[u8]])`
error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`)
--> tests/ui/transmute_ptr_to_ref.rs:125:17
--> tests/ui/transmute_ptr_to_ref.rs:177:17
|
LL | let _ = core::mem::transmute::<_, &[i32]>(ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(ptr as *const [i32])`
error: transmute from a pointer type (`*const [i32]`) to a reference type (`&[i32]`)
--> tests/ui/transmute_ptr_to_ref.rs:127:25
--> tests/ui/transmute_ptr_to_ref.rs:179:25
|
LL | let _: &[i32] = core::mem::transmute(ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*ptr`
error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`)
--> tests/ui/transmute_ptr_to_ref.rs:131:17
--> tests/ui/transmute_ptr_to_ref.rs:183:17
|
LL | let _ = core::mem::transmute::<_, &[&str]>(a_s_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&str])`
error: transmute from a pointer type (`*const [&str]`) to a reference type (`&[&str]`)
--> tests/ui/transmute_ptr_to_ref.rs:133:26
--> tests/ui/transmute_ptr_to_ref.rs:185:26
|
LL | let _: &[&str] = core::mem::transmute(a_s_ptr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&*(a_s_ptr as *const [&str])`
error: aborting due to 30 previous errors
error: aborting due to 41 previous errors

View file

@ -116,3 +116,26 @@ fn msrv_juust_right() {
let x = &[1, 2];
let x = (x[0], x[1]);
}
fn issue16192() {
fn do_something(tuple: (u32, u32)) {}
fn produce_array() -> [u32; 2] {
[1, 2]
}
let [a, b] = produce_array();
for tuple in [(a, b), (b, a)] {
do_something(tuple);
}
let [a, b] = produce_array();
let x = b;
do_something((a, b));
let [a, b] = produce_array();
do_something((b, a));
let [a, b] = produce_array();
do_something((a, b));
//~^ tuple_array_conversions
}

View file

@ -80,5 +80,13 @@ LL | let x = [x.0, x.1];
|
= help: use `.into()` instead, or `<[T; N]>::from` if type annotations are needed
error: aborting due to 10 previous errors
error: it looks like you're trying to convert an array to a tuple
--> tests/ui/tuple_array_conversions.rs:139:18
|
LL | do_something((a, b));
| ^^^^^^
|
= help: use `.into()` instead, or `<(T0, T1, ..., Tn)>::from` if type annotations are needed
error: aborting due to 11 previous errors

View file

@ -442,3 +442,14 @@ fn issue14739() {
let _ = R.map(|_x| 0);
//~^ useless_conversion
}
fn issue16165() {
macro_rules! mac {
(iter $e:expr) => {
$e.iter()
};
}
for _ in mac!(iter [1, 2]) {}
//~^ useless_conversion
}

View file

@ -442,3 +442,14 @@ fn issue14739() {
let _ = R.into_iter().map(|_x| 0);
//~^ useless_conversion
}
fn issue16165() {
macro_rules! mac {
(iter $e:expr) => {
$e.iter()
};
}
for _ in mac!(iter [1, 2]).into_iter() {}
//~^ useless_conversion
}

View file

@ -389,5 +389,11 @@ error: useless conversion to the same type: `std::ops::Range<u32>`
LL | let _ = R.into_iter().map(|_x| 0);
| ^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `R`
error: aborting due to 43 previous errors
error: useless conversion to the same type: `std::slice::Iter<'_, i32>`
--> tests/ui/useless_conversion.rs:453:14
|
LL | for _ in mac!(iter [1, 2]).into_iter() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `mac!(iter [1, 2])`
error: aborting due to 44 previous errors

View file

@ -492,6 +492,110 @@ fn issue13123() {
}
}
fn issue16089() {
trait CertainTrait: Iterator<Item = u8> {
fn iter_over_self(&mut self) {
let mut a = 0;
for r in &mut *self {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}
fn use_after_iter(&mut self) {}
}
}
fn issue16089_sized_trait_not_reborrowed() {
trait CertainTrait: Iterator<Item = u8> + Sized {
fn iter_over_self(&mut self) {
let mut a = 0;
// Check that the suggestion is just "self", since the trait is sized.
for r in self.by_ref() {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}
fn use_after_iter(&mut self) {}
}
}
fn issue16089_nested_derefs() {
struct S<T>(T);
impl<T> core::ops::Deref for S<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for S<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
fn f(mut x: S<S<&mut dyn Iterator<Item = u32>>>) {
for _ in &mut ***x {}
//~^ while_let_on_iterator
}
}
fn issue16089_nested_derefs_last_not_sized() {
struct WithSize<T>(T);
impl<T> core::ops::Deref for WithSize<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for WithSize<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
// The suggestion must use `&mut **x`. Using `x.by_ref()` doesn't work in this
// case, since the last type adjustment for `x` in the expression `x.next()` is
// to dereference a `?Sized` trait.
fn f(mut x: WithSize<&mut dyn Iterator<Item = u32>>) {
for _ in &mut **x {}
//~^ while_let_on_iterator
}
}
fn issue16089_nested_derefs_last_sized() {
struct NoSize<T: ?Sized>(T);
impl<T: ?Sized> core::ops::Deref for NoSize<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: ?Sized> core::ops::DerefMut for NoSize<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
struct SizedIter {}
impl Iterator for SizedIter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
Some(0)
}
}
// We want the suggestion to be `x.by_ref()`. It works in this case since the last type
// adjustment for `x` in the expression `x.next()` is to dereference a Sized type.
fn f(mut x: NoSize<NoSize<SizedIter>>) {
for _ in x.by_ref() {}
//~^ while_let_on_iterator
}
}
fn main() {
let mut it = 0..20;
for _ in it {

View file

@ -492,6 +492,110 @@ fn issue13123() {
}
}
fn issue16089() {
trait CertainTrait: Iterator<Item = u8> {
fn iter_over_self(&mut self) {
let mut a = 0;
while let Some(r) = self.next() {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}
fn use_after_iter(&mut self) {}
}
}
fn issue16089_sized_trait_not_reborrowed() {
trait CertainTrait: Iterator<Item = u8> + Sized {
fn iter_over_self(&mut self) {
let mut a = 0;
// Check that the suggestion is just "self", since the trait is sized.
while let Some(r) = self.next() {
//~^ while_let_on_iterator
a = r;
}
self.use_after_iter()
}
fn use_after_iter(&mut self) {}
}
}
fn issue16089_nested_derefs() {
struct S<T>(T);
impl<T> core::ops::Deref for S<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for S<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
fn f(mut x: S<S<&mut dyn Iterator<Item = u32>>>) {
while let Some(_) = x.next() {}
//~^ while_let_on_iterator
}
}
fn issue16089_nested_derefs_last_not_sized() {
struct WithSize<T>(T);
impl<T> core::ops::Deref for WithSize<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for WithSize<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
// The suggestion must use `&mut **x`. Using `x.by_ref()` doesn't work in this
// case, since the last type adjustment for `x` in the expression `x.next()` is
// to dereference a `?Sized` trait.
fn f(mut x: WithSize<&mut dyn Iterator<Item = u32>>) {
while let Some(_) = x.next() {}
//~^ while_let_on_iterator
}
}
fn issue16089_nested_derefs_last_sized() {
struct NoSize<T: ?Sized>(T);
impl<T: ?Sized> core::ops::Deref for NoSize<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: ?Sized> core::ops::DerefMut for NoSize<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
struct SizedIter {}
impl Iterator for SizedIter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
Some(0)
}
}
// We want the suggestion to be `x.by_ref()`. It works in this case since the last type
// adjustment for `x` in the expression `x.next()` is to dereference a Sized type.
fn f(mut x: NoSize<NoSize<SizedIter>>) {
while let Some(_) = x.next() {}
//~^ while_let_on_iterator
}
}
fn main() {
let mut it = 0..20;
while let Some(..) = it.next() {

View file

@ -164,10 +164,40 @@ LL | 'label: while let Some(n) = it.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'label: for n in it`
error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:497:5
--> tests/ui/while_let_on_iterator.rs:499:13
|
LL | while let Some(r) = self.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for r in &mut *self`
error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:515:13
|
LL | while let Some(r) = self.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for r in self.by_ref()`
error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:541:9
|
LL | while let Some(_) = x.next() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in &mut ***x`
error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:563:9
|
LL | while let Some(_) = x.next() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in &mut **x`
error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:594:9
|
LL | while let Some(_) = x.next() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in x.by_ref()`
error: this loop could be written as a `for` loop
--> tests/ui/while_let_on_iterator.rs:601:5
|
LL | while let Some(..) = it.next() {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it`
error: aborting due to 28 previous errors
error: aborting due to 33 previous errors

View file

@ -1,4 +1,4 @@
#[allow(clippy::borrow_as_ptr)]
#[allow(clippy::borrow_as_ptr, clippy::ptr_offset_by_literal)]
fn main() {
unsafe {
let m = &mut () as *mut ();

View file

@ -1,6 +1,11 @@
#![warn(clippy::zero_repeat_side_effects)]
#![expect(clippy::unnecessary_operation, clippy::useless_vec, clippy::needless_late_init)]
#![allow(clippy::no_effect)] // only fires _after_ the fix
#![allow(
clippy::unnecessary_operation,
clippy::useless_vec,
clippy::needless_late_init,
clippy::single_match,
clippy::no_effect // only fires _after_ the fix
)]
fn f() -> i32 {
println!("side effect");
@ -119,3 +124,26 @@ fn issue_14681() {
});
//~^ zero_repeat_side_effects
}
fn issue_15824() {
fn f() {}
match 0 {
0 => {
f();
_ = [] as [(); 0]
},
//~^ zero_repeat_side_effects
_ => {},
}
let mut a = [(); 0];
match 0 {
0 => {
f();
a = [] as [(); 0]
},
//~^ zero_repeat_side_effects
_ => {},
}
}

View file

@ -1,6 +1,11 @@
#![warn(clippy::zero_repeat_side_effects)]
#![expect(clippy::unnecessary_operation, clippy::useless_vec, clippy::needless_late_init)]
#![allow(clippy::no_effect)] // only fires _after_ the fix
#![allow(
clippy::unnecessary_operation,
clippy::useless_vec,
clippy::needless_late_init,
clippy::single_match,
clippy::no_effect // only fires _after_ the fix
)]
fn f() -> i32 {
println!("side effect");
@ -102,3 +107,20 @@ fn issue_14681() {
foo(&[Some(Some(S::new())); 0]);
//~^ zero_repeat_side_effects
}
fn issue_15824() {
fn f() {}
match 0 {
0 => _ = [f(); 0],
//~^ zero_repeat_side_effects
_ => {},
}
let mut a = [(); 0];
match 0 {
0 => a = [f(); 0],
//~^ zero_repeat_side_effects
_ => {},
}
}

View file

@ -1,5 +1,5 @@
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:17:5
--> tests/ui/zero_repeat_side_effects.rs:22:5
|
LL | let a = [f(); 0];
| ^^^^^^^^^^^^^^^^^
@ -13,7 +13,7 @@ LL + let a: [i32; 0] = [];
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:20:5
--> tests/ui/zero_repeat_side_effects.rs:25:5
|
LL | b = [f(); 0];
| ^^^^^^^^^^^^
@ -25,7 +25,7 @@ LL ~ b = [] as [i32; 0];
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:25:5
--> tests/ui/zero_repeat_side_effects.rs:30:5
|
LL | let c = vec![f(); 0];
| ^^^^^^^^^^^^^^^^^^^^^
@ -37,7 +37,7 @@ LL + let c: std::vec::Vec<i32> = vec![];
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:28:5
--> tests/ui/zero_repeat_side_effects.rs:33:5
|
LL | d = vec![f(); 0];
| ^^^^^^^^^^^^^^^^
@ -49,7 +49,7 @@ LL ~ d = vec![] as std::vec::Vec<i32>;
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:32:5
--> tests/ui/zero_repeat_side_effects.rs:37:5
|
LL | let e = [println!("side effect"); 0];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -61,7 +61,7 @@ LL + let e: [(); 0] = [];
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:36:5
--> tests/ui/zero_repeat_side_effects.rs:41:5
|
LL | let g = [{ f() }; 0];
| ^^^^^^^^^^^^^^^^^^^^^
@ -73,7 +73,7 @@ LL + let g: [i32; 0] = [];
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:40:10
--> tests/ui/zero_repeat_side_effects.rs:45:10
|
LL | drop(vec![f(); 0]);
| ^^^^^^^^^^^^
@ -87,7 +87,7 @@ LL ~ });
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:44:5
--> tests/ui/zero_repeat_side_effects.rs:49:5
|
LL | vec![f(); 0];
| ^^^^^^^^^^^^
@ -99,7 +99,7 @@ LL ~ vec![] as std::vec::Vec<i32>;
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:46:5
--> tests/ui/zero_repeat_side_effects.rs:51:5
|
LL | [f(); 0];
| ^^^^^^^^
@ -111,7 +111,7 @@ LL ~ [] as [i32; 0];
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:100:10
--> tests/ui/zero_repeat_side_effects.rs:105:10
|
LL | foo(&[Some(f()); 0]);
| ^^^^^^^^^^^^^^
@ -125,7 +125,7 @@ LL ~ });
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:102:10
--> tests/ui/zero_repeat_side_effects.rs:107:10
|
LL | foo(&[Some(Some(S::new())); 0]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@ -138,5 +138,33 @@ LL + [] as [std::option::Option<std::option::Option<S>>; 0]
LL ~ });
|
error: aborting due to 11 previous errors
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:115:14
|
LL | 0 => _ = [f(); 0],
| ^^^^^^^^^^^^
|
help: consider performing the side effect separately
|
LL ~ 0 => {
LL + f();
LL + _ = [] as [(); 0]
LL ~ },
|
error: expression with side effects as the initial value in a zero-sized array initializer
--> tests/ui/zero_repeat_side_effects.rs:122:14
|
LL | 0 => a = [f(); 0],
| ^^^^^^^^^^^^
|
help: consider performing the side effect separately
|
LL ~ 0 => {
LL + f();
LL + a = [] as [(); 0]
LL ~ },
|
error: aborting due to 13 previous errors

View file

@ -22,9 +22,6 @@ allow-unauthenticated = [
[mentions."clippy_lints/src/doc"]
cc = ["@notriddle"]
# Prevents mentions in commits to avoid users being spammed
[no-mentions]
# Have rustbot inform users about the *No Merge Policy*
[no-merges]
exclude_titles = ["Rustup"] # exclude syncs from rust-lang/rust
@ -65,6 +62,7 @@ users_on_vacation = [
"Manishearth",
"Alexendoo",
"y21",
"blyxyas",
]
[assign.owners]
@ -77,7 +75,6 @@ users_on_vacation = [
"@Alexendoo",
"@dswij",
"@Jarcho",
"@blyxyas",
"@y21",
"@samueltardieu",
]

View file

@ -637,14 +637,14 @@ pre, hr {
display: flex;
}
ul.dropdown-menu li.checkbox > button {
#menu-filters ul.dropdown-menu li.checkbox > button {
border: 0;
width: 100%;
background: var(--theme-popup-bg);
color: var(--fg);
}
ul.dropdown-menu li.checkbox > button:hover {
#menu-filters ul.dropdown-menu li.checkbox > button:hover {
background: var(--theme-hover);
box-shadow: none;
}