Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones
2026-04-16 19:07:27 +02:00
141 changed files with 3423 additions and 694 deletions
+118 -6
View File
@@ -6,7 +6,124 @@ document.
## Unreleased / Beta / In Rust Nightly
[500e0ff...master](https://github.com/rust-lang/rust-clippy/compare/500e0ff...master)
[df995e...master](https://github.com/rust-lang/rust-clippy/compare/df995e...master)
## Rust 1.95
Current stable, released 2026-04-16
[View all 107 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2026-01-09T11%3A07%3A27Z..2026-02-23T22%3A37%3A09Z+base%3Amaster)
### New Lints
* Added [`unnecessary_trailing_comma`] to `pedantic`
[#16530](https://github.com/rust-lang/rust-clippy/pull/16530)
* Added [`disallowed_fields`] to `style`
[#16218](https://github.com/rust-lang/rust-clippy/pull/16218)
* Added [`manual_checked_ops`] to `complexity`
[#16149](https://github.com/rust-lang/rust-clippy/pull/16149)
* Added [`duration_suboptimal_units`] to `pedantic`
[#16250](https://github.com/rust-lang/rust-clippy/pull/16250)
* Added [`manual_take`] to `complexity`
[#16368](https://github.com/rust-lang/rust-clippy/pull/16368)
### Enhancements
* [`explicit_counter_loop`] fix FN when loop counter starts at non-zero
[#16620](https://github.com/rust-lang/rust-clippy/pull/16620)
* [`manual_is_variant_and`] extend to cover `filter` chaining `is_some`
[#16521](https://github.com/rust-lang/rust-clippy/pull/16521)
* [`manual_is_variant_and`] enhance to cover manual `is_none_or`
[#16424](https://github.com/rust-lang/rust-clippy/pull/16424)
* [`collapsible_match`] extend to cover if-elses
[#16560](https://github.com/rust-lang/rust-clippy/pull/16560)
* [`useless_conversion`] also fire inside compiler desugarings
[#16594](https://github.com/rust-lang/rust-clippy/pull/16594)
* [`unwrap_used`] and [`expect_used`] add `allow-unwrap-types` configuration
[#16605](https://github.com/rust-lang/rust-clippy/pull/16605)
* [`unwrap_used`] and [`expect_used`] optimize `allow-unwrap-types` evaluation to eliminate performance regression
[#16652](https://github.com/rust-lang/rust-clippy/pull/16652)
* [`unchecked_time_subtraction`] extend to better handle `Duration` literals
[#16528](https://github.com/rust-lang/rust-clippy/pull/16528)
* [`unnecessary_fold`] match against an accumulator on both sides
[#16604](https://github.com/rust-lang/rust-clippy/pull/16604)
* [`iter_kv_map`] extend to cover `flat_map` and `filter_map`
[#16519](https://github.com/rust-lang/rust-clippy/pull/16519)
* [`question_mark`] enhance to cover `else if`
[#16455](https://github.com/rust-lang/rust-clippy/pull/16455)
* [`double_comparisons`] check for expressions such as `x != y && x >= y`
[#16033](https://github.com/rust-lang/rust-clippy/pull/16033)
* [`needless_collect`] enhance to cover vec `push`-alike cases
[#16305](https://github.com/rust-lang/rust-clippy/pull/16305)
* [`strlen_on_c_strings`] changes suggestion to use `CStr::count_bytes()`
[#16323](https://github.com/rust-lang/rust-clippy/pull/16323)
* [`transmuting_null`] now checks for `ptr::without_provenance` and `ptr::without_provenance_mut`
[#16336](https://github.com/rust-lang/rust-clippy/pull/16336)
* [`map_unwrap_or`] add cover for `Result::unwrap_or`
[#15718](https://github.com/rust-lang/rust-clippy/pull/15718)
* [`clone_on_ref_ptr`] don't add a `&` to the receiver if it's a reference
[#15742](https://github.com/rust-lang/rust-clippy/pull/15742)
* [`double_must_use`], [`drop_non_drop`], [`let_underscore_must_use`] consider `Result<T, U>` and
`ControlFlow<U, T>` as `T` wrt `#[must_use]` if `U` is uninhabited
[#16353](https://github.com/rust-lang/rust-clippy/pull/16353)
* [`str_to_string`] handle a case when `ToString::to_string` is passed as function parameter
[#16512](https://github.com/rust-lang/rust-clippy/pull/16512)
* [`must_use_candidate`] no longer lints `main` functions with return values
[#16552](https://github.com/rust-lang/rust-clippy/pull/16552)
* [`needless_continue`] `allow` and `expect` attributes can also be used on the statement
[#16265](https://github.com/rust-lang/rust-clippy/pull/16265)
* [`int_plus_one`] fix FN with negative literals, e.g. `-1 + x <= y`
[#16373](https://github.com/rust-lang/rust-clippy/pull/16373)
### False Positive Fixes
* [`assertions_on_result_states`] and [`missing_assert_message`] fix FP on edition 2015 and 2018
[#16473](https://github.com/rust-lang/rust-clippy/pull/16473)
* [`redundant_iter_cloned`] fix FP with move closures and coroutines
[#16494](https://github.com/rust-lang/rust-clippy/pull/16494)
* [`str_to_string`] fix FP on non-str types
[#16571](https://github.com/rust-lang/rust-clippy/pull/16571)
* [`unnecessary_cast`] do not warn on casts of external function return type
[#16415](https://github.com/rust-lang/rust-clippy/pull/16415)
* [`cmp_owned`] fix FP when `to_string` comes from macro input
[#16468](https://github.com/rust-lang/rust-clippy/pull/16468)
* [`useless_attribute`] fix FP on `exported_private_dependencies` lint attributes
[#16470](https://github.com/rust-lang/rust-clippy/pull/16470)
* [`manual_dangling_ptr`] fix FP when pointee type is not `Sized`
[#16469](https://github.com/rust-lang/rust-clippy/pull/16469)
* [`test_attr_in_doctest`] fix FP on `test_harness`
[#16454](https://github.com/rust-lang/rust-clippy/pull/16454)
* [`doc_paragraphs_missing_punctuation`] allow unpunctuated paragraphs before lists and code blocks
[#16487](https://github.com/rust-lang/rust-clippy/pull/16487)
* [`elidable_lifetime_names`] skip linting proc-macro generated code
[#16402](https://github.com/rust-lang/rust-clippy/pull/16402)
* [`undocumented_unsafe_blocks`] recognize safety comments inside blocks and on same line in macros
[#16339](https://github.com/rust-lang/rust-clippy/pull/16339)
### ICE Fixes
* [`match_same_arms`] fix ICE in `match_same_arms`
[#16685](https://github.com/rust-lang/rust-clippy/pull/16685)
* [`nonminimal_bool`] fix ICE in `swap_binop()` by using the proper `TypeckResults`
[#16659](https://github.com/rust-lang/rust-clippy/pull/16659)
* [`redundant_closure_for_method_calls`] fix ICE when computing the path from a type to itself
[#16362](https://github.com/rust-lang/rust-clippy/pull/16362)
### Documentation Improvements
* [`cast_possible_wrap`] mention `cast_{un,}signed()` methods in the documentation
[#16407](https://github.com/rust-lang/rust-clippy/pull/16407)
* [`ignore_without_reason`] and [`redundant_test_prefix`] mention an extra `clippy` argument
needed to check tests
[#16205](https://github.com/rust-lang/rust-clippy/pull/16205)
* [`doc_paragraphs_missing_punctuation`] improve its documentation
[#16377](https://github.com/rust-lang/rust-clippy/pull/16377)
* [`missing_trait_methods`] better help message
[#16380](https://github.com/rust-lang/rust-clippy/pull/16380)
* [`strlen_on_c_strings`] mention the specific type (`CString` or `CStr`)
[#16391](https://github.com/rust-lang/rust-clippy/pull/16391)
* [`suspicious_to_owned`] improve lint messages
[#16376](https://github.com/rust-lang/rust-clippy/pull/16376)
## Rust 1.94
@@ -104,11 +221,6 @@ Current stable, released 2026-03-05
* [`needless_type_cast`] do not ICE on struct constructor
[#16245](https://github.com/rust-lang/rust-clippy/pull/16245)
### New Lints
* Added [`unnecessary_trailing_comma`] to `style` (single-line format-like macros only)
[#13965](https://github.com/rust-lang/rust-clippy/issues/13965)
## Rust 1.93
Current stable, released 2026-01-22
+1 -1
View File
@@ -33,7 +33,7 @@ color-print = "0.3.4"
anstream = "0.6.18"
[dev-dependencies]
cargo_metadata = "0.18.1"
cargo_metadata = "0.23"
ui_test = "0.30.2"
regex = "1.5.5"
serde = { version = "1.0.145", features = ["derive"] }
+1
View File
@@ -928,6 +928,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else)
* [`manual_midpoint`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_midpoint)
* [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive)
* [`manual_noop_waker`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_noop_waker)
* [`manual_option_as_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_as_slice)
* [`manual_pattern_char_comparison`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison)
* [`manual_range_contains`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains)
+1
View File
@@ -789,6 +789,7 @@ fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
manual_let_else,
manual_midpoint,
manual_non_exhaustive,
manual_noop_waker,
manual_option_as_slice,
manual_pattern_char_comparison,
manual_range_contains,
+1 -1
View File
@@ -10,7 +10,7 @@ edition = "2024"
[dependencies]
arrayvec = { version = "0.7", default-features = false }
cargo_metadata = "0.18"
cargo_metadata = "0.23"
clippy_config = { path = "../clippy_config" }
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
@@ -306,9 +306,16 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
cur_f = Some(field);
}
},
ItemKind::Trait(_constness, is_auto, _safety, _impl_restriction, _ident, _generics, _generic_bounds, item_ref)
if self.enable_ordering_for_trait && *is_auto == IsAuto::No =>
{
ItemKind::Trait(
_constness,
is_auto,
_safety,
_impl_restriction,
_ident,
_generics,
_generic_bounds,
item_ref,
) if self.enable_ordering_for_trait && *is_auto == IsAuto::No => {
let mut cur_t: Option<(TraitItemId, Ident)> = None;
for &item in *item_ref {
+5 -2
View File
@@ -30,6 +30,8 @@
/// Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// Creates a big toll on performance, **only enable sporadically**
///
/// ### Example
/// ```ignore
/// if a && true {}
@@ -43,7 +45,7 @@
/// ```
#[clippy::version = "pre 1.29.0"]
pub NONMINIMAL_BOOL,
complexity,
pedantic,
"boolean expressions that can be written more concisely"
}
@@ -57,6 +59,7 @@
///
/// ### Known problems
/// Ignores short circuiting behavior.
/// Creates a big toll on performance, **only enable sporadically**
///
/// ### Example
/// ```rust,ignore
@@ -70,7 +73,7 @@
/// ```
#[clippy::version = "pre 1.29.0"]
pub OVERLY_COMPLEX_BOOL_EXPR,
correctness,
pedantic,
"boolean expressions that contain terminals which can be eliminated"
}
+64 -32
View File
@@ -1,9 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_ast::token::{Lit, LitKind};
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, span_contains_cfg, span_contains_comment};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@@ -30,47 +36,73 @@
declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]);
impl EarlyLintPass for ByteCharSlice {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
impl<'tcx> LateLintPass<'tcx> for ByteCharSlice {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !expr.span.from_expansion()
&& let Some(slice) = is_byte_char_slices(expr)
&& let Some((has_ref, slice)) = is_byte_char_slices(cx, expr)
{
span_lint_and_sugg(
span_lint_and_then(
cx,
BYTE_CHAR_SLICES,
expr.span,
"can be more succinctly written as a byte str",
"try",
format!("b\"{slice}\""),
Applicability::MachineApplicable,
|diag| {
let mut app = Applicability::MachineApplicable;
let mut sugg = Sugg::hir_from_snippet(cx, expr, |_| {
let mut slice = slice.iter().fold("b\"".to_owned(), |mut acc, span| {
let snippet = snippet_with_applicability(cx, *span, "b'?'", &mut app);
acc.push_str(match &snippet[2..snippet.len() - 1] {
"\"" => "\\\"",
"\\'" => "'",
other => other,
});
acc
});
slice.push('"');
Cow::Owned(slice)
});
if !has_ref && !cx.typeck_results().expr_ty_adjusted(expr).is_array_slice() {
sugg = sugg.deref();
}
diag.span_suggestion(expr.span, "try", sugg, app);
},
);
}
}
}
/// Checks whether the slice is that of byte chars, and if so, builds a byte-string out of it
fn is_byte_char_slices(expr: &Expr) -> Option<String> {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind
&& let ExprKind::Array(members) = &expr.kind
&& !members.is_empty()
fn is_byte_char_slices<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<(bool, Vec<Span>)> {
let (has_ref, expr) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = expr.kind {
(true, inner)
} else if let Some(parent) = get_parent_expr(cx, expr) // Already checked by the parent expr.
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind
{
members
.iter()
.map(|member| match &member.kind {
ExprKind::Lit(Lit {
kind: LitKind::Byte,
symbol,
..
}) => Some(symbol.as_str()),
_ => None,
})
.map(|maybe_quote| match maybe_quote {
Some("\"") => Some("\\\""),
Some("\\'") => Some("'"),
other => other,
})
.collect::<Option<String>>()
return None;
} else {
None
(false, expr)
};
if let ExprKind::Array(members) = expr.kind
&& !members.is_empty()
&& !span_contains_comment(cx, expr.span)
&& !span_contains_cfg(cx, expr.span)
{
return members
.iter()
.try_fold(Vec::new(), |mut acc, member| {
if let ExprKind::Lit(lit) = member.kind
&& let LitKind::Byte(_) = lit.node
&& expr.span.eq_ctxt(member.span)
{
acc.push(lit.span);
return Some(acc);
}
None
})
.map(|s| (has_ref, s));
}
None
}
@@ -32,7 +32,7 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, allowed_duplicate
{
for (name, group) in &packages
.iter()
.filter(|p| !allowed_duplicate_crates.contains(&p.name))
.filter(|p| !allowed_duplicate_crates.contains(p.name.as_str()))
.group_by(|p| &p.name)
{
let group: Vec<&Package> = group.collect();
@@ -10,7 +10,7 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
// VersionReq::any() does not work
if let Ok(wildcard_ver) = semver::VersionReq::parse("*")
&& let Some(ref source) = dep.source
&& !source.starts_with("git")
&& !source.repr.starts_with("git")
&& dep.req == wildcard_ver
{
span_lint(
@@ -8,11 +8,6 @@
use super::FN_TO_NUMERIC_CAST_ANY;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
// We allow casts from any function type to any function type.
if cast_to.is_fn() {
return;
}
if cast_from.is_fn() {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
+3 -3
View File
@@ -911,10 +911,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
ptr_as_ptr::check(cx, expr, cast_from_expr, cast_from, cast_to_hir, cast_to, self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv);
if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) {
@@ -932,6 +929,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
}
cast_lossless::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir, self.msrv);
cast_enum_constructor::check(cx, expr, cast_from_expr, cast_from);
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
}
as_underscore::check(cx, expr, cast_to_hir);
+2 -3
View File
@@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{ExprUseNode, expr_use_ctxt, is_expr_temporary_value, std_or_core};
use clippy_utils::{ExprUseNode, get_expr_use_site, is_expr_temporary_value, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
@@ -22,13 +22,12 @@ pub(super) fn check<'tcx>(
if matches!(cast_from.kind(), ty::Ref(..))
&& let ty::RawPtr(_, to_mutbl) = cast_to.kind()
&& let use_cx = expr_use_ctxt(cx, expr)
&& let Some(std_or_core) = std_or_core(cx)
{
if let ExprKind::AddrOf(_, _, addr_inner) = cast_expr.kind
&& is_expr_temporary_value(cx, addr_inner)
&& matches!(
use_cx.use_node(cx),
get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx),
ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)
)
{
+347 -2
View File
@@ -8,10 +8,10 @@
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, FnRetTy, Lit, Node, Path, QPath, TyKind, UnOp};
use rustc_hir::{Expr, ExprKind, FnRetTy, HirId, Lit, Node, Path, QPath, TyKind, UnOp};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
use rustc_middle::ty::{self, FloatTy, GenericArg, InferTy, Ty};
use rustc_span::Symbol;
use std::ops::ControlFlow;
@@ -173,6 +173,16 @@ fn is_in_allowed_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
cx.tcx.get_diagnostic_name(def_id).is_some_and(|sym| ALLOWED_MACROS.contains(&sym)))
}
// Removing the cast here can change inference along the path to an outer
// method receiver, so avoid linting in that case.
if is_inference_sensitive_inner_expr(cx, cast_expr)
&& contains_unsuffixed_numeric_literal(cast_expr)
&& feeds_outer_method_receiver(cx, expr)
&& has_lint_blocking_context_on_receiver_path(cx, expr)
{
return false;
}
if let Some(id) = cast_expr.res_local_id()
&& !cx.tcx.hir_span(id).eq_ctxt(cast_expr.span)
{
@@ -340,3 +350,338 @@ fn emit_lint(
applicability,
);
}
fn contains_unsuffixed_numeric_literal<'e>(expr: &'e Expr<'e>) -> bool {
for_each_expr_without_closures(expr, |e| {
if let Some(lit) = get_numeric_literal(e)
&& matches!(
lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
)
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
})
.is_some()
}
// Returns `true` for expressions whose resolved type or method depends on inference.
fn is_inference_sensitive_inner_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Index(..) => cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.and_then(|def_id| cx.tcx.opt_associated_item(def_id))
.is_some_and(|assoc| assoc.trait_container(cx.tcx).is_some()),
_ => false,
}
}
// Returns `true` if the function's output type contains a type parameter
// originating from the selected input.
fn output_depends_on_input_param(cx: &LateContext<'_>, def_id: rustc_hir::def_id::DefId, input_index: usize) -> bool {
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
let Some(input_ty) = sig.inputs().get(input_index) else {
return false;
};
let output_ty = sig.output();
input_ty.walk().filter_map(GenericArg::as_type).any(|input_part| {
match input_part.kind() {
ty::Param(input_param) => output_ty
.walk()
.filter_map(GenericArg::as_type)
.any(|output_part| {
matches!(output_part.kind(), ty::Param(output_param) if output_param.index == input_param.index)
}),
_ => false,
}
})
}
// Returns `true` if the generic arguments include at least one explicit type or const
// argument and none of the provided generic arguments are placeholders like `::<_>`.
fn has_explicit_type_or_const_args(args: Option<&rustc_hir::GenericArgs<'_>>) -> bool {
let Some(args) = args else {
return false;
};
let mut has_explicit = false;
for arg in args.args {
match arg {
rustc_hir::GenericArg::Type(_) | rustc_hir::GenericArg::Const(_) => {
has_explicit = true;
},
rustc_hir::GenericArg::Infer(_) => return false,
rustc_hir::GenericArg::Lifetime(_) => {},
}
}
has_explicit
}
// Controls whether the receiver path walk is looking for an outer method
// receiver or for a context where linting should stop.
#[derive(Copy, Clone)]
enum ReceiverPathMode {
FindReceiver,
FindLintBlockingContext,
}
enum ReceiverPathResult {
Continue(HirId),
Stop(bool),
}
fn stop_if_lint_blocking_else_continue(parent_hir_id: HirId, mode: ReceiverPathMode) -> ReceiverPathResult {
if matches!(mode, ReceiverPathMode::FindLintBlockingContext) {
ReceiverPathResult::Stop(true)
} else {
ReceiverPathResult::Continue(parent_hir_id)
}
}
fn walk_receiver_path_method_call(
cx: &LateContext<'_>,
current_hir_id: HirId,
parent: &Expr<'_>,
segment: &rustc_hir::PathSegment<'_>,
receiver: &Expr<'_>,
args: &[Expr<'_>],
mode: ReceiverPathMode,
) -> ReceiverPathResult {
if receiver.hir_id == current_hir_id {
return if matches!(mode, ReceiverPathMode::FindLintBlockingContext) {
ReceiverPathResult::Continue(parent.hir_id)
} else {
ReceiverPathResult::Stop(true)
};
}
let Some(arg_index) = args.iter().position(|arg| arg.hir_id == current_hir_id) else {
return ReceiverPathResult::Stop(false);
};
let passthrough = !has_explicit_type_or_const_args(segment.args)
&& cx
.typeck_results()
.type_dependent_def_id(parent.hir_id)
.is_some_and(|def_id| output_depends_on_input_param(cx, def_id, arg_index + 1));
if matches!(mode, ReceiverPathMode::FindLintBlockingContext) {
if passthrough
|| args.iter().any(|arg| {
arg.hir_id != current_hir_id
&& get_numeric_literal(arg).is_none()
&& !cx.typeck_results().expr_ty(arg).is_primitive()
})
{
ReceiverPathResult::Stop(true)
} else {
ReceiverPathResult::Continue(parent.hir_id)
}
} else if passthrough {
ReceiverPathResult::Continue(parent.hir_id)
} else {
ReceiverPathResult::Stop(false)
}
}
fn walk_receiver_path_call(
cx: &LateContext<'_>,
current_hir_id: HirId,
parent: &Expr<'_>,
callee: &Expr<'_>,
args: &[Expr<'_>],
mode: ReceiverPathMode,
) -> ReceiverPathResult {
if callee.hir_id == current_hir_id {
return ReceiverPathResult::Continue(parent.hir_id);
}
let Some(arg_index) = args.iter().position(|arg| arg.hir_id == current_hir_id) else {
return ReceiverPathResult::Stop(false);
};
let passthrough = if let ExprKind::Path(qpath) = callee.kind
&& let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id)
{
let has_explicit_args = match &qpath {
QPath::Resolved(_, path) => path
.segments
.last()
.is_some_and(|seg| has_explicit_type_or_const_args(seg.args)),
QPath::TypeRelative(_, segment) => has_explicit_type_or_const_args(segment.args),
};
!has_explicit_args && output_depends_on_input_param(cx, def_id, arg_index)
} else {
false
};
if matches!(mode, ReceiverPathMode::FindLintBlockingContext) {
ReceiverPathResult::Stop(passthrough)
} else if passthrough {
ReceiverPathResult::Continue(parent.hir_id)
} else {
ReceiverPathResult::Stop(false)
}
}
// Walk one step up the receiver path for the current mode.
fn walk_receiver_path_step(cx: &LateContext<'_>, current_hir_id: HirId, mode: ReceiverPathMode) -> ReceiverPathResult {
match cx.tcx.parent_hir_node(current_hir_id) {
Node::Expr(parent) => match parent.kind {
// Main case.
// The current node may be the receiver.
// Or it may flow through a passthrough method.
ExprKind::MethodCall(segment, receiver, args, _) => {
walk_receiver_path_method_call(cx, current_hir_id, parent, segment, receiver, args, mode)
},
// Regular calls only keep the path alive
// if the output still depends on this input.
ExprKind::Call(callee, args) => walk_receiver_path_call(cx, current_hir_id, parent, callee, args, mode),
// A sibling that is not primitive blocks the lint.
ExprKind::Binary(_, left, right) | ExprKind::Index(left, right, _)
if left.hir_id == current_hir_id || right.hir_id == current_hir_id =>
{
if matches!(mode, ReceiverPathMode::FindLintBlockingContext) {
let sibling = if left.hir_id == current_hir_id { right } else { left };
if get_numeric_literal(sibling).is_none() && !cx.typeck_results().expr_ty(sibling).is_primitive() {
ReceiverPathResult::Stop(true)
} else {
ReceiverPathResult::Continue(parent.hir_id)
}
} else {
ReceiverPathResult::Continue(parent.hir_id)
}
},
// These expressions don't block the lint, so we continue walking up the path.
ExprKind::Unary(_, inner)
| ExprKind::Cast(inner, _)
| ExprKind::AddrOf(_, _, inner)
| ExprKind::Field(inner, _)
| ExprKind::DropTemps(inner)
if inner.hir_id == current_hir_id =>
{
ReceiverPathResult::Continue(parent.hir_id)
},
// A block can forward its tail expression, so we keep walking through it.
ExprKind::Block(block, _)
if block.hir_id == current_hir_id || block.expr.is_some_and(|tail| tail.hir_id == current_hir_id) =>
{
ReceiverPathResult::Continue(parent.hir_id)
},
// Depending on the mode, either keep walking or block the lint.
ExprKind::Loop(block, ..) if block.hir_id == current_hir_id => {
stop_if_lint_blocking_else_continue(parent.hir_id, mode)
},
// Tuples and arrays wrap the current expression, so we continue walking up the path.
ExprKind::Tup(exprs) | ExprKind::Array(exprs) if exprs.iter().any(|e| e.hir_id == current_hir_id) => {
ReceiverPathResult::Continue(parent.hir_id)
},
// The expression is stored in a field. We continue walking up the path to see how the struct is used.
ExprKind::Struct(_, fields, _)
if fields
.iter()
.any(|field| field.hir_id == current_hir_id || field.expr.hir_id == current_hir_id) =>
{
ReceiverPathResult::Continue(parent.hir_id)
},
// Depending on the mode, either keep walking or block the lint.
ExprKind::If(cond, then_expr, else_expr)
if cond.hir_id == current_hir_id
|| then_expr.hir_id == current_hir_id
|| else_expr.is_some_and(|else_expr| else_expr.hir_id == current_hir_id) =>
{
stop_if_lint_blocking_else_continue(parent.hir_id, mode)
},
// Depending on the mode, either keep walking or block the lint.
ExprKind::Match(scrutinee, arms, _)
if scrutinee.hir_id == current_hir_id
|| arms
.iter()
.any(|arm| arm.hir_id == current_hir_id || arm.body.hir_id == current_hir_id) =>
{
stop_if_lint_blocking_else_continue(parent.hir_id, mode)
},
// Depending on the mode, either keep walking or block the lint.
ExprKind::Break(_, Some(inner)) if inner.hir_id == current_hir_id => {
stop_if_lint_blocking_else_continue(parent.hir_id, mode)
},
_ => ReceiverPathResult::Stop(false),
},
// These are structural HIR nodes. We just skip them and keep walking.
Node::ExprField(_) | Node::Block(_) | Node::Arm(_) | Node::Stmt(_) => {
ReceiverPathResult::Continue(cx.tcx.parent_hir_id(current_hir_id))
},
// Handle `let x = init; x` in the same block.
// Depending on the mode, either keep walking init or block the lint.
Node::LetStmt(local) => {
if let Some(block_hir_id) = let_init_to_block_hir_id(cx, local, current_hir_id) {
stop_if_lint_blocking_else_continue(block_hir_id, mode)
} else {
ReceiverPathResult::Stop(false)
}
},
_ => ReceiverPathResult::Stop(false),
}
}
// Returns `true` if `expr` eventually becomes the receiver of an outer method call.
fn feeds_outer_method_receiver(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let mut current_hir_id = expr.hir_id;
loop {
match walk_receiver_path_step(cx, current_hir_id, ReceiverPathMode::FindReceiver) {
ReceiverPathResult::Continue(next) => current_hir_id = next,
ReceiverPathResult::Stop(result) => return result,
}
}
}
// Returns `true` if the receiver path contains a context that should block the lint.
fn has_lint_blocking_context_on_receiver_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let mut current_hir_id = expr.hir_id;
loop {
match walk_receiver_path_step(cx, current_hir_id, ReceiverPathMode::FindLintBlockingContext) {
ReceiverPathResult::Continue(next) => current_hir_id = next,
ReceiverPathResult::Stop(result) => return result,
}
}
}
// If the initializer flows into the tail expression of the same block, returns that block HirId.
fn let_init_to_block_hir_id(
cx: &LateContext<'_>,
local: &rustc_hir::LetStmt<'_>,
current_hir_id: HirId,
) -> Option<HirId> {
let init = local.init?;
if init.hir_id != current_hir_id {
return None;
}
let stmt_hir_id = match cx.tcx.parent_hir_node(local.hir_id) {
Node::Stmt(stmt) => stmt.hir_id,
_ => return None,
};
let Node::Block(block) = cx.tcx.parent_hir_node(stmt_hir_id) else {
return None;
};
let tail = block.expr?;
let binding_hir_id = tail.res_local_id()?;
match local.pat.kind {
rustc_hir::PatKind::Binding(_, local_hir_id, ..) if local_hir_id == binding_hir_id => Some(block.hir_id),
_ => None,
}
}
+1 -1
View File
@@ -145,7 +145,7 @@ fn check_fn(
def_id: LocalDefId,
) {
#[allow(deprecated)]
if !cx.tcx.get_attrs(def_id, sym::test).next().is_some() {
if cx.tcx.get_attrs(def_id, sym::test).next().is_none() {
let expr = if kind.asyncness().is_async() {
match get_async_fn_body(cx.tcx, body) {
Some(b) => b,
+15 -15
View File
@@ -4,7 +4,7 @@
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs};
use clippy_utils::{
DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym,
DefinedTy, ExprUseNode, get_expr_use_site, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed, sym,
};
use rustc_ast::util::parser::ExprPrecedence;
use rustc_data_structures::fx::FxIndexMap;
@@ -19,7 +19,7 @@
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypeckResults};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, Symbol};
use rustc_span::{Span, Symbol, SyntaxContext};
use std::borrow::Cow;
declare_clippy_lint! {
@@ -276,15 +276,15 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
match (self.state.take(), kind) {
(None, kind) => {
let expr_ty = typeck.expr_ty(expr);
let use_cx = expr_use_ctxt(cx, expr);
let adjusted_ty = use_cx.adjustments.last().map_or(expr_ty, |a| a.target);
let use_site = get_expr_use_site(cx.tcx, typeck, SyntaxContext::root(), expr);
let adjusted_ty = use_site.adjustments.last().map_or(expr_ty, |a| a.target);
match kind {
RefOp::Deref if use_cx.same_ctxt => {
let use_node = use_cx.use_node(cx);
RefOp::Deref if use_site.same_ctxt => {
let use_node = use_site.use_node(cx);
let sub_ty = typeck.expr_ty(sub_expr);
if let ExprUseNode::FieldAccess(name) = use_node
&& !use_cx.moved_before_use
&& !use_site.moved_before_use
&& !ty_contains_field(sub_ty, name.name)
{
self.state = Some((
@@ -331,9 +331,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
},
));
},
RefOp::AddrOf(mutability) if use_cx.same_ctxt => {
RefOp::AddrOf(mutability) if use_site.same_ctxt => {
// Find the number of times the borrow is auto-derefed.
let mut iter = use_cx.adjustments.iter();
let mut iter = use_site.adjustments.iter();
let mut deref_count = 0usize;
let next_adjust = loop {
match iter.next() {
@@ -350,13 +350,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
}
};
let use_node = use_cx.use_node(cx);
let use_node = use_site.use_node(cx);
let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| {
TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return())
});
let can_auto_borrow = match use_node {
ExprUseNode::FieldAccess(_)
if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
if !use_site.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) =>
{
// `DerefMut` will not be automatically applied to `ManuallyDrop<_>`
// field expressions when the base type is a union and the parent
@@ -364,10 +364,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
//
// e.g. `&mut x.y.z` where `x` is a union, and accessing `z` requires a
// deref through `ManuallyDrop<_>` will not compile.
!adjust_derefs_manually_drop(use_cx.adjustments, expr_ty)
!adjust_derefs_manually_drop(use_site.adjustments, expr_ty)
},
ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true,
ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => {
ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_site.moved_before_use => true,
ExprUseNode::MethodArg(hir_id, _, 0) if !use_site.moved_before_use => {
// Check for calls to trait methods where the trait is implemented
// on a reference.
// Two cases need to be handled:
@@ -455,7 +455,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
msg,
stability,
for_field_access: if let ExprUseNode::FieldAccess(name) = use_node
&& !use_cx.moved_before_use
&& !use_site.moved_before_use
{
Some(name.name)
} else {
@@ -85,8 +85,5 @@ fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) ->
.upcast(tcx)
}),
)));
ty::TypingEnv::new(
param_env,
ty::TypingMode::non_body_analysis(),
)
ty::TypingEnv::new(param_env, ty::TypingMode::non_body_analysis())
}
+1 -1
View File
@@ -50,7 +50,7 @@
/// let range = Range { start: 0, end: 1 };
/// println!("{}", range.end); // `end` is _not_ disallowed in the config.
/// ```
#[clippy::version = "1.93.0"]
#[clippy::version = "1.95.0"]
pub DISALLOWED_FIELDS,
style,
"declaration of a disallowed field use"
+5 -2
View File
@@ -1,6 +1,6 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal};
use clippy_utils::{ExprUseNode, get_expr_use_site, numeric_literal};
use rustc_ast::ast::{LitFloatType, LitKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
@@ -143,7 +143,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
}
} else if digits > max as usize && count_digits(&float_str) < digits {
if digits >= self.const_literal_digits_threshold
&& matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_))
&& matches!(
get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx),
ExprUseNode::ConstStatic(_)
)
{
// If a big enough number of digits is specified and it's a constant
// we assume the user is definining a constant, and excessive precision is ok
+26 -24
View File
@@ -3,7 +3,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::{Sugg, make_binop};
use clippy_utils::{
SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, is_integer_literal_untyped,
@@ -246,33 +246,35 @@ fn check_subtraction(
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if !is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST) {
let mut applicability = Applicability::MachineApplicable;
let big_expr_sugg = (if is_integer_literal_untyped(big_expr) {
let get_snippet = |span: Span| {
let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
let big_expr_ty = cx.typeck_results().expr_ty(big_expr);
Cow::Owned(format!("{snippet}_{big_expr_ty}"))
};
Sugg::hir_from_snippet(cx, big_expr, get_snippet)
} else {
Sugg::hir_with_applicability(cx, big_expr, "..", &mut applicability)
})
.maybe_paren();
let little_expr_sugg = Sugg::hir_with_applicability(cx, little_expr, "..", &mut applicability);
let sugg = format!(
"{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}",
if is_composited { "{ " } else { "" },
if is_composited { " }" } else { "" }
);
span_lint_and_sugg(
span_lint_and_then(
cx,
IMPLICIT_SATURATING_SUB,
expr_span,
"manual arithmetic check found",
"replace it with",
sugg,
applicability,
|diag| {
let mut applicability = Applicability::MachineApplicable;
let expr_span_ctxt = expr_span.ctxt();
let big_expr_sugg = (if is_integer_literal_untyped(big_expr) {
let get_snippet = |span: Span| {
let (snippet, _) =
snippet_with_context(cx, span, expr_span_ctxt, "..", &mut applicability);
let big_expr_ty = cx.typeck_results().expr_ty(big_expr);
Cow::Owned(format!("{snippet}_{big_expr_ty}"))
};
Sugg::hir_from_snippet(cx, big_expr, get_snippet)
} else {
Sugg::hir_with_context(cx, big_expr, expr_span_ctxt, "..", &mut applicability)
})
.maybe_paren();
let little_expr_sugg =
Sugg::hir_with_context(cx, little_expr, expr_span_ctxt, "..", &mut applicability);
let sugg = format!(
"{}{big_expr_sugg}.saturating_sub({little_expr_sugg}){}",
if is_composited { "{ " } else { "" },
if is_composited { " }" } else { "" }
);
diag.span_suggestion(expr_span, "replace it with", sugg, applicability);
},
);
}
} else if eq_expr_value(cx, left, little_expr)
+9 -1
View File
@@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
use rustc_ast::util::parser::AssocOp;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@@ -134,12 +135,19 @@ fn emit_warning(cx: &EarlyContext<'_>, expr: &Expr, new_lhs: &Expr, le_or_ge: Le
|diag| {
let mut app = Applicability::MachineApplicable;
let ctxt = expr.span.ctxt();
let new_lhs = sugg::Sugg::ast(cx, new_lhs, "_", ctxt, &mut app);
let mut new_lhs = sugg::Sugg::ast(cx, new_lhs, "_", ctxt, &mut app);
let new_rhs = sugg::Sugg::ast(cx, new_rhs, "_", ctxt, &mut app);
let new_binop = match le_or_ge {
LeOrGe::Ge => BinOpKind::Gt,
LeOrGe::Le => BinOpKind::Lt,
};
// When the replacement operator is `<`, an `as` cast on the LHS
// must be parenthesized. Otherwise, the parser interprets the `<`
// as the start of generic arguments on the cast type
// (e.g., `x as usize < y` is parsed as `x as usize<y>`).
if matches!(new_lhs, sugg::Sugg::BinOp(AssocOp::Cast, ..)) && new_binop == BinOpKind::Lt {
new_lhs = new_lhs.maybe_paren();
}
let rec = sugg::make_binop(new_binop, &new_lhs, &new_rhs);
diag.span_suggestion(expr.span, "change it to", rec, app);
},
+2 -2
View File
@@ -515,7 +515,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|| Box::new(visibility::Visibility)),
Box::new(|| Box::new(multiple_bound_locations::MultipleBoundLocations)),
Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)),
Box::new(|| Box::new(byte_char_slices::ByteCharSlice)),
Box::new(|| Box::new(cfg_not_test::CfgNotTest)),
Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())),
// add early passes here, used by `cargo dev new_lint`
@@ -866,7 +865,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))),
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))),
Box::new(|_| Box::new(manual_noop_waker::ManualNoopWaker)),
Box::new(move |_| Box::new(manual_noop_waker::ManualNoopWaker::new(conf))),
Box::new(|_| Box::new(byte_char_slices::ByteCharSlice)),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);
+1 -1
View File
@@ -51,7 +51,7 @@ fn build_sugg(&self, cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>)
}
let mut applicability = Applicability::MachineApplicable;
let snippet = Sugg::hir_with_applicability(cx, receiver, "_", &mut applicability);
let snippet = Sugg::hir_with_context(cx, receiver, expr.span.ctxt(), "_", &mut applicability);
span_lint_and_sugg(
cx,
+15 -2
View File
@@ -1,8 +1,10 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{is_empty_block, sym};
use rustc_hir::{ImplItemKind, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
@@ -35,7 +37,17 @@
"manual implementations of noop wakers can be simplified using Waker::noop()"
}
declare_lint_pass!(ManualNoopWaker => [MANUAL_NOOP_WAKER]);
impl_lint_pass!(ManualNoopWaker => [MANUAL_NOOP_WAKER]);
pub struct ManualNoopWaker {
msrv: Msrv,
}
impl ManualNoopWaker {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for ManualNoopWaker {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
@@ -43,6 +55,7 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
&& let Some(trait_ref) = imp.of_trait
&& let Some(trait_id) = trait_ref.trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Wake, trait_id)
&& self.msrv.meets(cx, msrvs::WAKER_NOOP)
{
for impl_item_ref in imp.items {
let impl_item = cx
+14 -8
View File
@@ -1,7 +1,7 @@
use std::fmt::Display;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
@@ -116,17 +116,23 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
}
};
let mut applicability = Applicability::MachineApplicable;
let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_paren();
let amount = sugg::Sugg::hir_with_applicability(cx, amount, "_", &mut applicability);
span_lint_and_sugg(
span_lint_and_then(
cx,
MANUAL_ROTATE,
expr.span,
"there is no need to manually implement bit rotation",
"this expression can be rewritten as",
format!("{expr_sugg}.{shift_function}({amount})"),
Applicability::MachineApplicable,
|diag| {
let mut applicability = Applicability::MachineApplicable;
let expr_sugg = sugg::Sugg::hir_with_context(cx, l_expr, expr.span.ctxt(), "_", &mut applicability)
.maybe_paren();
let amount = sugg::Sugg::hir_with_context(cx, amount, expr.span.ctxt(), "_", &mut applicability);
diag.span_suggestion(
expr.span,
"this expression can be rewritten as",
format!("{expr_sugg}.{shift_function}({amount})"),
applicability,
);
},
);
}
}
+1 -1
View File
@@ -33,7 +33,7 @@
/// let mut x = true;
/// let _ = std::mem::take(&mut x);
/// ```
#[clippy::version = "1.94.0"]
#[clippy::version = "1.95.0"]
pub MANUAL_TAKE,
complexity,
"manual `mem::take` implementation"
+68 -5
View File
@@ -1,12 +1,15 @@
use clippy_utils::as_some_expr;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath};
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_copy;
use clippy_utils::visitors::contains_unsafe_block;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_span::{SyntaxContext, sym};
use rustc_span::{Span, SyntaxContext, sym};
use super::MANUAL_FILTER;
use super::manual_utils::{SomeExpr, check_with};
@@ -21,8 +24,8 @@ fn get_cond_expr<'tcx>(
expr: &'tcx Expr<'_>,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr)
&& let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind
let block_expr = peels_blocks_incl_unsafe(expr);
if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind
&& let PatKind::Binding(_, target, ..) = pat.kind
&& (is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr))
@@ -89,6 +92,66 @@ fn add_ampersand_if_copy(body_str: String, has_copy_trait: bool) -> String {
}
}
/// Checks for the following pattern:
/// `opt.and_then(|x| if /* predicate on x */ { Some(x) } else { None })`
/// and suggests replacing with:
/// `opt.filter(|&x| /* predicate on x */ )`
pub(crate) fn check_and_then_method<'tcx>(
cx: &LateContext<'tcx>,
scrutinee: &'tcx Expr<'_>,
arg: &'tcx Expr<'_>,
call_span: Span,
expr: &'tcx Expr<'_>,
) {
let ty = cx.typeck_results().expr_ty(scrutinee);
if ty.is_diag_item(cx, sym::Option)
&& let ExprKind::Closure(closure) = arg.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let Some(fn_arg_span) = closure.fn_arg_span
&& let [param] = body.params
&& let expr_span_ctxt = expr.span.ctxt()
&& let Some(some_expr) = get_cond_expr(cx, param.pat, body.value, expr_span_ctxt)
{
span_lint_and_then(
cx,
MANUAL_FILTER,
call_span,
"manual implementation of `Option::filter`",
|diag| {
let mut applicability = Applicability::MachineApplicable;
let mut cond_snip =
Sugg::hir_with_context(cx, some_expr.expr, expr_span_ctxt, "..", &mut applicability);
if some_expr.needs_unsafe_block {
cond_snip = cond_snip.unsafeify();
}
if some_expr.needs_negated {
cond_snip = !cond_snip;
}
let (prefix_snip, _) = snippet_with_context(
cx,
closure.fn_decl_span.until(fn_arg_span),
expr_span_ctxt,
"..",
&mut applicability,
);
let (param_snip, _) =
snippet_with_context(cx, param.pat.span, expr_span_ctxt, "..", &mut applicability);
diag.span_suggestion(
call_span,
"try",
format!(
"filter({prefix_snip}|{}{param_snip}| {cond_snip})",
if is_copy(cx, ty) { "&" } else { "" }
),
applicability,
);
},
);
}
}
pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>,
scrutinee: &'tcx Expr<'_>,
+17 -21
View File
@@ -87,28 +87,24 @@ pub(super) fn check_with<'tcx, F>(
None => "",
};
match can_move_expr_to_closure(cx, some_expr.expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Use | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return None;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
let captures = can_move_expr_to_closure(cx, some_expr.expr)?;
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Use | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return None;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
},
None => return None,
}
}
let mut app = Applicability::MachineApplicable;
+1 -1
View File
@@ -1,6 +1,6 @@
mod collapsible_match;
mod infallible_destructuring_match;
mod manual_filter;
pub(crate) mod manual_filter;
mod manual_map;
mod manual_ok_err;
mod manual_unwrap_or;
+7 -1
View File
@@ -75,7 +75,13 @@ fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Ex
let mut arg_root = peel_blocks(arg);
loop {
arg_root = match &arg_root.kind {
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr,
hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => {
let expr_ty = cx.typeck_results().expr_ty(expr);
if expr_ty.is_str() {
break;
}
expr
},
hir::ExprKind::MethodCall(method_name, receiver, [], ..) => {
if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && {
let arg_type = cx.typeck_results().expr_ty(receiver);
@@ -35,13 +35,12 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) ->
if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() {
// For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..)
len.try_to_target_usize(cx.tcx).map(u128::from)
} else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) {
} else {
let args = VecArgs::hir(cx, expr_or_init(cx, recv))?;
match args {
VecArgs::Vec(vec) => vec.len().try_into().ok(),
VecArgs::Repeat(_, len) => expr_as_u128(cx, len),
}
} else {
None
}
},
Some(sym::IterEmpty) => Some(0),
+6 -6
View File
@@ -4,13 +4,13 @@
use clippy_utils::source::{IntoSpan, SpanRangeExt};
use clippy_utils::ty::get_field_by_name;
use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures};
use clippy_utils::{ExprUseNode, expr_use_ctxt, sym};
use clippy_utils::{ExprUseNode, get_expr_use_site, sym};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_span::{DUMMY_SP, Span, Symbol};
use rustc_span::{DUMMY_SP, Span, Symbol, SyntaxContext};
use super::MANUAL_INSPECT;
@@ -49,7 +49,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
// Nested closures don't need to treat returns specially.
let _: Option<!> = for_each_expr(cx, cx.tcx.hir_body(c.body).value, |e| {
if e.res_local_id() == Some(arg_id) {
let (kind, same_ctxt) = check_use(cx, e);
let (kind, same_ctxt) = check_use(cx, ctxt, e);
match (kind, same_ctxt && e.span.ctxt() == ctxt) {
(_, false) | (UseKind::Deref | UseKind::Return(..), true) => {
requires_copy = true;
@@ -67,7 +67,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name:
} else if matches!(e.kind, ExprKind::Ret(_)) {
ret_count += 1;
} else if e.res_local_id() == Some(arg_id) {
let (kind, same_ctxt) = check_use(cx, e);
let (kind, same_ctxt) = check_use(cx, ctxt, e);
match (kind, same_ctxt && e.span.ctxt() == ctxt) {
(UseKind::Return(..), false) => {
return ControlFlow::Break(());
@@ -209,8 +209,8 @@ enum UseKind<'tcx> {
}
/// Checks how the value is used, and whether it was used in the same `SyntaxContext`.
fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) {
let use_cx = expr_use_ctxt(cx, e);
fn check_use<'tcx>(cx: &LateContext<'tcx>, ctxt: SyntaxContext, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) {
let use_cx = get_expr_use_site(cx.tcx, cx.typeck_results(), ctxt, e);
if use_cx
.adjustments
.first()
+2 -2
View File
@@ -2,7 +2,7 @@
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::{expr_use_ctxt, fn_def_id, std_or_core, sym};
use clippy_utils::{fn_def_id, get_expr_use_site, std_or_core, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
@@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
&& let ExprKind::Call(_, [repeat_arg]) = repeat_expr.kind
&& let Some(def_id) = fn_def_id(cx, repeat_expr)
&& cx.tcx.is_diagnostic_item(sym::iter_repeat, def_id)
&& !expr_use_ctxt(cx, expr).is_ty_unified
&& !get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).is_ty_unified
&& let Some(std_or_core) = std_or_core(cx)
&& msrv.meets(cx, msrvs::REPEAT_N)
{
@@ -1,7 +1,7 @@
use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::{eager_or_lazy, higher, std_or_core, usage};
use rustc_ast::LitKind;
@@ -10,12 +10,13 @@
use rustc_errors::Applicability;
use rustc_hir::{Body, Closure, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
fn extract_count_with_applicability(
cx: &LateContext<'_>,
range: higher::Range<'_>,
applicability: &mut Applicability,
ctxt: SyntaxContext,
) -> Option<String> {
let start = range.start?;
let end = range.end?;
@@ -40,7 +41,7 @@ fn extract_count_with_applicability(
};
return Some(format!("{count}"));
}
let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability)
let end_snippet = Sugg::hir_with_context(cx, end, ctxt, "...", applicability)
.maybe_paren()
.into_string();
if lower_bound == 0 {
@@ -74,45 +75,23 @@ pub(super) fn check(
value: body_expr,
} = body_hir
&& !usage::BindingUsageFinder::are_params_used(cx, body_hir)
&& let Some(count) = extract_count_with_applicability(cx, range, &mut applicability)
&& let ctxt = ex.span.ctxt()
&& let Some(count) = extract_count_with_applicability(cx, range, &mut applicability, ctxt)
&& let Some(exec_context) = std_or_core(cx)
{
let method_to_use_name;
let new_span;
let use_take;
if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
let (method_to_use_name, new_span, use_take) = if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
if msrv.meets(cx, msrvs::REPEAT_N) {
method_to_use_name = "repeat_n";
let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
new_span = (arg.span, format!("{body_snippet}, {count}"));
use_take = false;
let (body_snippet, _) = snippet_with_context(cx, body_expr.span, ctxt, "..", &mut applicability);
("repeat_n", (arg.span, format!("{body_snippet}, {count}")), false)
} else {
method_to_use_name = "repeat";
let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability);
new_span = (arg.span, body_snippet.to_string());
use_take = true;
let (body_snippet, _) = snippet_with_context(cx, body_expr.span, ctxt, "..", &mut applicability);
("repeat", (arg.span, body_snippet.to_string()), true)
}
} else if msrv.meets(cx, msrvs::REPEAT_WITH) {
method_to_use_name = "repeat_with";
new_span = (param.span, String::new());
use_take = true;
("repeat_with", (param.span, String::new()), true)
} else {
return;
}
// We need to provide nonempty parts to diag.multipart_suggestion so we
// collate all our parts here and then remove those that are empty.
let mut parts = vec![
(
ex.span.with_hi(method_name_span.hi()),
format!("{exec_context}::iter::{method_to_use_name}"),
),
new_span,
];
if use_take {
parts.push((ex.span.shrink_to_hi(), format!(".take({count})")));
}
};
span_lint_and_then(
cx,
@@ -120,6 +99,19 @@ pub(super) fn check(
ex.span,
"map of a closure that does not depend on its parameter over a range",
|diag| {
// We need to provide nonempty parts to diag.multipart_suggestion so we
// collate all our parts here and then remove those that are empty.
let mut parts = vec![
(
ex.span.with_hi(method_name_span.hi()),
format!("{exec_context}::iter::{method_to_use_name}"),
),
new_span,
];
if use_take {
parts.push((ex.span.shrink_to_hi(), format!(".take({count})")));
}
diag.multipart_suggestion(
if use_take {
format!("remove the explicit range and use `{method_to_use_name}` and `take`")
+4
View File
@@ -163,6 +163,8 @@
use rustc_session::impl_lint_pass;
use rustc_span::{Span, Symbol};
use crate::matches::manual_filter;
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))`
@@ -5154,6 +5156,8 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
return_and_then::check(cx, expr, recv, arg);
}
}
manual_filter::check_and_then_method(cx, recv, arg, call_span, expr);
},
(sym::any, [arg]) => {
needless_character_iteration::check(cx, expr, recv, arg, false);
+6 -6
View File
@@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes};
use clippy_utils::source::snippet_with_context;
use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_blocks, strip_pat_refs};
use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_blocks, strip_pat_refs};
use rustc_ast::ast;
use rustc_data_structures::packed::Pu128;
use rustc_errors::{Applicability, Diag};
@@ -17,10 +17,10 @@
/// Do we need to suggest turbofish when suggesting a replacement method?
/// Changing `fold` to `sum` needs it sometimes when the return type can't be
/// inferred. This checks for some common cases where it can be safely omitted
fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) -> bool {
let use_cx = expr_use_ctxt(cx, expr);
if use_cx.same_ctxt
&& let use_node = use_cx.use_node(cx)
fn needs_turbofish<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) -> bool {
let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr);
if use_site.same_ctxt
&& let use_node = use_site.use_node(cx)
&& let Some(ty) = use_node.defined_ty(cx)
{
// some common cases where turbofish isn't needed:
@@ -209,7 +209,7 @@ fn check_fold_with_method(
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
init: &hir::Expr<'_>,
acc: &hir::Expr<'_>,
fold_span: Span,
+2 -2
View File
@@ -197,7 +197,7 @@
declare_clippy_lint! {
/// ### What it does
/// Checks for tuple patterns with a wildcard
/// Checks for tuple and struct patterns with a wildcard
/// pattern (`_`) is next to a rest pattern (`..`).
///
/// _NOTE_: While `_, ..` means there is at least one element left, `..`
@@ -231,7 +231,7 @@
#[clippy::version = "1.40.0"]
pub UNNEEDED_WILDCARD_PATTERN,
complexity,
"tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
"tuple and struct patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
}
declare_clippy_lint! {
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{Pat, PatKind};
use rustc_ast::ast::{Pat, PatFieldsRest, PatKind};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::Span;
@@ -32,6 +32,19 @@ pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
right_index == 0,
);
}
} else if let PatKind::Struct(_, _, patfields, rest) = &pat.kind
&& let PatFieldsRest::Rest(rspan) = rest
&& let Some((right_index, _right_pat)) = patfields
.iter()
.rev()
.take_while(|patfield| matches!(patfield.pat.kind, PatKind::Wild))
.enumerate()
.last()
{
// Unlike the tuples above, structs have patfields rather than patterns, and separate out the
// `..` into a separate parameter. Also, the `..` can only be at the end of the pattern.
let singlewild = patfields.len() - right_index - 1;
span_lint(cx, patfields[singlewild].span.until(*rspan), right_index == 0);
}
}
@@ -105,10 +105,6 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id)
&& adj
+1 -1
View File
@@ -112,7 +112,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
{
let reduce = |ret, not| {
let mut applicability = Applicability::MachineApplicable;
let snip = Sugg::hir_with_applicability(cx, cond, "<predicate>", &mut applicability);
let snip = Sugg::hir_with_context(cx, cond, e.span.ctxt(), "<predicate>", &mut applicability);
let mut snip = if not { !snip } else { snip };
if ret {
@@ -4,7 +4,7 @@
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{DefinedTy, ExprUseNode, expr_use_ctxt, peel_n_hir_expr_refs, sym};
use clippy_utils::{DefinedTy, ExprUseNode, get_expr_use_site, peel_n_hir_expr_refs, sym};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -17,6 +17,7 @@
self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty,
};
use rustc_session::impl_lint_pass;
use rustc_span::SyntaxContext;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
use rustc_trait_selection::traits::{Obligation, ObligationCause};
use std::collections::VecDeque;
@@ -82,10 +83,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if matches!(expr.kind, ExprKind::AddrOf(..))
&& !expr.span.from_expansion()
&& let use_cx = expr_use_ctxt(cx, expr)
&& use_cx.same_ctxt
&& !use_cx.is_ty_unified
&& let use_node = use_cx.use_node(cx)
&& let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), SyntaxContext::root(), expr)
&& use_site.same_ctxt
&& !use_site.is_ty_unified
&& let use_node = use_site.use_node(cx)
&& let Some(DefinedTy::Mir { def_site_def_id: _, ty }) = use_node.defined_ty(cx)
&& let ty::Param(param_ty) = *ty.skip_binder().kind()
&& let Some((hir_id, fn_id, i)) = match use_node {
+8 -1
View File
@@ -36,12 +36,19 @@ fn invert_cmp(cmp: BinOpKind) -> BinOpKind {
}
}
fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, cmp_value: u128, span: Span) {
fn check_compare<'a>(cx: &LateContext<'a>, bit_op: &Expr<'a>, cmp_op: BinOpKind, mut cmp_value: u128, span: Span) {
if let ExprKind::Binary(op, left, right) = &bit_op.kind {
if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr || is_from_proc_macro(cx, bit_op) {
return;
}
if let Some(mask) = fetch_int_literal(cx, right).or_else(|| fetch_int_literal(cx, left)) {
let ty = cx.typeck_results().expr_ty(bit_op);
if !ty.is_ptr_sized_integral()
&& let bits = ty.primitive_size(cx.tcx)
{
// Strip high bits that don't fit into the result type as they won't be used in the comparison
cmp_value &= bits.unsigned_int_max();
}
check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span);
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt, integer_const, is_zero_integer_const};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext};
use clippy_utils::{ExprUseNode, clip, get_expr_use_site, peel_hir_expr_refs, unsext};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath};
@@ -250,7 +250,7 @@ fn span_ineffective_operation(
}
fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
match expr_use_ctxt(cx, expr).use_node(cx) {
match get_expr_use_site(cx.tcx, cx.typeck_results(), expr.span.ctxt(), expr).use_node(cx) {
ExprUseNode::LetStmt(letstmt) => letstmt.ty.is_some(),
ExprUseNode::Return(_) => true,
_ => false,
@@ -148,7 +148,8 @@ fn build_suggestion(
rhs: &Expr<'_>,
applicability: &mut Applicability,
) {
let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_paren();
let ctxt = expr.span.ctxt();
let dividend_sugg = Sugg::hir_with_context(cx, lhs, ctxt, "..", applicability).maybe_paren();
let rhs_ty = cx.typeck_results().expr_ty(rhs);
let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric()
&& matches!(
@@ -186,7 +187,7 @@ fn build_suggestion(
};
// Dereference the RHS if it is a reference type
let divisor_snippet = match Sugg::hir_with_context(cx, rhs, expr.span.ctxt(), "_", applicability) {
let divisor_snippet = match Sugg::hir_with_context(cx, rhs, ctxt, "_", applicability) {
sugg if rhs_ty.is_ref() => sugg.deref(),
sugg => sugg,
};
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::source::snippet_with_context;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@@ -24,12 +24,12 @@ pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Exp
e.span,
"use of bitwise operator instead of lazy operator between booleans",
|diag| {
if let Some(lhs_snip) = lhs.span.get_source_text(cx)
&& let Some(rhs_snip) = rhs.span.get_source_text(cx)
{
let sugg = format!("{lhs_snip} {op_str} {rhs_snip}");
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
}
let mut applicability = Applicability::MachineApplicable;
let expr_span_ctxt = e.span.ctxt();
let (lhs_snip, _) = snippet_with_context(cx, lhs.span, expr_span_ctxt, "..", &mut applicability);
let (rhs_snip, _) = snippet_with_context(cx, rhs.span, expr_span_ctxt, "..", &mut applicability);
diag.span_suggestion(e.span, "try", format!("{lhs_snip} {op_str} {rhs_snip}"), applicability);
},
);
}
+116 -47
View File
@@ -2,10 +2,10 @@
use crate::question_mark_used::QUESTION_MARK_USED;
use clippy_config::Conf;
use clippy_config::types::MatchLintBehaviour;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::usage::local_used_after_expr;
@@ -24,6 +24,7 @@
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::symbol::Symbol;
declare_clippy_lint! {
@@ -222,13 +223,11 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_
// We only need to check `if let Some(x) = option` not `if let None = option`,
// because the later one will be suggested as `if option.is_none()` thus causing conflict.
res.ctor_parent(cx).is_lang_item(cx, OptionSome)
&& if_else.is_some()
&& expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None)
&& matches!(if_else, Some(inner) if expr_return_none_or_err(smbl, cx, inner, let_expr, None))
},
sym::Result => {
(res.ctor_parent(cx).is_lang_item(cx, ResultOk)
&& if_else.is_some()
&& expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym)))
&& matches!(if_else, Some(inner) if expr_return_none_or_err(smbl, cx, inner, let_expr, Some(let_pat_sym))))
|| res.ctor_parent(cx).is_lang_item(cx, ResultErr)
&& expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym))
&& if_else.is_none()
@@ -328,7 +327,7 @@ enum TryMode {
}
fn find_try_mode<'tcx>(cx: &LateContext<'tcx>, scrutinee: &Expr<'tcx>) -> Option<TryMode> {
let scrutinee_ty = cx.typeck_results().expr_ty_adjusted(scrutinee);
let scrutinee_ty = cx.typeck_results().expr_ty_adjusted(scrutinee).peel_refs();
let ty::Adt(scrutinee_adt_def, _) = scrutinee_ty.kind() else {
return None;
};
@@ -360,14 +359,18 @@ fn extract_ctor_call<'a, 'tcx>(
// Extracts the local ID of a plain `val` pattern.
fn extract_binding_pat(pat: &Pat<'_>) -> Option<HirId> {
if let PatKind::Binding(BindingMode::NONE, binding, _, None) = pat.kind {
if let PatKind::Binding(_, binding, _, None) = pat.kind {
Some(binding)
} else {
None
}
}
fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool {
fn check_arm_is_some_or_ok<'tcx>(
cx: &LateContext<'tcx>,
mode: TryMode,
arm: &Arm<'tcx>,
) -> Option<IfLetOrMatchThen<'tcx>> {
let happy_ctor = match mode {
TryMode::Result => ResultOk,
TryMode::Option => OptionSome,
@@ -378,13 +381,16 @@ fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Ar
&& let Some(val_binding) = extract_ctor_call(cx, happy_ctor, arm.pat)
// Extract out `val`
&& let Some(binding) = extract_binding_pat(val_binding)
// Check body is just `=> val`
&& peel_blocks(arm.body).res_local_id() == Some(binding)
{
true
} else {
false
// Check body is just `=> val`
return Some(if peel_blocks(arm.body).res_local_id() == Some(binding) {
IfLetOrMatchThen::DirectReturn
} else {
IfLetOrMatchThen::ManualUnwrap(val_binding.span, arm.body)
});
}
None
}
fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool {
@@ -439,9 +445,23 @@ fn is_local_or_local_into(cx: &LateContext<'_>, expr: &Expr<'_>, val: HirId) ->
}
}
fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool {
(check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2))
|| (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1))
fn check_arms_are_try<'tcx>(
cx: &LateContext<'tcx>,
mode: TryMode,
arm1: &Arm<'tcx>,
arm2: &Arm<'tcx>,
) -> Option<IfLetOrMatchThen<'tcx>> {
(check_arm_is_none_or_err(cx, mode, arm2).then(|| check_arm_is_some_or_ok(cx, mode, arm1)))
.or_else(|| check_arm_is_none_or_err(cx, mode, arm1).then(|| check_arm_is_some_or_ok(cx, mode, arm2)))
.flatten()
}
#[derive(Debug)]
enum IfLetOrMatchThen<'tcx> {
/// Return the binding from an if let or match arm as is.
DirectReturn,
/// Working on the binding from an if let or match arm as if it comes from a `?`.
ManualUnwrap(Span, &'tcx Expr<'tcx>),
}
fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
@@ -449,19 +469,47 @@ fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
&& !expr.span.from_expansion()
&& let Some(mode) = find_try_mode(cx, scrutinee)
&& !span_contains_cfg(cx, expr.span)
&& check_arms_are_try(cx, mode, arm1, arm2)
&& let Some(if_let_or_match_then) = check_arms_are_try(cx, mode, arm1, arm2)
{
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, scrutinee.span.source_callsite(), "..", &mut applicability);
span_lint_and_sugg(
span_lint_and_then(
cx,
QUESTION_MARK,
expr.span,
"this `match` expression can be replaced with `?`",
"try instead",
snippet.into_owned() + "?",
applicability,
|diag| {
let mut applicability = Applicability::MachineApplicable;
let scrutinee_snippet =
snippet_with_applicability(cx, scrutinee.span.source_callsite(), "..", &mut applicability);
match if_let_or_match_then {
IfLetOrMatchThen::DirectReturn => {
diag.span_suggestion(
expr.span,
"try instead",
scrutinee_snippet.into_owned() + "?",
applicability,
);
},
IfLetOrMatchThen::ManualUnwrap(binding_span, arm_body) => {
let indent = indent_of(cx, expr.span).unwrap_or_default();
let arm_body_snippet = snippet_with_applicability(cx, arm_body.span, "..", &mut applicability);
let mut sugg = reindent_multiline(&arm_body_snippet, true, Some(indent));
let binding_snippet = snippet_with_applicability(cx, binding_span, "..", &mut applicability);
let inner_indent = " ".repeat(indent + 4);
if matches!(arm_body.kind, ExprKind::Block(..)) {
sugg.insert_str(
1,
&format!("\n{inner_indent}let {binding_snippet} = {scrutinee_snippet}?;"),
);
} else {
let outer_indent = " ".repeat(indent);
sugg = format!(
"{{\n{inner_indent}let {binding_snippet} = {scrutinee_snippet}?;\n{inner_indent}{sugg}\n{outer_indent}}}"
);
}
diag.span_suggestion(expr.span, "try instead", sugg, applicability);
},
}
},
);
}
}
@@ -486,8 +534,8 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
if_then,
if_else,
)
&& ((is_early_return(sym::Option, cx, &if_block) && peel_blocks(if_then).res_local_id() == Some(bind_id))
|| is_early_return(sym::Result, cx, &if_block))
&& let is_option_early_return = is_early_return(sym::Option, cx, &if_block)
&& (is_option_early_return || is_early_return(sym::Result, cx, &if_block))
&& if_else
.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e)))
.is_none_or(|e| !e)
@@ -499,32 +547,53 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
return;
}
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let parent = cx.tcx.parent_hir_node(expr.hir_id);
let requires_semi = matches!(parent, Node::Stmt(_)) || cx.typeck_results().expr_ty(expr).is_unit();
let method_call_str = match by_ref {
ByRef::Yes(_, Mutability::Mut) => ".as_mut()",
ByRef::Yes(_, Mutability::Not) => ".as_ref()",
ByRef::No => "",
};
let mut sugg = format!(
"{receiver_str}{method_call_str}?{}",
if requires_semi { ";" } else { "" }
);
if is_else_clause(cx.tcx, expr) || (requires_semi && !matches!(parent, Node::Stmt(_) | Node::Block(_))) {
sugg = format!("{{ {sugg} }}");
// Leave `if let Some(x) = opt { .. } else { None }` to `needless_match` or `manual_map_option`.
if is_option_early_return
&& if_else.is_some_and(|else_| !matches!(peel_blocks_with_stmt(else_).kind, ExprKind::Ret(_)))
{
return;
}
span_lint_and_sugg(
span_lint_and_then(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
|diag| {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
if !is_option_early_return || peel_blocks(if_then).res_local_id() == Some(bind_id) {
let parent = cx.tcx.parent_hir_node(expr.hir_id);
let requires_semi = matches!(parent, Node::Stmt(_)) || cx.typeck_results().expr_ty(expr).is_unit();
let method_call_str = match by_ref {
ByRef::Yes(_, Mutability::Mut) => ".as_mut()",
ByRef::Yes(_, Mutability::Not) => ".as_ref()",
ByRef::No => "",
};
let mut sugg = format!(
"{receiver_str}{method_call_str}?{}",
if requires_semi { ";" } else { "" }
);
if is_else_clause(cx.tcx, expr)
|| (requires_semi && !matches!(parent, Node::Stmt(_) | Node::Block(_)))
{
sugg = format!("{{ {sugg} }}");
}
diag.span_suggestion(expr.span, "replace it with", sugg, applicability);
return;
}
let mut sugg = snippet_with_applicability(cx, if_then.span, "..", &mut applicability).into_owned();
let binding_snippet = snippet_with_applicability(cx, field.span, "..", &mut applicability);
let indent = indent_of(cx, expr.span).unwrap_or_default();
sugg.insert_str(
1,
&format!("\n{}let {binding_snippet} = {receiver_str}?;", " ".repeat(indent + 4)),
);
diag.span_suggestion(expr.span, "replace it with", sugg, applicability);
},
);
}
}
+11 -10
View File
@@ -6,7 +6,7 @@
use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::{expr_use_ctxt, fn_def_id, get_parent_expr, higher, is_in_const_context, is_integer_const, sym};
use clippy_utils::{fn_def_id, get_expr_use_site, get_parent_expr, higher, is_in_const_context, is_integer_const, sym};
use rustc_ast::Mutability;
use rustc_ast::ast::RangeLimits;
use rustc_errors::Applicability;
@@ -14,7 +14,7 @@
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::ty::{self, ClauseKind, GenericArgKind, PredicatePolarity, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::{DesugaringKind, Span, Spanned};
use rustc_span::{DesugaringKind, Span, Spanned, SyntaxContext};
use std::cmp::Ordering;
declare_clippy_lint! {
@@ -355,18 +355,19 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<R
/// check that the obligations are still satisfied after switching the range type.
fn can_switch_ranges<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
expr: &'tcx Expr<'_>,
original: RangeLimits,
inner_ty: Ty<'tcx>,
) -> bool {
let use_ctxt = expr_use_ctxt(cx, expr);
let (Node::Expr(parent_expr), false) = (use_ctxt.node, use_ctxt.is_ty_unified) else {
let use_site = get_expr_use_site(cx.tcx, cx.typeck_results(), ctxt, expr);
let (Node::Expr(parent_expr), false) = (use_site.node, use_site.is_ty_unified) else {
return false;
};
// Check if `expr` is the argument of a compiler-generated `IntoIter::into_iter(expr)`
if let ExprKind::Call(func, [arg]) = parent_expr.kind
&& arg.hir_id == use_ctxt.child_id
&& arg.hir_id == use_site.child_id
&& let ExprKind::Path(qpath) = func.kind
&& cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter)
&& parent_expr.span.is_desugaring(DesugaringKind::ForLoop)
@@ -377,7 +378,7 @@ fn can_switch_ranges<'tcx>(
// Check if `expr` is used as the receiver of a method of the `Iterator`, `IntoIterator`,
// or `RangeBounds` traits.
if let ExprKind::MethodCall(_, receiver, _, _) = parent_expr.kind
&& receiver.hir_id == use_ctxt.child_id
&& receiver.hir_id == use_site.child_id
&& let Some(method_did) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
&& let Some(trait_did) = cx.tcx.trait_of_assoc(method_did)
&& matches!(
@@ -392,7 +393,7 @@ fn can_switch_ranges<'tcx>(
// or `RangeBounds` trait.
if let ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) = parent_expr.kind
&& let Some(id) = fn_def_id(cx, parent_expr)
&& let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_ctxt.child_id)
&& let Some(arg_idx) = args.iter().position(|e| e.hir_id == use_site.child_id)
{
let input_idx = if matches!(parent_expr.kind, ExprKind::MethodCall(..)) {
arg_idx + 1
@@ -512,16 +513,16 @@ fn check_range_switch<'tcx>(
&& span.can_be_used_for_suggestions()
&& limits == kind
&& let Some(y) = predicate(cx, end)
&& can_switch_ranges(cx, expr, kind, cx.typeck_results().expr_ty(y))
&& can_switch_ranges(cx, span.ctxt(), expr, kind, cx.typeck_results().expr_ty(y))
{
span_lint_and_then(cx, lint, span, msg, |diag| {
let mut app = Applicability::MachineApplicable;
let start = start.map_or(String::new(), |x| {
Sugg::hir_with_applicability(cx, x, "<x>", &mut app)
Sugg::hir_with_context(cx, x, span.ctxt(), "<x>", &mut app)
.maybe_paren()
.to_string()
});
let end = Sugg::hir_with_applicability(cx, y, "<y>", &mut app).maybe_paren();
let end = Sugg::hir_with_context(cx, y, span.ctxt(), "<y>", &mut app).maybe_paren();
match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) {
Some(true) => {
diag.span_suggestion(span, "use", format!("({start}{operator}{end})"), app);
+23 -23
View File
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::res::MaybeResPath;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_non_whitespace};
@@ -19,6 +19,7 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>)
&& let Some(stmt) = block.stmts.last()
&& let StmtKind::Let(local) = &stmt.kind
&& local.ty.is_none()
&& local.els.is_none()
&& cx.tcx.hir_attrs(local.hir_id).is_empty()
&& let Some(initexpr) = &local.init
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
@@ -38,30 +39,29 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>)
|err| {
err.span_label(local.span, "unnecessary `let` binding");
if let Some(src) = initexpr.span.get_source_text(cx) {
let sugg = if binary_expr_needs_parentheses(initexpr) {
if has_enclosing_paren(&src) {
src.to_owned()
} else {
format!("({src})")
}
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
if has_enclosing_paren(&src) {
format!("{src} as _")
} else {
format!("({src}) as _")
}
let mut app = Applicability::MachineApplicable;
let (src, _) = snippet_with_context(cx, initexpr.span, local.span.ctxt(), "..", &mut app);
let sugg = if binary_expr_needs_parentheses(initexpr) {
if has_enclosing_paren(&src) {
src.to_string()
} else {
src.to_owned()
};
err.multipart_suggestion(
"return the expression directly",
vec![(local.span, String::new()), (retexpr.span, sugg)],
Applicability::MachineApplicable,
);
format!("({src})")
}
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
if has_enclosing_paren(&src) {
format!("{src} as _")
} else {
format!("({src}) as _")
}
} else {
err.span_help(initexpr.span, "this expression can be directly returned");
}
src.to_string()
};
err.multipart_suggestion(
"return the expression directly",
vec![(local.span, String::new()), (retexpr.span, sugg)],
app,
);
},
);
}
+1 -1
View File
@@ -259,7 +259,7 @@ fn emit_return_lint(
// Go backwards while encountering whitespace and extend the given Span to that point.
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
let ws = [b' ', b'\t', b'\n'];
let ws = *b" \t\n";
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
let len = prev_source.len() - non_ws_pos - 1;
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
+1 -1
View File
@@ -112,7 +112,7 @@ fn generate_swap_warning<'tcx>(
|| ty.is_diag_item(cx, sym::Vec)
|| ty.is_diag_item(cx, sym::VecDeque)
{
let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
let slice = Sugg::hir_with_context(cx, lhs1, ctxt, "<slice>", &mut applicability);
span_lint_and_sugg(
cx,
@@ -40,6 +40,9 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) {
match use_tree.kind {
UseTreeKind::Simple(Some(new_name)) => {
if new_name.as_str() == "_" {
return;
}
let old_name = use_tree
.prefix
.segments
-1
View File
@@ -211,7 +211,6 @@ fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(expr) = block.expr
&& let ExprKind::DropTemps(inner) = expr.kind
{
+8 -11
View File
@@ -308,18 +308,15 @@ fn extract_local(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> Option<Local> {
field_indices.push(field_idx);
expr = recv;
}
if let Some(local_id) = expr.res_local_id() {
if field_indices.is_empty() {
Some(Local::Pure { local_id })
} else {
Some(Local::WithFieldAccess {
local_id,
field_indices,
span,
})
}
let local_id = expr.res_local_id()?;
if field_indices.is_empty() {
Some(Local::Pure { local_id })
} else {
None
Some(Local::WithFieldAccess {
local_id,
field_indices,
span,
})
}
}
+17 -11
View File
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::MacroCall;
use clippy_utils::source::expand_past_previous_comma;
use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
use clippy_utils::{span_extract_comments, sym};
use rustc_ast::{FormatArgs, FormatArgsPiece};
use rustc_errors::Applicability;
@@ -23,17 +23,23 @@ pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call:
format!("empty string literal in `{name}!`"),
|diag| {
if span_extract_comments(cx, macro_call.span).is_empty() {
let closing_paren = cx.sess().source_map().span_extend_to_prev_char_before(
macro_call.span.shrink_to_hi(),
')',
false,
);
let mut span = format_args.span.with_hi(closing_paren.lo());
if is_writeln {
span = expand_past_previous_comma(cx, span);
}
let closing_delim = snippet_opt(cx, macro_call.span)
.and_then(|snippet| snippet.chars().last())
.filter(|ch| matches!(ch, ')' | ']' | '}'));
diag.span_suggestion(span, "remove the empty string", "", Applicability::MachineApplicable);
if let Some(closing_delim) = closing_delim {
let closing_paren = cx.sess().source_map().span_extend_to_prev_char_before(
macro_call.span.shrink_to_hi(),
closing_delim,
false,
);
let mut span = format_args.span.with_hi(closing_paren.lo());
if is_writeln {
span = expand_past_previous_comma(cx, span);
}
diag.span_suggestion(span, "remove the empty string", "", Applicability::MachineApplicable);
}
} else {
// If there is a comment in the span of macro call, we don't provide an auto-fix suggestion.
diag.span_note(format_args.span, "remove the empty string");
+215 -146
View File
@@ -1,6 +1,5 @@
#![feature(box_patterns)]
#![feature(macro_metavar_expr)]
#![feature(never_type)]
#![feature(rustc_private)]
#![feature(unwrap_infallible)]
#![recursion_limit = "512"]
@@ -94,11 +93,12 @@
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_hir::{
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring,
CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs,
HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId,
OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr,
self as hir, AnonConst, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, CRATE_HIR_ID, Closure, ConstArg,
ConstArgKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind,
FieldDef, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem,
LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path,
PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, Variant, def,
find_attr,
};
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -110,12 +110,12 @@
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, GenericArgKind, GenericArgsRef, IntTy, Ty, TyCtxt,
TypeFlags, TypeVisitableExt, UintTy, UpvarCapture,
TypeFlags, TypeVisitableExt, TypeckResults, UintTy, UpvarCapture,
};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{Ident, Symbol, kw};
use rustc_span::{InnerSpan, Span};
use rustc_span::{InnerSpan, Span, SyntaxContext};
use source::{SpanRangeExt, walk_span_to_context};
use visitors::{Visitable, for_each_unconsumed_temporary};
@@ -840,11 +840,10 @@ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
));
let mut child_id = e.hir_id;
let mut capture = CaptureKind::Value;
let mut capture_expr_ty = e;
for (parent_id, parent) in cx.tcx.hir_parent_iter(e.hir_id) {
for (parent, child_id) in hir_parent_with_src_iter(cx.tcx, e.hir_id) {
if let [
Adjustment {
kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
@@ -900,8 +899,6 @@ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
},
_ => break,
}
child_id = parent_id;
}
if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
@@ -1297,38 +1294,28 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
/// Checks if the given expression is a part of `let else`
/// returns `true` for both the `init` and the `else` part
pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
let mut child_id = expr.hir_id;
for (parent_id, node) in tcx.hir_parent_iter(child_id) {
if let Node::LetStmt(LetStmt {
init: Some(init),
els: Some(els),
..
}) = node
&& (init.hir_id == child_id || els.hir_id == child_id)
{
return true;
}
child_id = parent_id;
}
false
hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
matches!(
node,
Node::LetStmt(LetStmt {
init: Some(init),
els: Some(els),
..
})
if init.hir_id == child_id || els.hir_id == child_id
)
})
}
/// Checks if the given expression is the else clause of a `let else` expression
pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
let mut child_id = expr.hir_id;
for (parent_id, node) in tcx.hir_parent_iter(child_id) {
if let Node::LetStmt(LetStmt { els: Some(els), .. }) = node
&& els.hir_id == child_id
{
return true;
}
child_id = parent_id;
}
false
hir_parent_with_src_iter(tcx, expr.hir_id).any(|(node, child_id)| {
matches!(
node,
Node::LetStmt(LetStmt { els: Some(els), .. })
if els.hir_id == child_id
)
})
}
/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
@@ -2048,22 +2035,20 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool
/// Gets the node where an expression is either used, or it's type is unified with another branch.
/// Returns both the node and the `HirId` of the closest child node.
pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> {
let mut child_id = expr.hir_id;
let mut iter = tcx.hir_parent_iter(child_id);
loop {
match iter.next() {
None => break None,
Some((id, Node::Block(_))) => child_id = id,
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
Some((_, Node::Expr(expr))) => match expr.kind {
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
_ => break Some((Node::Expr(expr), child_id)),
for (node, child_id) in hir_parent_with_src_iter(tcx, expr.hir_id) {
match node {
Node::Block(_) => {},
Node::Arm(arm) if arm.body.hir_id == child_id => {},
Node::Expr(expr) => match expr.kind {
ExprKind::Block(..) | ExprKind::DropTemps(_) => {},
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => {},
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => return None,
_ => return Some((Node::Expr(expr), child_id)),
},
Some((_, node)) => break Some((node, child_id)),
node => return Some((node, child_id)),
}
}
None
}
/// Checks if the result of an expression is used, or it's type is unified with another branch.
@@ -2507,60 +2492,12 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
find_attr!(tcx, def_id, CfgTrace(..))
|| find_attr!(
tcx.hir_parent_iter(tcx.local_def_id_to_hir_id(def_id))
.flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id)),
tcx.hir_parent_id_iter(tcx.local_def_id_to_hir_id(def_id))
.flat_map(|parent_id| tcx.hir_attrs(parent_id)),
CfgTrace(..)
)
}
/// Walks up the HIR tree from the given expression in an attempt to find where the value is
/// consumed.
///
/// Termination has three conditions:
/// - The given function returns `Break`. This function will return the value.
/// - The consuming node is found. This function will return `Continue(use_node, child_id)`.
/// - No further parent nodes are found. This will trigger a debug assert or return `None`.
///
/// This allows walking through `if`, `match`, `break`, and block expressions to find where the
/// value produced by the expression is consumed.
pub fn walk_to_expr_usage<'tcx, T>(
cx: &LateContext<'tcx>,
e: &Expr<'tcx>,
mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>,
) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> {
let mut iter = cx.tcx.hir_parent_iter(e.hir_id);
let mut child_id = e.hir_id;
while let Some((parent_id, parent)) = iter.next() {
if let ControlFlow::Break(x) = f(parent_id, parent, child_id) {
return Some(ControlFlow::Break(x));
}
let parent_expr = match parent {
Node::Expr(e) => e,
Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
child_id = parent_id;
continue;
},
Node::Arm(a) if a.body.hir_id == child_id => {
child_id = parent_id;
continue;
},
_ => return Some(ControlFlow::Continue((parent, child_id))),
};
match parent_expr.kind {
ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
child_id = id;
iter = cx.tcx.hir_parent_iter(id);
},
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id,
_ => return Some(ControlFlow::Continue((parent, child_id))),
}
}
debug_assert!(false, "no parent node found for `{child_id:?}`");
None
}
/// A type definition as it would be viewed from within a function.
#[derive(Clone, Copy)]
pub enum DefinedTy<'tcx> {
@@ -2579,11 +2516,11 @@ pub enum DefinedTy<'tcx> {
},
}
/// The context an expressions value is used in.
pub struct ExprUseCtxt<'tcx> {
/// The location that recives the value of an expression.
pub struct ExprUseSite<'tcx> {
/// The parent node which consumes the value.
pub node: Node<'tcx>,
/// The child id of the node the value came from.
/// The ID of the immediate child of the use node.
pub child_id: HirId,
/// Any adjustments applied to the type.
pub adjustments: &'tcx [Adjustment<'tcx>],
@@ -2594,7 +2531,7 @@ pub struct ExprUseCtxt<'tcx> {
/// Whether the use site has the same `SyntaxContext` as the value.
pub same_ctxt: bool,
}
impl<'tcx> ExprUseCtxt<'tcx> {
impl<'tcx> ExprUseSite<'tcx> {
pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> {
match self.node {
Node::LetStmt(l) => ExprUseNode::LetStmt(l),
@@ -2760,54 +2697,178 @@ pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> {
}
}
/// Gets the context an expression's value is used in.
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'tcx>) -> ExprUseCtxt<'tcx> {
let mut adjustments = [].as_slice();
struct ReplacingFilterMap<I, F>(I, F);
impl<I, F, U> Iterator for ReplacingFilterMap<I, F>
where
I: Iterator,
F: FnMut(&mut I, I::Item) -> Option<U>,
{
type Item = U;
fn next(&mut self) -> Option<U> {
while let Some(x) = self.0.next() {
if let Some(x) = (self.1)(&mut self.0, x) {
return Some(x);
}
}
None
}
}
/// Returns an iterator which walks successive value using parent nodes skipping any node
/// which simply moves a value.
#[expect(clippy::too_many_lines)]
pub fn expr_use_sites<'tcx>(
tcx: TyCtxt<'tcx>,
typeck: &'tcx TypeckResults<'tcx>,
mut ctxt: SyntaxContext,
e: &'tcx Expr<'tcx>,
) -> impl Iterator<Item = ExprUseSite<'tcx>> {
let mut adjustments: &[_] = typeck.expr_adjustments(e);
let mut is_ty_unified = false;
let mut moved_before_use = false;
let mut same_ctxt = true;
let ctxt = e.span.ctxt();
let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow<!> {
if adjustments.is_empty()
&& let Node::Expr(e) = cx.tcx.hir_node(child_id)
{
adjustments = cx.typeck_results().expr_adjustments(e);
}
same_ctxt &= cx.tcx.hir_span(parent_id).ctxt() == ctxt;
if let Node::Expr(e) = parent {
match e.kind {
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
is_ty_unified = true;
moved_before_use = true;
ReplacingFilterMap(
hir_parent_with_src_iter(tcx, e.hir_id),
move |iter: &mut _, (parent, child_id)| {
let parent_ctxt;
let mut parent_adjustments: &[_] = &[];
match parent {
Node::Expr(parent_expr) => {
parent_ctxt = parent_expr.span.ctxt();
same_ctxt &= parent_ctxt == ctxt;
parent_adjustments = typeck.expr_adjustments(parent_expr);
match parent_expr.kind {
ExprKind::Match(scrutinee, arms, _) if scrutinee.hir_id != child_id => {
is_ty_unified |= arms.len() != 1;
moved_before_use = true;
if adjustments.is_empty() {
adjustments = parent_adjustments;
}
return None;
},
ExprKind::If(cond, _, else_) if cond.hir_id != child_id => {
is_ty_unified |= else_.is_some();
moved_before_use = true;
if adjustments.is_empty() {
adjustments = parent_adjustments;
}
return None;
},
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
is_ty_unified = true;
moved_before_use = true;
*iter = hir_parent_with_src_iter(tcx, id);
if adjustments.is_empty() {
adjustments = parent_adjustments;
}
return None;
},
ExprKind::Block(b, _) => {
is_ty_unified |= b.targeted_by_break;
moved_before_use = true;
if adjustments.is_empty() {
adjustments = parent_adjustments;
}
return None;
},
ExprKind::DropTemps(_) | ExprKind::Type(..) => {
if adjustments.is_empty() {
adjustments = parent_adjustments;
}
return None;
},
_ => {},
}
},
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
is_ty_unified = true;
moved_before_use = true;
Node::Arm(arm) => {
parent_ctxt = arm.span.ctxt();
same_ctxt &= parent_ctxt == ctxt;
if arm.body.hir_id == child_id {
return None;
}
},
Node::Block(b) => {
same_ctxt &= b.span.ctxt() == ctxt;
return None;
},
Node::ConstBlock(_) => parent_ctxt = ctxt,
Node::ExprField(&ExprField { span, .. }) => {
parent_ctxt = span.ctxt();
same_ctxt &= parent_ctxt == ctxt;
},
Node::AnonConst(&AnonConst { span, .. })
| Node::ConstArg(&ConstArg { span, .. })
| Node::Field(&FieldDef { span, .. })
| Node::ImplItem(&ImplItem { span, .. })
| Node::Item(&Item { span, .. })
| Node::LetStmt(&LetStmt { span, .. })
| Node::Stmt(&Stmt { span, .. })
| Node::TraitItem(&TraitItem { span, .. })
| Node::Variant(&Variant { span, .. }) => {
parent_ctxt = span.ctxt();
same_ctxt &= parent_ctxt == ctxt;
*iter = hir_parent_with_src_iter(tcx, CRATE_HIR_ID);
},
Node::AssocItemConstraint(_)
| Node::ConstArgExprField(_)
| Node::Crate(_)
| Node::Ctor(_)
| Node::Err(_)
| Node::ForeignItem(_)
| Node::GenericParam(_)
| Node::Infer(_)
| Node::Lifetime(_)
| Node::OpaqueTy(_)
| Node::Param(_)
| Node::Pat(_)
| Node::PatExpr(_)
| Node::PatField(_)
| Node::PathSegment(_)
| Node::PreciseCapturingNonLifetimeArg(_)
| Node::Synthetic
| Node::TraitRef(_)
| Node::Ty(_)
| Node::TyPat(_)
| Node::WherePredicate(_) => {
// This shouldn't be possible to hit; the inner iterator should have
// been moved to the end before we hit any of these nodes.
debug_assert!(false, "found {parent:?} which is after the final use node");
return None;
},
ExprKind::Block(..) => moved_before_use = true,
_ => {},
}
}
ControlFlow::Continue(())
});
match node {
Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt {
node,
child_id,
adjustments,
is_ty_unified,
moved_before_use,
same_ctxt,
ctxt = parent_ctxt;
Some(ExprUseSite {
node: parent,
child_id,
adjustments: mem::replace(&mut adjustments, parent_adjustments),
is_ty_unified: mem::replace(&mut is_ty_unified, false),
moved_before_use: mem::replace(&mut moved_before_use, false),
same_ctxt: mem::replace(&mut same_ctxt, true),
})
},
None => ExprUseCtxt {
node: Node::Crate(cx.tcx.hir_root_module()),
child_id: HirId::INVALID,
)
}
pub fn get_expr_use_site<'tcx>(
tcx: TyCtxt<'tcx>,
typeck: &'tcx TypeckResults<'tcx>,
ctxt: SyntaxContext,
e: &'tcx Expr<'tcx>,
) -> ExprUseSite<'tcx> {
// The value in `unwrap_or` doesn't actually matter; an expression always
// has a use site.
expr_use_sites(tcx, typeck, ctxt, e).next().unwrap_or_else(|| {
debug_assert!(false, "failed to find a use site for expr {e:?}");
ExprUseSite {
node: Node::Synthetic, // The crate root would also work.
child_id: CRATE_HIR_ID,
adjustments: &[],
is_ty_unified: true,
moved_before_use: true,
is_ty_unified: false,
moved_before_use: false,
same_ctxt: false,
},
}
}
})
}
/// Tokenizes the input while keeping the text associated with each token.
@@ -3642,3 +3703,11 @@ pub fn is_expr_async_block(expr: &Expr<'_>) -> bool {
pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool {
cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS)
}
/// Returns an iterator over successive parent nodes paired with the ID of the node which
/// immediatly preceeded them.
#[inline]
pub fn hir_parent_with_src_iter(tcx: TyCtxt<'_>, mut id: HirId) -> impl Iterator<Item = (Node<'_>, HirId)> {
tcx.hir_parent_id_iter(id)
.map(move |parent| (tcx.hir_node(parent), mem::replace(&mut id, parent)))
}
+1 -1
View File
@@ -28,7 +28,7 @@ macro_rules! msrv_aliases {
1,88,0 { LET_CHAINS }
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
1,86,0 { VEC_POP_IF }
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL, WAKER_NOOP }
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS }
+5
View File
@@ -335,6 +335,11 @@ pub fn blockify(self) -> Sugg<'static> {
Sugg::NonParen(Cow::Owned(format!("{{ {self} }}")))
}
/// Convenience method to wrap the expression in an `unsafe` block.
pub fn unsafeify(self) -> Sugg<'static> {
Sugg::NonParen(Cow::Owned(format!("unsafe {{ {self} }}")))
}
/// Convenience method to prefix the expression with the `async` keyword.
/// Can be used after `blockify` to create an async block.
pub fn asyncify(self) -> Sugg<'static> {
+8 -11
View File
@@ -1265,17 +1265,14 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl
/// This does not look for impls in the type's `Deref::Target` type.
/// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`.
pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> {
if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) {
cx.tcx.inherent_impls(ty_did).iter().find_map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.tag() == AssocTag::Fn)
})
} else {
None
}
let ty_did = ty.ty_adt_def().map(AdtDef::did)?;
cx.tcx.inherent_impls(ty_did).iter().find_map(|&did| {
cx.tcx
.associated_items(did)
.filter_by_name_unhygienic(method_name)
.next()
.filter(|item| item.tag() == AssocTag::Fn)
})
}
/// Gets the type of a field by name.
+1 -1
View File
@@ -11,7 +11,7 @@ publish = false
default-run = "lintcheck"
[dependencies]
cargo_metadata = "0.15.3"
cargo_metadata = "0.23"
clap = { version = "4.4", features = ["derive", "env"] }
crossbeam-channel = "0.5.6"
diff = "0.1.13"
+1
View File
@@ -1,4 +1,5 @@
#![deny(clippy::bind_instead_of_map)]
#![allow(clippy::manual_filter)]
// need a main anyway, use it get rid of unused warnings too
pub fn main() {
+1
View File
@@ -1,4 +1,5 @@
#![deny(clippy::bind_instead_of_map)]
#![allow(clippy::manual_filter)]
// need a main anyway, use it get rid of unused warnings too
pub fn main() {
+3 -3
View File
@@ -1,5 +1,5 @@
error: using `Option.and_then(Some)`, which is a no-op
--> tests/ui/bind_instead_of_map.rs:7:13
--> tests/ui/bind_instead_of_map.rs:8:13
|
LL | let _ = x.and_then(Some);
| ^^^^^^^^^^^^^^^^ help: use the expression directly: `x`
@@ -11,13 +11,13 @@ LL | #![deny(clippy::bind_instead_of_map)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`
--> tests/ui/bind_instead_of_map.rs:9:13
--> tests/ui/bind_instead_of_map.rs:10:13
|
LL | let _ = x.and_then(|o| Some(o + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.map(|o| o + 1)`
error: using `Result.and_then(Ok)`, which is a no-op
--> tests/ui/bind_instead_of_map.rs:16:13
--> tests/ui/bind_instead_of_map.rs:17:13
|
LL | let _ = x.and_then(Ok);
| ^^^^^^^^^^^^^^ help: use the expression directly: `x`
+10
View File
@@ -89,3 +89,13 @@ fn ineffective() {
x | 3 > 4; // not an error (yet), better written as x >= 4
x | 4 <= 19;
}
mod issue16781 {
fn unsigned(x: u8) -> bool {
x & 0xf0 == 0x11 << 4
}
fn signed(x: i8) -> bool {
x & 0x70 == 0x11 << 4
}
}
+37 -3
View File
@@ -1,4 +1,5 @@
#![warn(clippy::byte_char_slices)]
#![allow(clippy::useless_vec)]
fn main() {
let bad = b"abc";
@@ -11,7 +12,40 @@ fn main() {
//~^ byte_char_slices
let good = &[b'a', 0x42];
let good = [b'a', b'a'];
//~^ useless_vec
let good: u8 = [b'a', b'c'].into_iter().sum();
let good = vec![b'a', b'a'];
}
fn takes_array_ref(_: &[u8; 2]) {}
fn takes_array_ref_ref(_: &&[u8; 2]) {}
fn issue16759(bytes: [u32; 3]) {
const START: u32 = u32::from_le_bytes(*b"WORK");
//~^ byte_char_slices
let auto_deref_to_slice: u8 = b"ac".iter().copied().sum();
//~^ byte_char_slices
let with_comment = [
// 1 2 3
b'a', b'b', b'c', // x
b'd', b'e', b'f', // 2x
b'g', b'h', b'i', // 3x
];
let with_cfg = [
b'a',
b'b',
b'c',
#[cfg(feature = "foo")]
b'd',
];
let with_escape: u8 = b"'\"\x00\n\\".iter().copied().sum();
//~^ byte_char_slices
takes_array_ref(b"ab");
//~^ byte_char_slices
takes_array_ref_ref(&b"ab");
//~^ byte_char_slices
}
+36 -2
View File
@@ -1,4 +1,5 @@
#![warn(clippy::byte_char_slices)]
#![allow(clippy::useless_vec)]
fn main() {
let bad = &[b'a', b'b', b'c'];
@@ -12,6 +13,39 @@ fn main() {
let good = &[b'a', 0x42];
let good = vec![b'a', b'a'];
//~^ useless_vec
let good: u8 = [b'a', b'c'].into_iter().sum();
}
fn takes_array_ref(_: &[u8; 2]) {}
fn takes_array_ref_ref(_: &&[u8; 2]) {}
fn issue16759(bytes: [u32; 3]) {
const START: u32 = u32::from_le_bytes([b'W', b'O', b'R', b'K']);
//~^ byte_char_slices
let auto_deref_to_slice: u8 = [b'a', b'c'].iter().copied().sum();
//~^ byte_char_slices
let with_comment = [
// 1 2 3
b'a', b'b', b'c', // x
b'd', b'e', b'f', // 2x
b'g', b'h', b'i', // 3x
];
let with_cfg = [
b'a',
b'b',
b'c',
#[cfg(feature = "foo")]
b'd',
];
let with_escape: u8 = [b'\'', b'"', b'\x00', b'\n', b'\\'].iter().copied().sum();
//~^ byte_char_slices
takes_array_ref(&[b'a', b'b']);
//~^ byte_char_slices
takes_array_ref_ref(&&[b'a', b'b']);
//~^ byte_char_slices
}
+32 -11
View File
@@ -1,5 +1,5 @@
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:4:15
--> tests/ui/byte_char_slices.rs:5:15
|
LL | let bad = &[b'a', b'b', b'c'];
| ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"`
@@ -8,31 +8,52 @@ LL | let bad = &[b'a', b'b', b'c'];
= help: to override `-D warnings` add `#[allow(clippy::byte_char_slices)]`
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:6:18
--> tests/ui/byte_char_slices.rs:7:18
|
LL | let quotes = &[b'"', b'H', b'i'];
| ^^^^^^^^^^^^^^^^^^^ help: try: `b"\"Hi"`
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:8:18
--> tests/ui/byte_char_slices.rs:9:18
|
LL | let quotes = &[b'\'', b'S', b'u', b'p'];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'Sup"`
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:10:19
--> tests/ui/byte_char_slices.rs:11:19
|
LL | let escapes = &[b'\x42', b'E', b's', b'c'];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"\x42Esc"`
error: useless use of `vec!`
--> tests/ui/byte_char_slices.rs:14:16
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:23:43
|
LL | let good = vec![b'a', b'a'];
| ^^^^^^^^^^^^^^^^ help: you can use an array directly: `[b'a', b'a']`
LL | const START: u32 = u32::from_le_bytes([b'W', b'O', b'R', b'K']);
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `*b"WORK"`
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:26:35
|
= note: `-D clippy::useless-vec` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::useless_vec)]`
LL | let auto_deref_to_slice: u8 = [b'a', b'c'].iter().copied().sum();
| ^^^^^^^^^^^^ help: try: `b"ac"`
error: aborting due to 5 previous errors
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:43:27
|
LL | let with_escape: u8 = [b'\'', b'"', b'\x00', b'\n', b'\\'].iter().copied().sum();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'\"\x00\n\\"`
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:46:21
|
LL | takes_array_ref(&[b'a', b'b']);
| ^^^^^^^^^^^^^ help: try: `b"ab"`
error: can be more succinctly written as a byte str
--> tests/ui/byte_char_slices.rs:49:26
|
LL | takes_array_ref_ref(&&[b'a', b'b']);
| ^^^^^^^^^^^^^ help: try: `b"ab"`
error: aborting due to 9 previous errors
+10
View File
@@ -147,3 +147,13 @@ fn main() {
return;
});
}
fn issue16747() {
let x = 42;
let _c = char::from_u32(x).unwrap_or_else(|| panic!("{}", &format!("Illegal: {x}")[..]));
//~^ expect_fun_call
let s = "hello";
let _c = char::from_u32(x).unwrap_or_else(|| panic!("{}", &s.to_lowercase()[..2]));
//~^ expect_fun_call
}
+10
View File
@@ -147,3 +147,13 @@ const fn const_evaluable() -> &'static str {
return;
});
}
fn issue16747() {
let x = 42;
let _c = char::from_u32(x).expect(&format!("Illegal: {x}")[..]);
//~^ expect_fun_call
let s = "hello";
let _c = char::from_u32(x).expect(&s.to_lowercase()[..2]);
//~^ expect_fun_call
}
+13 -1
View File
@@ -97,5 +97,17 @@ error: function call inside of `expect`
LL | format_capture_and_value.expect(&format!("{error_code}, {}", 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{error_code}, {}", 1))`
error: aborting due to 16 previous errors
error: function call inside of `expect`
--> tests/ui/expect_fun_call.rs:153:32
|
LL | let _c = char::from_u32(x).expect(&format!("Illegal: {x}")[..]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", &format!("Illegal: {x}")[..]))`
error: function call inside of `expect`
--> tests/ui/expect_fun_call.rs:157:32
|
LL | let _c = char::from_u32(x).expect(&s.to_lowercase()[..2]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_else(|| panic!("{}", &s.to_lowercase()[..2]))`
error: aborting due to 18 previous errors
+1 -1
View File
@@ -82,7 +82,7 @@ fn closure_to_fn_to_integer() {
fn fn_to_raw_ptr() {
let _ = foo as *const ();
//~^ fn_to_numeric_cast_any
let _ = foo as *mut ();
}
fn cast_fn_to_self() {
+1 -12
View File
@@ -176,16 +176,5 @@ help: did you mean to invoke the function?
LL | let _ = (clos as fn(u32) -> u32)() as usize;
| ++
error: casting function pointer `foo` to `*const ()`
--> tests/ui/fn_to_numeric_cast_any.rs:84:13
|
LL | let _ = foo as *const ();
| ^^^^^^^^^^^^^^^^
|
help: did you mean to invoke the function?
|
LL | let _ = foo() as *const ();
| ++
error: aborting due to 17 previous errors
error: aborting due to 16 previous errors
+5 -1
View File
@@ -1,5 +1,9 @@
#![warn(clippy::if_then_some_else_none)]
#![allow(clippy::redundant_pattern_matching, clippy::unnecessary_lazy_evaluations)]
#![allow(
clippy::redundant_pattern_matching,
clippy::unnecessary_lazy_evaluations,
clippy::manual_filter
)]
fn main() {
// Should issue an error.
+5 -1
View File
@@ -1,5 +1,9 @@
#![warn(clippy::if_then_some_else_none)]
#![allow(clippy::redundant_pattern_matching, clippy::unnecessary_lazy_evaluations)]
#![allow(
clippy::redundant_pattern_matching,
clippy::unnecessary_lazy_evaluations,
clippy::manual_filter
)]
fn main() {
// Should issue an error.
+14 -14
View File
@@ -1,5 +1,5 @@
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:6:13
--> tests/ui/if_then_some_else_none.rs:10:13
|
LL | let _ = if foo() {
| _____________^
@@ -24,7 +24,7 @@ LL ~ });
|
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:16:13
--> tests/ui/if_then_some_else_none.rs:20:13
|
LL | let _ = if matches!(true, true) {
| _____________^
@@ -47,19 +47,19 @@ LL ~ });
|
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:27:28
--> tests/ui/if_then_some_else_none.rs:31:28
|
LL | let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(o < 32).then_some(o)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:32:13
--> tests/ui/if_then_some_else_none.rs:36:13
|
LL | let _ = if !x { Some(0) } else { None };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(!x).then_some(0)`
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:88:13
--> tests/ui/if_then_some_else_none.rs:92:13
|
LL | let _ = if foo() {
| _____________^
@@ -82,13 +82,13 @@ LL ~ });
|
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:138:5
--> tests/ui/if_then_some_else_none.rs:142:5
|
LL | if s == "1" { Some(true) } else { None }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(s == "1").then(|| true)`
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:154:9
--> tests/ui/if_then_some_else_none.rs:158:9
|
LL | / if rs.len() == 1 && rs[0].start == rs[0].end {
LL | |
@@ -99,7 +99,7 @@ LL | | }
| |_________^ help: try: `(rs.len() == 1 && rs[0].start == rs[0].end).then(|| vec![rs[0].start])`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:164:9
--> tests/ui/if_then_some_else_none.rs:168:9
|
LL | / if modulo == 0 {
LL | |
@@ -110,7 +110,7 @@ LL | | }
| |_________^ help: try: `(modulo == 0).then_some(i)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:181:22
--> tests/ui/if_then_some_else_none.rs:185:22
|
LL | do_something(if i % 2 == 0 {
| ______________________^
@@ -122,7 +122,7 @@ LL | | });
| |_________^ help: try: `(i % 2 == 0).then_some(item_fn)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:198:22
--> tests/ui/if_then_some_else_none.rs:202:22
|
LL | do_something(if i % 2 == 0 {
| ______________________^
@@ -134,7 +134,7 @@ LL | | });
| |_________^ help: try: `(i % 2 == 0).then_some(item_fn)`
error: this could be simplified with `bool::then_some`
--> tests/ui/if_then_some_else_none.rs:206:22
--> tests/ui/if_then_some_else_none.rs:210:22
|
LL | do_something(if i % 2 == 0 {
| ______________________^
@@ -146,7 +146,7 @@ LL | | });
| |_________^ help: try: `(i % 2 == 0).then_some(closure_fn)`
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:231:13
--> tests/ui/if_then_some_else_none.rs:235:13
|
LL | / if self.count < 5 {
LL | | self.count += 1;
@@ -165,7 +165,7 @@ LL + })
|
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:249:13
--> tests/ui/if_then_some_else_none.rs:253:13
|
LL | let _ = if true {
| _____________^
@@ -185,7 +185,7 @@ LL ~ });
|
error: this could be simplified with `bool::then`
--> tests/ui/if_then_some_else_none.rs:292:5
--> tests/ui/if_then_some_else_none.rs:296:5
|
LL | / if 1 <= 3 {
LL | | let a = UnsafeCell::new(1);
+18
View File
@@ -260,3 +260,21 @@ fn issue16307() {
println!("{y}");
}
fn wrongly_unmangled_macros() {
macro_rules! test_big {
($val:expr) => {
($val * 2 + 1)
};
}
macro_rules! test_little {
($val:expr) => {
($val * 2 + 0)
};
}
let a = 15u64;
let b = 20u64;
let _ = test_big!(a).saturating_sub(test_little!(b));
}
+23
View File
@@ -334,3 +334,26 @@ fn issue16307() {
println!("{y}");
}
fn wrongly_unmangled_macros() {
macro_rules! test_big {
($val:expr) => {
($val * 2 + 1)
};
}
macro_rules! test_little {
($val:expr) => {
($val * 2 + 0)
};
}
let a = 15u64;
let b = 20u64;
let _ = if test_big!(a) > test_little!(b) {
//~^ implicit_saturating_sub
test_big!(a) - test_little!(b)
} else {
0
};
}
+13 -1
View File
@@ -244,5 +244,17 @@ error: manual arithmetic check found
LL | let y = if x >= 100 { 0 } else { 100 - x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `100_u8.saturating_sub(x)`
error: aborting due to 28 previous errors
error: manual arithmetic check found
--> tests/ui/implicit_saturating_sub.rs:353:13
|
LL | let _ = if test_big!(a) > test_little!(b) {
| _____________^
LL | |
LL | | test_big!(a) - test_little!(b)
LL | | } else {
LL | | 0
LL | | };
| |_____^ help: replace it with: `test_big!(a).saturating_sub(test_little!(b))`
error: aborting due to 29 previous errors
+14
View File
@@ -16,4 +16,18 @@ fn main() {
let _ = x > y; // should be ok
let _ = y < x; // should be ok
// When the suggestion replaces `<=`/`>=` with `<`, an `as` cast on
// the LHS must be parenthesized to avoid parser ambiguity
// (e.g., `x as usize < y` is parsed as `x as usize<y>`).
let z = 0usize;
let _ = (x as usize) < z; //~ int_plus_one
let _ = z > x as usize; //~ int_plus_one
// No parentheses needed when the replacement operator is `>`.
let _ = x as usize > z; //~ int_plus_one
let _ = z < x as usize; //~ int_plus_one
// Nested and parenthesized casts on the LHS.
let _ = ((x as usize) as u8) < 5u8; //~ int_plus_one
let _ = (x as usize) < z; //~ int_plus_one
}
+14
View File
@@ -16,4 +16,18 @@ fn main() {
let _ = x > y; // should be ok
let _ = y < x; // should be ok
// When the suggestion replaces `<=`/`>=` with `<`, an `as` cast on
// the LHS must be parenthesized to avoid parser ambiguity
// (e.g., `x as usize < y` is parsed as `x as usize<y>`).
let z = 0usize;
let _ = x as usize + 1 <= z; //~ int_plus_one
let _ = z >= x as usize + 1; //~ int_plus_one
// No parentheses needed when the replacement operator is `>`.
let _ = x as usize - 1 >= z; //~ int_plus_one
let _ = z <= x as usize - 1; //~ int_plus_one
// Nested and parenthesized casts on the LHS.
let _ = ((x as usize) as u8) + 1 <= 5u8; //~ int_plus_one
let _ = (x as usize) + 1 <= z; //~ int_plus_one
}
+37 -1
View File
@@ -49,5 +49,41 @@ error: unnecessary `>= y + 1` or `x - 1 >=`
LL | let _ = y <= -1 + x;
| ^^^^^^^^^^^ help: change it to: `y < x`
error: aborting due to 8 previous errors
error: unnecessary `>= y + 1` or `x - 1 >=`
--> tests/ui/int_plus_one.rs:24:13
|
LL | let _ = x as usize + 1 <= z;
| ^^^^^^^^^^^^^^^^^^^ help: change it to: `(x as usize) < z`
error: unnecessary `>= y + 1` or `x - 1 >=`
--> tests/ui/int_plus_one.rs:25:13
|
LL | let _ = z >= x as usize + 1;
| ^^^^^^^^^^^^^^^^^^^ help: change it to: `z > x as usize`
error: unnecessary `>= y + 1` or `x - 1 >=`
--> tests/ui/int_plus_one.rs:27:13
|
LL | let _ = x as usize - 1 >= z;
| ^^^^^^^^^^^^^^^^^^^ help: change it to: `x as usize > z`
error: unnecessary `>= y + 1` or `x - 1 >=`
--> tests/ui/int_plus_one.rs:28:13
|
LL | let _ = z <= x as usize - 1;
| ^^^^^^^^^^^^^^^^^^^ help: change it to: `z < x as usize`
error: unnecessary `>= y + 1` or `x - 1 >=`
--> tests/ui/int_plus_one.rs:31:13
|
LL | let _ = ((x as usize) as u8) + 1 <= 5u8;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: change it to: `((x as usize) as u8) < 5u8`
error: unnecessary `>= y + 1` or `x - 1 >=`
--> tests/ui/int_plus_one.rs:32:13
|
LL | let _ = (x as usize) + 1 <= z;
| ^^^^^^^^^^^^^^^^^^^^^ help: change it to: `(x as usize) < z`
error: aborting due to 14 previous errors
+23
View File
@@ -279,4 +279,27 @@ fn has_comment() -> Vec<usize> {
v
}
fn wrongly_unmangled_macros() -> i32 {
let x = 1;
macro_rules! plus_one {
($e:expr) => {
$e + 1
};
}
plus_one!(x)
//~^ let_and_return
}
fn issue16820() -> Option<i32> {
let value = Some(42);
let v @ None = value else {
panic!("uh oh!");
};
v
}
fn main() {}
+15 -1
View File
@@ -148,5 +148,19 @@ LL ~
LL ~ ({ true } || { false } && { 2 <= 3 })
|
error: aborting due to 10 previous errors
error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:291:5
|
LL | let y = plus_one!(x);
| --------------------- unnecessary `let` binding
LL | y
| ^
|
help: return the expression directly
|
LL ~
LL ~ plus_one!(x)
|
error: aborting due to 11 previous errors
+23
View File
@@ -279,4 +279,27 @@ fn has_comment() -> Vec<usize> {
v
}
fn wrongly_unmangled_macros() -> i32 {
let x = 1;
macro_rules! plus_one {
($e:expr) => {
$e + 1
};
}
plus_one!(x)
//~^ let_and_return
}
fn issue16820() -> Option<i32> {
let value = Some(42);
let v @ None = value else {
panic!("uh oh!");
};
v
}
fn main() {}
+15 -1
View File
@@ -224,5 +224,19 @@ LL + None => Ok(Ok(0)),
LL + }?
|
error: aborting due to 15 previous errors
error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:291:5
|
LL | let y = plus_one!(x);
| --------------------- unnecessary `let` binding
LL | y
| ^
|
help: return the expression directly
|
LL ~
LL ~ plus_one!(x)
|
error: aborting due to 16 previous errors
+23
View File
@@ -279,4 +279,27 @@ fn has_comment() -> Vec<usize> {
v
}
fn wrongly_unmangled_macros() -> i32 {
let x = 1;
macro_rules! plus_one {
($e:expr) => {
$e + 1
};
}
let y = plus_one!(x);
y
//~^ let_and_return
}
fn issue16820() -> Option<i32> {
let value = Some(42);
let v @ None = value else {
panic!("uh oh!");
};
v
}
fn main() {}
+9
View File
@@ -16,6 +16,12 @@ macro_rules! eight {
};
}
macro_rules! plus_one {
($val:expr) => {
($val + 1)
};
}
fn main() {
let x = 7_u32;
let y = 4_u32;
@@ -55,6 +61,9 @@ fn main() {
// Also test if RHS should be result of macro expansion
let _ = 33u32.div_ceil(eight!());
//~^ manual_div_ceil
let _ = plus_one!(x).div_ceil(y);
//~^ manual_div_ceil
}
fn issue_13843() {
+9
View File
@@ -16,6 +16,12 @@ macro_rules! eight {
};
}
macro_rules! plus_one {
($val:expr) => {
($val + 1)
};
}
fn main() {
let x = 7_u32;
let y = 4_u32;
@@ -55,6 +61,9 @@ fn main() {
// Also test if RHS should be result of macro expansion
let _ = (33u32 + 7) / eight!();
//~^ manual_div_ceil
let _ = (plus_one!(x) + (y - 1)) / y;
//~^ manual_div_ceil
}
fn issue_13843() {
+28 -22
View File
@@ -1,5 +1,5 @@
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:25:13
--> tests/ui/manual_div_ceil.rs:31:13
|
LL | let _ = (x + (y - 1)) / y;
| ^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)`
@@ -8,25 +8,25 @@ LL | let _ = (x + (y - 1)) / y;
= help: to override `-D warnings` add `#[allow(clippy::manual_div_ceil)]`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:27:13
--> tests/ui/manual_div_ceil.rs:33:13
|
LL | let _ = ((y - 1) + x) / y;
| ^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:29:13
--> tests/ui/manual_div_ceil.rs:35:13
|
LL | let _ = (x + y - 1) / y;
| ^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(y)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:32:13
--> tests/ui/manual_div_ceil.rs:38:13
|
LL | let _ = (7_u32 + (4 - 1)) / 4;
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `7_u32.div_ceil(4)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:34:13
--> tests/ui/manual_div_ceil.rs:40:13
|
LL | let _ = (7_i32 as u32 + (4 - 1)) / 4;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `(7_i32 as u32).div_ceil(4)`
@@ -54,100 +54,106 @@ LL | y!();
= note: this error originates in the macro `y` (in Nightly builds, run with -Z macro-backtrace for more info)
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:56:13
--> tests/ui/manual_div_ceil.rs:62:13
|
LL | let _ = (33u32 + 7) / eight!();
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `33u32.div_ceil(eight!())`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:62:13
--> tests/ui/manual_div_ceil.rs:65:13
|
LL | let _ = (plus_one!(x) + (y - 1)) / y;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `plus_one!(x).div_ceil(y)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:71:13
|
LL | let _ = (2048 + x - 1) / x;
| ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_usize.div_ceil(x)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:66:13
--> tests/ui/manual_div_ceil.rs:75:13
|
LL | let _ = (2048usize + x - 1) / x;
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048usize.div_ceil(x)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:70:13
--> tests/ui/manual_div_ceil.rs:79:13
|
LL | let _ = (2048_usize + x - 1) / x;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_usize.div_ceil(x)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:74:13
--> tests/ui/manual_div_ceil.rs:83:13
|
LL | let _ = (x + 4 - 1) / 4;
| ^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(4)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:77:18
--> tests/ui/manual_div_ceil.rs:86:18
|
LL | let _: u32 = (2048 + 6 - 1) / 6;
| ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_u32.div_ceil(6)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:79:20
--> tests/ui/manual_div_ceil.rs:88:20
|
LL | let _: usize = (2048 + 6 - 1) / 6;
| ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_usize.div_ceil(6)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:81:18
--> tests/ui/manual_div_ceil.rs:90:18
|
LL | let _: u32 = (0x2048 + 0x6 - 1) / 0x6;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `0x2048_u32.div_ceil(0x6)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:84:13
--> tests/ui/manual_div_ceil.rs:93:13
|
LL | let _ = (2048 + 6u32 - 1) / 6u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `2048_u32.div_ceil(6u32)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:87:13
--> tests/ui/manual_div_ceil.rs:96:13
|
LL | let _ = (1_000_000 + 6u32 - 1) / 6u32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `1_000_000_u32.div_ceil(6u32)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:93:13
--> tests/ui/manual_div_ceil.rs:102:13
|
LL | let _ = (x + 7) / 8;
| ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:95:13
--> tests/ui/manual_div_ceil.rs:104:13
|
LL | let _ = (7 + x) / 8;
| ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:105:13
--> tests/ui/manual_div_ceil.rs:114:13
|
LL | let _ = (size + c - 1) / c;
| ^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `size.div_ceil(*c)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:121:13
--> tests/ui/manual_div_ceil.rs:130:13
|
LL | let _ = x.next_multiple_of(8) / 8;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:123:13
--> tests/ui/manual_div_ceil.rs:132:13
|
LL | let _ = u32::next_multiple_of(x, 8) / 8;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)`
error: manually reimplementing `div_ceil`
--> tests/ui/manual_div_ceil.rs:127:13
--> tests/ui/manual_div_ceil.rs:136:13
|
LL | let _ = y.next_multiple_of(8) / 8;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(8)`
error: aborting due to 23 previous errors
error: aborting due to 24 previous errors
+27 -1
View File
@@ -1,5 +1,10 @@
#![warn(clippy::manual_filter)]
#![allow(unused_variables, dead_code, clippy::useless_vec)]
#![allow(
unused_variables,
clippy::question_mark,
clippy::useless_vec,
clippy::nonminimal_bool
)]
fn main() {
Some(0).filter(|&x| x <= 0);
@@ -156,3 +161,24 @@ fn main() {
fn maybe_some() -> Option<u32> {
Some(0)
}
fn issue14440(opt: Option<i32>) {
opt.filter(|&x| x != 0);
//~^ manual_filter
let y = 1i32;
opt.filter(move |&x| x == y);
//~^ manual_filter
let opt1 = Some("123".to_string());
opt1.filter(|s| s.len() > 2);
//~^ manual_filter
unsafe fn f(x: u32) -> bool {
true
}
opt.filter(|&x| unsafe { f(x as u32) });
//~^ manual_filter
opt.filter(|&x| unsafe { f(x as u32) });
//~^ manual_filter
}
+27 -1
View File
@@ -1,5 +1,10 @@
#![warn(clippy::manual_filter)]
#![allow(unused_variables, dead_code, clippy::useless_vec)]
#![allow(
unused_variables,
clippy::question_mark,
clippy::useless_vec,
clippy::nonminimal_bool
)]
fn main() {
match Some(0) {
@@ -293,3 +298,24 @@ struct NamedTuple {
fn maybe_some() -> Option<u32> {
Some(0)
}
fn issue14440(opt: Option<i32>) {
opt.and_then(|x| if x == 0 { None } else { Some(x) });
//~^ manual_filter
let y = 1i32;
opt.and_then(move |x| if x == y { Some(x) } else { None });
//~^ manual_filter
let opt1 = Some("123".to_string());
opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None });
//~^ manual_filter
unsafe fn f(x: u32) -> bool {
true
}
opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None });
//~^ manual_filter
opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } });
//~^ manual_filter
}
+46 -16
View File
@@ -1,5 +1,5 @@
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:5:5
--> tests/ui/manual_filter.rs:10:5
|
LL | / match Some(0) {
LL | |
@@ -14,7 +14,7 @@ LL | | };
= help: to override `-D warnings` add `#[allow(clippy::manual_filter)]`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:17:5
--> tests/ui/manual_filter.rs:22:5
|
LL | / match Some(1) {
LL | |
@@ -26,7 +26,7 @@ LL | | };
| |_____^ help: try: `Some(1).filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:29:5
--> tests/ui/manual_filter.rs:34:5
|
LL | / match Some(2) {
LL | |
@@ -38,7 +38,7 @@ LL | | };
| |_____^ help: try: `Some(2).filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:41:5
--> tests/ui/manual_filter.rs:46:5
|
LL | / match Some(3) {
LL | |
@@ -50,7 +50,7 @@ LL | | };
| |_____^ help: try: `Some(3).filter(|&x| x > 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:54:5
--> tests/ui/manual_filter.rs:59:5
|
LL | / match y {
LL | |
@@ -62,7 +62,7 @@ LL | | };
| |_____^ help: try: `y.filter(|&x| x <= 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:67:5
--> tests/ui/manual_filter.rs:72:5
|
LL | / match Some(5) {
LL | |
@@ -74,7 +74,7 @@ LL | | };
| |_____^ help: try: `Some(5).filter(|&x| x > 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:79:5
--> tests/ui/manual_filter.rs:84:5
|
LL | / match Some(6) {
LL | |
@@ -86,7 +86,7 @@ LL | | };
| |_____^ help: try: `Some(6).as_ref().filter(|&x| x > &0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:92:5
--> tests/ui/manual_filter.rs:97:5
|
LL | / match Some(String::new()) {
LL | |
@@ -98,7 +98,7 @@ LL | | };
| |_____^ help: try: `Some(String::new()).filter(|x| external_cond)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:104:5
--> tests/ui/manual_filter.rs:109:5
|
LL | / if let Some(x) = Some(7) {
LL | |
@@ -109,7 +109,7 @@ LL | | };
| |_____^ help: try: `Some(7).filter(|&x| external_cond)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:111:5
--> tests/ui/manual_filter.rs:116:5
|
LL | / match &Some(8) {
LL | |
@@ -121,7 +121,7 @@ LL | | };
| |_____^ help: try: `Some(8).filter(|&x| x != 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:123:5
--> tests/ui/manual_filter.rs:128:5
|
LL | / match Some(9) {
LL | |
@@ -133,7 +133,7 @@ LL | | };
| |_____^ help: try: `Some(9).filter(|&x| x > 10 && x < 100)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:150:5
--> tests/ui/manual_filter.rs:155:5
|
LL | / match Some(11) {
LL | |
@@ -153,7 +153,7 @@ LL ~ });
|
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:195:13
--> tests/ui/manual_filter.rs:200:13
|
LL | let _ = match Some(14) {
| _____________^
@@ -166,7 +166,7 @@ LL | | };
| |_____^ help: try: `Some(14).filter(|&x| unsafe { f(x) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:206:13
--> tests/ui/manual_filter.rs:211:13
|
LL | let _ = match Some(15) {
| _____________^
@@ -177,7 +177,7 @@ LL | | };
| |_____^ help: try: `Some(15).filter(|&x| unsafe { f(x) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:215:12
--> tests/ui/manual_filter.rs:220:12
|
LL | } else if let Some(x) = Some(16) {
| ____________^
@@ -189,5 +189,35 @@ LL | | None
LL | | };
| |_____^ help: try: `{ Some(16).filter(|&x| x % 2 == 0) }`
error: aborting due to 15 previous errors
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:303:9
|
LL | opt.and_then(|x| if x == 0 { None } else { Some(x) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| x != 0)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:307:9
|
LL | opt.and_then(move |x| if x == y { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(move |&x| x == y)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:311:10
|
LL | opt1.and_then(|s| if s.len() > 2 { Some(s) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|s| s.len() > 2)`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:317:9
|
LL | opt.and_then(|x| if unsafe { f(x as u32) } { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })`
error: manual implementation of `Option::filter`
--> tests/ui/manual_filter.rs:319:9
|
LL | opt.and_then(|x| unsafe { if f(x as u32) { Some(x) } else { None } });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter(|&x| unsafe { f(x as u32) })`
error: aborting due to 20 previous errors
+12
View File
@@ -58,3 +58,15 @@ const fn high_msrv(a: u32) -> bool {
a.is_power_of_two()
//~^ manual_is_power_of_two
}
fn wrongly_unmangled_macros() {
macro_rules! test_val {
($val:expr) => {
($val * 2 + 1)
};
}
let a = 16_u64;
let _ = test_val!(a).is_power_of_two();
//~^ manual_is_power_of_two
}
+12
View File
@@ -58,3 +58,15 @@ const fn high_msrv(a: u32) -> bool {
a & (a - 1) == 0
//~^ manual_is_power_of_two
}
fn wrongly_unmangled_macros() {
macro_rules! test_val {
($val:expr) => {
($val * 2 + 1)
};
}
let a = 16_u64;
let _ = test_val!(a).count_ones() == 1;
//~^ manual_is_power_of_two
}
+7 -1
View File
@@ -55,5 +55,11 @@ error: manually reimplementing `is_power_of_two`
LL | a & (a - 1) == 0
| ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
error: aborting due to 9 previous errors
error: manually reimplementing `is_power_of_two`
--> tests/ui/manual_is_power_of_two.rs:70:13
|
LL | let _ = test_val!(a).count_ones() == 1;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `test_val!(a).is_power_of_two()`
error: aborting due to 10 previous errors
+25
View File
@@ -38,3 +38,28 @@ fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}
}
#[clippy::msrv = "1.84"]
mod msrv_1_84 {
use std::sync::Arc;
use std::task::Wake;
struct CustomWaker;
impl Wake for CustomWaker {
fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}
}
#[clippy::msrv = "1.85"]
mod msrv_1_85 {
use std::sync::Arc;
use std::task::Wake;
struct CustomWaker;
impl Wake for CustomWaker {
//~^ manual_noop_waker
fn wake(self: Arc<Self>) {}
fn wake_by_ref(self: &Arc<Self>) {}
}
}
+9 -1
View File
@@ -16,5 +16,13 @@ LL | impl Wake for MyWakerPartial {
|
= help: use `std::task::Waker::noop()` instead
error: aborting due to 2 previous errors
error: manual implementation of a no-op waker
--> tests/ui/manual_noop_waker.rs:60:10
|
LL | impl Wake for CustomWaker {
| ^^^^
|
= help: use `std::task::Waker::noop()` instead
error: aborting due to 3 previous errors
+23
View File
@@ -61,3 +61,26 @@ fn issue13028() {
// don't lint, because `s` and `u` are different variables, albeit with the same value
let _ = (x << s) | (x >> (32 - u));
}
fn wrongly_unmangled_macros() {
macro_rules! test_expr {
($val:expr) => {
$val.inner
};
}
struct Wrapper {
inner: u32,
}
let x = Wrapper { inner: 42 };
let _ = test_expr!(x).rotate_left(3);
//~^ manual_rotate
let y = Wrapper { inner: 100 };
let _ = x.inner.rotate_left(test_expr!(y));
//~^ manual_rotate
let _ = (test_expr!(x) << 3) | (x.inner >> 29);
}
+23
View File
@@ -61,3 +61,26 @@ fn issue13028() {
// don't lint, because `s` and `u` are different variables, albeit with the same value
let _ = (x << s) | (x >> (32 - u));
}
fn wrongly_unmangled_macros() {
macro_rules! test_expr {
($val:expr) => {
$val.inner
};
}
struct Wrapper {
inner: u32,
}
let x = Wrapper { inner: 42 };
let _ = (test_expr!(x) << 3) | (test_expr!(x) >> 29);
//~^ manual_rotate
let y = Wrapper { inner: 100 };
let _ = (x.inner << test_expr!(y)) | (x.inner >> (32 - test_expr!(y)));
//~^ manual_rotate
let _ = (test_expr!(x) << 3) | (x.inner >> 29);
}
+13 -1
View File
@@ -91,5 +91,17 @@ error: there is no need to manually implement bit rotation
LL | let _ = (x >> 9) | (x << (32 - 9));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.rotate_right(9)`
error: aborting due to 15 previous errors
error: there is no need to manually implement bit rotation
--> tests/ui/manual_rotate.rs:77:13
|
LL | let _ = (test_expr!(x) << 3) | (test_expr!(x) >> 29);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `test_expr!(x).rotate_left(3)`
error: there is no need to manually implement bit rotation
--> tests/ui/manual_rotate.rs:82:13
|
LL | let _ = (x.inner << test_expr!(y)) | (x.inner >> (32 - test_expr!(y)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x.inner.rotate_left(test_expr!(y))`
error: aborting due to 17 previous errors
+1 -1
View File
@@ -1,5 +1,5 @@
#![warn(clippy::map_flatten)]
#![allow(clippy::unnecessary_filter_map)]
#![allow(clippy::unnecessary_filter_map, clippy::manual_filter)]
// issue #8506, multi-line
#[rustfmt::skip]

Some files were not shown because too many files have changed in this diff Show More