mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-26 13:01:27 +03:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
+118
-6
@@ -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
@@ -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"] }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(_)
|
||||
)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<'_>,
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,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`")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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,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,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`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,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
Reference in New Issue
Block a user