Auto merge of #145056 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? `@Manishearth`

Cargo.lock update due to clippy version bump
This commit is contained in:
bors 2025-08-07 17:29:24 +00:00
commit 2fd855fbfc
182 changed files with 2808 additions and 1032 deletions

View file

@ -568,7 +568,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "clippy"
version = "0.1.90"
version = "0.1.91"
dependencies = [
"anstream",
"askama",
@ -595,7 +595,7 @@ dependencies = [
[[package]]
name = "clippy_config"
version = "0.1.90"
version = "0.1.91"
dependencies = [
"clippy_utils",
"itertools",
@ -618,7 +618,7 @@ dependencies = [
[[package]]
name = "clippy_lints"
version = "0.1.90"
version = "0.1.91"
dependencies = [
"arrayvec",
"cargo_metadata 0.18.1",
@ -649,7 +649,7 @@ dependencies = [
[[package]]
name = "clippy_utils"
version = "0.1.90"
version = "0.1.91"
dependencies = [
"arrayvec",
"itertools",
@ -1051,7 +1051,7 @@ dependencies = [
[[package]]
name = "declare_clippy_lint"
version = "0.1.90"
version = "0.1.91"
[[package]]
name = "derive-where"

View file

@ -48,3 +48,24 @@ body:
```
validations:
required: true
- type: textarea
id: comparison
attributes:
label: Comparison with existing lints
description: |
What makes this lint different from any existing lints that are similar, and how are those differences useful?
You can [use this playground template to see what existing lints are triggered by the bad code][playground]
(make sure to use "Tools > Clippy" and not "Build").
You can also look through the list of [rustc's allowed-by-default lints][allowed-by-default],
as those won't show up in the playground above.
[allowed-by-default]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
[playground]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&code=%23%21%5Bwarn%28clippy%3A%3Apedantic%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Anursery%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Arestriction%29%5D%0A%23%21%5Bwarn%28clippy%3A%3Aall%29%5D%0A%23%21%5Ballow%28clippy%3A%3Ablanket_clippy_restriction_lints%2C+reason+%3D+%22testing+to+see+if+any+restriction+lints+match+given+code%22%29%5D%0A%0A%2F%2F%21+Template+that+can+be+used+to+see+what+clippy+lints+a+given+piece+of+code+would+trigger
placeholder: Unlike `clippy::...`, the proposed lint would...
- type: textarea
id: context
attributes:
label: Additional Context
description: Any additional context that you believe may be relevant.

View file

@ -47,9 +47,9 @@ unset CARGO_MANIFEST_DIR
# Run a lint and make sure it produces the expected output. It's also expected to exit with code 1
# FIXME: How to match the clippy invocation in compile-test.rs?
./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/string_to_string.rs 2>string_to_string.stderr && exit 1
sed -e "/= help: for/d" string_to_string.stderr > normalized.stderr
diff -u normalized.stderr tests/ui/string_to_string.stderr
./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/char_lit_as_u8.rs 2>char_lit_as_u8.stderr && exit 1
sed -e "/= help: for/d" char_lit_as_u8.stderr > normalized.stderr
diff -u normalized.stderr tests/ui/char_lit_as_u8.stderr
# make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same
SYSROOT=$(rustc --print sysroot)

View file

@ -6,7 +6,105 @@ document.
## Unreleased / Beta / In Rust Nightly
[03a5b6b9...master](https://github.com/rust-lang/rust-clippy/compare/03a5b6b9...master)
[4ef75291...master](https://github.com/rust-lang/rust-clippy/compare/4ef75291...master)
## Rust 1.89
Current stable, released 2025-08-07
[View all 137 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-05-01T16%3A52%3A57Z..2025-06-13T08%3A33%3A27Z+base%3Amaster)
### New Lints
* Added [`coerce_container_to_any`] to `nursery` [#14812](https://github.com/rust-lang/rust-clippy/pull/14812)
* Added [`ip_constant`] to `pedantic` [#14878](https://github.com/rust-lang/rust-clippy/pull/14878)
* Added [`infallible_try_from`] to `suspicious` [#14813](https://github.com/rust-lang/rust-clippy/pull/14813)
* Added [`doc_suspicious_footnotes`] to `suspicious` [#14708](https://github.com/rust-lang/rust-clippy/pull/14708)
* Added [`pointer_format`] to `restriction` [#14792](https://github.com/rust-lang/rust-clippy/pull/14792)
* Added [`useless_concat`] to `complexity` [#13829](https://github.com/rust-lang/rust-clippy/pull/13829)
* Added [`cloned_ref_to_slice_refs`] to `perf` [#14284](https://github.com/rust-lang/rust-clippy/pull/14284)
* Added [`confusing_method_to_numeric_cast`] to `suspicious` [#13979](https://github.com/rust-lang/rust-clippy/pull/13979)
### Moves and Deprecations
* Removed superseded lints: `transmute_float_to_int`, `transmute_int_to_char`,
`transmute_int_to_float`, `transmute_num_to_bytes` (now in rustc)
[#14703](https://github.com/rust-lang/rust-clippy/pull/14703)
### Enhancements
* [`module_name_repetitions`] added `allow_exact_repetitions` configuration option
[#14261](https://github.com/rust-lang/rust-clippy/pull/14261)
* [`missing_docs_in_private_items`] added `allow_unused` config for underscored fields
[#14453](https://github.com/rust-lang/rust-clippy/pull/14453)
* [`unnecessary_unwrap`] fixed being emitted twice in closure
[#14763](https://github.com/rust-lang/rust-clippy/pull/14763)
* [`needless_return`] lint span no longer wraps to previous line
[#14790](https://github.com/rust-lang/rust-clippy/pull/14790)
* [`trivial-copy-size-limit`] now defaults to `target_pointer_width`
[#13319](https://github.com/rust-lang/rust-clippy/pull/13319)
* [`integer_division`] fixed false negative for NonZero denominators
[#14664](https://github.com/rust-lang/rust-clippy/pull/14664)
* [`arbitrary_source_item_ordering`] no longer lints inside items with `#[repr]` attribute
[#14610](https://github.com/rust-lang/rust-clippy/pull/14610)
* [`excessive_precision`] no longer triggers on exponent with leading zeros
[#14824](https://github.com/rust-lang/rust-clippy/pull/14824)
* [`to_digit_is_some`] no longer lints in const contexts when MSRV is below 1.87
[#14771](https://github.com/rust-lang/rust-clippy/pull/14771)
### False Positive Fixes
* [`std_instead_of_core`] fixed FP when part of the `use` cannot be replaced
[#15016](https://github.com/rust-lang/rust-clippy/pull/15016)
* [`unused_unit`] fixed FP for `Fn` bounds
[#14962](https://github.com/rust-lang/rust-clippy/pull/14962)
* [`unnecessary_debug_formatting`] fixed FP inside `Debug` impl
[#14955](https://github.com/rust-lang/rust-clippy/pull/14955)
* [`assign_op_pattern`] fixed FP on unstable const trait
[#14886](https://github.com/rust-lang/rust-clippy/pull/14886)
* [`useless_conversion`] fixed FP when using `.into_iter().any()`
[#14800](https://github.com/rust-lang/rust-clippy/pull/14800)
* [`collapsible_if`] fixed FP on block stmt before expr
[#14730](https://github.com/rust-lang/rust-clippy/pull/14730)
* [`manual_unwrap_or_default`] fixed FP on ref binding
[#14731](https://github.com/rust-lang/rust-clippy/pull/14731)
* [`unused_async`] fixed FP on default impl
[#14720](https://github.com/rust-lang/rust-clippy/pull/14720)
* [`manual_slice_fill`] fixed FP on `IndexMut` overload
[#14719](https://github.com/rust-lang/rust-clippy/pull/14719)
* [`unnecessary_to_owned`] fixed FP when map key is a reference
[#14834](https://github.com/rust-lang/rust-clippy/pull/14834)
### ICE Fixes
* [`mutable_key_type`] fixed ICE when infinitely associated generic types are used
[#14965](https://github.com/rust-lang/rust-clippy/pull/14965)
* [`zero_sized_map_values`] fixed ICE while computing type layout
[#14837](https://github.com/rust-lang/rust-clippy/pull/14837)
* [`useless_asref`] fixed ICE on trait method
[#14830](https://github.com/rust-lang/rust-clippy/pull/14830)
* [`manual_slice_size_calculation`] fixed ICE in suggestion and triggers in `const` context
[#14804](https://github.com/rust-lang/rust-clippy/pull/14804)
* [`missing_const_for_fn`]: fix ICE with some compilation options
[#14776](https://github.com/rust-lang/rust-clippy/pull/14776)
### Documentation Improvements
* [`manual_contains`] improved documentation wording
[#14917](https://github.com/rust-lang/rust-clippy/pull/14917)
### Performance improvements
* [`strlen_on_c_strings`] optimized by 99.75% (31M → 76k instructions)
[#15043](https://github.com/rust-lang/rust-clippy/pull/15043)
* Reduced documentation lints execution time by 85% (7.5% → 1% of total runtime)
[#14870](https://github.com/rust-lang/rust-clippy/pull/14870)
* [`unit_return_expecting_ord`] optimized to reduce binder instantiation from 95k to 10k calls
[#14905](https://github.com/rust-lang/rust-clippy/pull/14905)
* [`doc_markdown`] optimized by 50%
[#14693](https://github.com/rust-lang/rust-clippy/pull/14693)
* Refactor and speed up `cargo dev fmt`
[#14638](https://github.com/rust-lang/rust-clippy/pull/14638)
## Rust 1.88
@ -6260,6 +6358,7 @@ Released 2018-09-13
[`pointers_in_nomem_asm_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointers_in_nomem_asm_block
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
[`possible_missing_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_else
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
[`precedence_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence_bits
[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl

View file

@ -199,26 +199,34 @@ currently. Between writing new lints, fixing issues, reviewing pull requests and
responding to issues there may not always be enough time to stay on top of it
all.
Our highest priority is fixing [ICEs][I-ICE] and [bugs][C-bug], for example
an ICE in a popular crate that many other crates depend on. We don't
want Clippy to crash on your code and we want it to be as reliable as the
suggestions from Rust compiler errors.
To find things to fix, go to the [tracking issue][tracking_issue], find an issue that you like,
and claim it with `@rustbot claim`.
We have prioritization labels and a sync-blocker label, which are described below.
- [P-low][p-low]: Requires attention (fix/response/evaluation) by a team member but isn't urgent.
- [P-medium][p-medium]: Should be addressed by a team member until the next sync.
- [P-high][p-high]: Should be immediately addressed and will require an out-of-cycle sync or a backport.
- [L-sync-blocker][l-sync-blocker]: An issue that "blocks" a sync.
Or rather: before the sync this should be addressed,
e.g. by removing a lint again, so it doesn't hit beta/stable.
As a general metric and always taking into account your skill and knowledge level, you can use this guide:
- 🟥 [ICEs][search_ice], these are compiler errors that causes Clippy to panic and crash. Usually involves high-level
debugging, sometimes interacting directly with the upstream compiler. Difficult to fix but a great challenge that
improves a lot developer workflows!
- 🟧 [Suggestion causes bug][sugg_causes_bug], Clippy suggested code that changed logic in some silent way.
Unacceptable, as this may have disastrous consequences. Easier to fix than ICEs
- 🟨 [Suggestion causes error][sugg_causes_error], Clippy suggested code snippet that caused a compiler error
when applied. We need to make sure that Clippy doesn't suggest using a variable twice at the same time or similar
easy-to-happen occurrences.
- 🟩 [False positives][false_positive], a lint should not have fired, the easiest of them all, as this is "just"
identifying the root of a false positive and making an exception for those cases.
Note that false negatives do not have priority unless the case is very clear, as they are a feature-request in a
trench coat.
[triage]: https://forge.rust-lang.org/release/triage-procedure.html
[I-ICE]: https://github.com/rust-lang/rust-clippy/labels/I-ICE
[C-bug]: https://github.com/rust-lang/rust-clippy/labels/C-bug
[p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low
[p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium
[p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high
[l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker
[search_ice]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22I-ICE%22
[sugg_causes_bug]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-bug
[sugg_causes_error]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-error%20
[false_positive]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-false-positive
[tracking_issue]: https://github.com/rust-lang/rust-clippy/issues/15086
## Contributions

View file

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

View file

@ -555,7 +555,7 @@ default configuration of Clippy. By default, any configuration will replace the
* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]`
---
**Affected lints:**

View file

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

View file

@ -44,7 +44,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"WebP", "OpenExr", "YCbCr", "sRGB",
"TensorFlow",
"TrueType",
"iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD",
"iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "NixOS",
"TeX", "LaTeX", "BibTeX", "BibLaTeX",
"MinGW",
"CamelCase",

View file

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

View file

@ -2,8 +2,7 @@ use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv};
use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
use rustc_hir::RustcVersion;
use rustc_hir::{HirId, Lit};
use rustc_hir::{HirId, Lit, RustcVersion};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, symbol};

View file

@ -2,6 +2,7 @@ use super::DUPLICATED_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_then;
use itertools::Itertools;
use rustc_ast::{Attribute, MetaItem};
use rustc_ast_pretty::pprust::path_to_string;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::EarlyContext;
use rustc_span::{Span, Symbol, sym};
@ -35,31 +36,38 @@ fn check_duplicated_attr(
if attr.span.from_expansion() {
return;
}
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
return;
}
if let Some(direct_parent) = parent.last()
&& *direct_parent == sym::cfg_trace
&& [sym::all, sym::not, sym::any].contains(&name)
{
// FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one
// level `cfg`, we leave.
return;
let attr_path = if let Some(ident) = attr.ident() {
ident.name
} else {
Symbol::intern(&path_to_string(&attr.path))
};
if let Some(ident) = attr.ident() {
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason
{
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
return;
}
if let Some(direct_parent) = parent.last()
&& *direct_parent == sym::cfg_trace
&& [sym::all, sym::not, sym::any].contains(&name)
{
// FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one
// level `cfg`, we leave.
return;
}
}
if let Some(value) = attr.value_str() {
emit_if_duplicated(
cx,
attr,
attr_paths,
format!("{}:{name}={value}", parent.iter().join(":")),
format!("{}:{attr_path}={value}", parent.iter().join(":")),
);
} else if let Some(sub_attrs) = attr.meta_item_list() {
parent.push(name);
parent.push(attr_path);
for sub_attr in sub_attrs {
if let Some(meta) = sub_attr.meta_item() {
check_duplicated_attr(cx, meta, attr_paths, parent);
@ -67,7 +75,7 @@ fn check_duplicated_attr(
}
parent.pop();
} else {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.iter().join(":")));
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{attr_path}", parent.iter().join(":")));
}
}

View file

@ -1,8 +1,7 @@
use super::INLINE_ALWAYS;
use clippy_utils::diagnostics::span_lint;
use rustc_hir::attrs::{AttributeKind, InlineAttr};
use rustc_hir::find_attr;
use rustc_hir::Attribute;
use rustc_hir::{Attribute, find_attr};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::symbol::Symbol;

View file

@ -1,6 +1,5 @@
use rustc_hir::attrs::{AttributeKind, ReprAttr};
use rustc_hir::find_attr;
use rustc_hir::Attribute;
use rustc_hir::{Attribute, find_attr};
use rustc_lint::LateContext;
use rustc_span::Span;

View file

@ -100,8 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInConditions {
cond.span,
BRACED_EXPR_MESSAGE,
"try",
snippet_block_with_applicability(cx, ex.span, "..", Some(expr.span), &mut applicability)
.to_string(),
snippet_block_with_applicability(cx, ex.span, "..", Some(expr.span), &mut applicability),
applicability,
);
}

View file

@ -7,10 +7,9 @@ use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{eq_expr_value, sym};
use rustc_ast::ast::LitKind;
use rustc_hir::RustcVersion;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, RustcVersion, UnOp};
use rustc_lint::{LateContext, LateLintPass, Level};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;

View file

@ -25,6 +25,12 @@ pub(super) fn check(
return;
}
// If the `as` is from a macro and the casting type is from macro input, whether it is lossless is
// dependent on the input
if expr.span.from_expansion() && !cast_to_hir.span.eq_ctxt(expr.span) {
return;
}
span_lint_and_then(
cx,
CAST_LOSSLESS,

View file

@ -807,7 +807,7 @@ declare_clippy_lint! {
/// ```no_run
/// let _ = u16::MAX as usize;
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.89.0"]
pub CONFUSING_METHOD_TO_NUMERIC_CAST,
suspicious,
"casting a primitive method pointer to any integer type"

View file

@ -37,7 +37,7 @@ declare_clippy_lint! {
/// let data_ref = &data;
/// take_slice(slice::from_ref(data_ref));
/// ```
#[clippy::version = "1.87.0"]
#[clippy::version = "1.89.0"]
pub CLONED_REF_TO_SLICE_REFS,
perf,
"cloning a reference for slice references"

View file

@ -41,7 +41,7 @@ declare_clippy_lint! {
/// // Succeeds since we have a &dyn Any to the inner u32!
/// assert_eq!(dyn_any_of_u32.downcast_ref::<u32>(), Some(&0u32));
/// ```
#[clippy::version = "1.88.0"]
#[clippy::version = "1.89.0"]
pub COERCE_CONTAINER_TO_ANY,
nursery,
"coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended"

View file

@ -35,7 +35,7 @@ declare_clippy_lint! {
/// * [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
#[clippy::version = "1.35.0"]
pub COGNITIVE_COMPLEXITY,
nursery,
restriction,
"functions that should be split up into multiple functions",
@eval_always = true
}

View file

@ -136,6 +136,9 @@ impl CollapsibleIf {
return;
}
// Peel off any parentheses.
let (_, else_block_span, _) = peel_parens(cx.tcx.sess.source_map(), else_.span);
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
@ -152,7 +155,7 @@ impl CollapsibleIf {
if requires_space { " " } else { "" },
snippet_block_with_applicability(
cx,
else_.span,
else_block_span,
"..",
Some(else_block.span),
&mut applicability
@ -187,7 +190,8 @@ impl CollapsibleIf {
.with_leading_whitespace(cx)
.into_span()
};
let inner_if = inner.span.split_at(2).0;
let (paren_start, inner_if_span, paren_end) = peel_parens(cx.tcx.sess.source_map(), inner.span);
let inner_if = inner_if_span.split_at(2).0;
let mut sugg = vec![
// Remove the outer then block `{`
(then_open_bracket, String::new()),
@ -196,6 +200,17 @@ impl CollapsibleIf {
// Replace inner `if` by `&&`
(inner_if, String::from("&&")),
];
if !paren_start.is_empty() {
// Remove any leading parentheses '('
sugg.push((paren_start, String::new()));
}
if !paren_end.is_empty() {
// Remove any trailing parentheses ')'
sugg.push((paren_end, String::new()));
}
sugg.extend(parens_around(check));
sugg.extend(parens_around(check_inner));
@ -285,3 +300,37 @@ fn span_extract_keyword(sm: &SourceMap, span: Span, keyword: &str) -> Option<Spa
})
.next()
}
/// Peel the parentheses from an `if` expression, e.g. `((if true {} else {}))`.
fn peel_parens(sm: &SourceMap, mut span: Span) -> (Span, Span, Span) {
use crate::rustc_span::Pos;
let start = span.shrink_to_lo();
let end = span.shrink_to_hi();
let snippet = sm.span_to_snippet(span).unwrap();
if let Some((trim_start, _, trim_end)) = peel_parens_str(&snippet) {
let mut data = span.data();
data.lo = data.lo + BytePos::from_usize(trim_start);
data.hi = data.hi - BytePos::from_usize(trim_end);
span = data.span();
}
(start.with_hi(span.lo()), span, end.with_lo(span.hi()))
}
fn peel_parens_str(snippet: &str) -> Option<(usize, &str, usize)> {
let trimmed = snippet.trim();
if !(trimmed.starts_with('(') && trimmed.ends_with(')')) {
return None;
}
let trim_start = (snippet.len() - snippet.trim_start().len()) + 1;
let trim_end = (snippet.len() - snippet.trim_end().len()) + 1;
let inner = snippet.get(trim_start..snippet.len() - trim_end)?;
Some(match peel_parens_str(inner) {
None => (trim_start, inner, trim_end),
Some((start, inner, end)) => (trim_start + start, inner, trim_end + end),
})
}

View file

@ -235,9 +235,9 @@ fn lint_branches_sharing_code<'tcx>(
let cond_snippet = reindent_multiline(&snippet(cx, cond_span, "_"), false, None);
let cond_indent = indent_of(cx, cond_span);
let moved_snippet = reindent_multiline(&snippet(cx, span, "_"), true, None);
let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{";
let suggestion = moved_snippet + "\n" + &cond_snippet + "{";
let suggestion = reindent_multiline(&suggestion, true, cond_indent);
(replace_span, suggestion.to_string())
(replace_span, suggestion)
});
let end_suggestion = res.end_span(last_block, sm).map(|span| {
let moved_snipped = reindent_multiline(&snippet(cx, span, "_"), true, None);
@ -253,7 +253,7 @@ fn lint_branches_sharing_code<'tcx>(
.then_some(range.start - 4..range.end)
})
.map_or(span, |range| range.with_ctxt(span.ctxt()));
(span, suggestion.to_string())
(span, suggestion.clone())
});
let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) {

View file

@ -178,6 +178,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO,
crate::format_push_string::FORMAT_PUSH_STRING_INFO,
crate::formatting::POSSIBLE_MISSING_COMMA_INFO,
crate::formatting::POSSIBLE_MISSING_ELSE_INFO,
crate::formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING_INFO,
crate::formatting::SUSPICIOUS_ELSE_FORMATTING_INFO,
crate::formatting::SUSPICIOUS_UNARY_OP_FORMATTING_INFO,
@ -690,7 +691,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::strings::STRING_FROM_UTF8_AS_BYTES_INFO,
crate::strings::STRING_LIT_AS_BYTES_INFO,
crate::strings::STRING_SLICE_INFO,
crate::strings::STRING_TO_STRING_INFO,
crate::strings::STR_TO_STRING_INFO,
crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_hir::attrs::{AttributeKind, ReprAttr};
use rustc_hir::find_attr;
use rustc_hir::{HirId, Item, ItemKind};
use rustc_hir::{HirId, Item, ItemKind, find_attr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{self, FieldDef};

View file

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

View file

@ -19,52 +19,52 @@ pub fn check(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragm
}
fn warn_if_broken_link(cx: &LateContext<'_>, bl: &PullDownBrokenLink<'_>, doc: &str, fragments: &[DocFragment]) {
if let Some((span, _)) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments) {
let mut len = 0;
let mut len = 0;
// grab raw link data
let (_, raw_link) = doc.split_at(bl.span.start);
// grab raw link data
let (_, raw_link) = doc.split_at(bl.span.start);
// strip off link text part
let raw_link = match raw_link.split_once(']') {
None => return,
Some((prefix, suffix)) => {
len += prefix.len() + 1;
suffix
},
};
// strip off link text part
let raw_link = match raw_link.split_once(']') {
None => return,
Some((prefix, suffix)) => {
len += prefix.len() + 1;
suffix
},
};
let raw_link = match raw_link.split_once('(') {
None => return,
Some((prefix, suffix)) => {
if !prefix.is_empty() {
// there is text between ']' and '(' chars, so it is not a valid link
return;
}
len += prefix.len() + 1;
suffix
},
};
let raw_link = match raw_link.split_once('(') {
None => return,
Some((prefix, suffix)) => {
if !prefix.is_empty() {
// there is text between ']' and '(' chars, so it is not a valid link
return;
}
len += prefix.len() + 1;
suffix
},
};
if raw_link.starts_with("(http") {
// reduce chances of false positive reports
// by limiting this checking only to http/https links.
if raw_link.starts_with("(http") {
// reduce chances of false positive reports
// by limiting this checking only to http/https links.
return;
}
for c in raw_link.chars() {
if c == ')' {
// it is a valid link
return;
}
for c in raw_link.chars() {
if c == ')' {
// it is a valid link
return;
}
if c == '\n' {
report_broken_link(cx, span, len);
break;
}
len += 1;
if c == '\n'
&& let Some((span, _)) = source_span_for_markdown_range(cx.tcx, doc, &bl.span, fragments)
{
report_broken_link(cx, span, len);
break;
}
len += 1;
}
}

View file

@ -662,7 +662,7 @@ declare_clippy_lint! {
/// /// [^1]: defined here
/// fn my_fn() {}
/// ```
#[clippy::version = "1.88.0"]
#[clippy::version = "1.89.0"]
pub DOC_SUSPICIOUS_FOOTNOTES,
suspicious,
"looks like a link or footnote ref, but with no definition"

View file

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::AttrStyle;
use rustc_ast::token::CommentKind;
use rustc_hir::attrs::AttributeKind;
use rustc_errors::Applicability;
use rustc_hir::Attribute;
use rustc_hir::attrs::AttributeKind;
use rustc_lint::LateContext;
use rustc_span::Span;

View file

@ -1,5 +1,5 @@
use rustc_hir::attrs::AttributeKind;
use rustc_errors::Applicability;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{Attribute, Item, ItemKind};
use rustc_lint::LateContext;

View file

@ -93,11 +93,11 @@ impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VA
impl LateLintPass<'_> for EmptyWithBrackets {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
// FIXME: handle `struct $name {}`
if let ItemKind::Struct(ident, _, var_data) = &item.kind
if let ItemKind::Struct(ident, generics, var_data) = &item.kind
&& !item.span.from_expansion()
&& !ident.span.from_expansion()
&& has_brackets(var_data)
&& let span_after_ident = item.span.with_lo(ident.span.hi())
&& let span_after_ident = item.span.with_lo(generics.span.hi())
&& has_no_fields(cx, var_data, span_after_ident)
{
span_lint_and_then(

View file

@ -7,10 +7,9 @@ use clippy_utils::{
get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id,
};
use rustc_abi::ExternAbi;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_errors::Applicability;
use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind, find_attr};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{

View file

@ -1,9 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::indent_of;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{Item, ItemKind, find_attr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

View file

@ -148,7 +148,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
.into();
suggestion = match suggestion {
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op),
};
}

View file

@ -17,12 +17,11 @@ use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
FormatPlaceholder, FormatTrait,
};
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::{find_attr,RustcVersion};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{Expr, ExprKind, LangItem, RustcVersion, find_attr};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Upcast};
@ -223,7 +222,7 @@ declare_clippy_lint! {
/// ```
///
/// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
#[clippy::version = "1.88.0"]
#[clippy::version = "1.89.0"]
pub POINTER_FORMAT,
restriction,
"formatting a pointer"

View file

@ -91,6 +91,31 @@ declare_clippy_lint! {
"suspicious formatting of `else`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for an `if` expression followed by either a block or another `if` that
/// looks like it should have an `else` between them.
///
/// ### Why is this bad?
/// This is probably some refactoring remnant, even if the code is correct, it
/// might look confusing.
///
/// ### Example
/// ```rust,ignore
/// if foo {
/// } { // looks like an `else` is missing here
/// }
///
/// if foo {
/// } if bar { // looks like an `else` is missing here
/// }
/// ```
#[clippy::version = "1.90.0"]
pub POSSIBLE_MISSING_ELSE,
suspicious,
"possibly missing `else`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for possible missing comma in an array. It lints if
@ -116,6 +141,7 @@ declare_lint_pass!(Formatting => [
SUSPICIOUS_ASSIGNMENT_FORMATTING,
SUSPICIOUS_UNARY_OP_FORMATTING,
SUSPICIOUS_ELSE_FORMATTING,
POSSIBLE_MISSING_ELSE,
POSSIBLE_MISSING_COMMA
]);
@ -307,7 +333,7 @@ fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
POSSIBLE_MISSING_ELSE,
else_span,
format!("this looks like {looks_like} but the `else` is missing"),
None,

View file

@ -1,4 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt as _;
use itertools::Itertools;
use rustc_errors::Applicability;
use rustc_hir::Item;
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -81,6 +83,14 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes {
"turn these into doc comments by removing one `/`"
};
// If the comment contains a bare CR (not followed by a LF), do not propose an auto-fix
// as bare CR are not allowed in doc comments.
if span.check_source_text(cx, contains_bare_cr) {
diag.help(msg)
.note("bare CR characters are not allowed in doc comments");
return;
}
diag.multipart_suggestion(
msg,
bad_comments
@ -97,3 +107,8 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes {
}
}
}
/// Checks if `text` contains any CR not followed by a LF
fn contains_bare_cr(text: &str) -> bool {
text.bytes().tuple_windows().any(|(a, b)| a == b'\r' && b != b'\n')
}

View file

@ -14,7 +14,7 @@ use clippy_utils::source::snippet_indent;
use clippy_utils::ty::is_must_use_ty;
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{return_ty, trait_ref_of_method};
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::find_attr;
use rustc_span::Symbol;
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;

View file

@ -81,7 +81,7 @@ impl LateLintPass<'_> for IfNotElse {
e.span,
msg,
"try",
make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)).to_string(),
make_sugg(cx, &cond.kind, cond_inner.span, els.span, "..", Some(e.span)),
Applicability::MachineApplicable,
),
_ => span_lint_and_help(cx, IF_NOT_ELSE, e.span, msg, None, help),

View file

@ -2,14 +2,13 @@ use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::Msrv;
use clippy_utils::{is_in_const_context, is_in_test};
use rustc_hir::{RustcVersion, StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::DefKind;
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath};
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath, RustcVersion, StabilityLevel, StableSince};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::def_id::{CrateNum, DefId};
use rustc_span::{ExpnKind, Span, sym};
declare_clippy_lint! {
@ -83,16 +82,22 @@ pub struct IncompatibleMsrv {
msrv: Msrv,
availability_cache: FxHashMap<(DefId, bool), Availability>,
check_in_tests: bool,
core_crate: Option<CrateNum>,
}
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
impl IncompatibleMsrv {
pub fn new(conf: &'static Conf) -> Self {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv,
availability_cache: FxHashMap::default(),
check_in_tests: conf.check_incompatible_msrv_in_tests,
core_crate: tcx
.crates(())
.iter()
.find(|krate| tcx.crate_name(**krate) == sym::core)
.copied(),
}
}
@ -140,23 +145,16 @@ impl IncompatibleMsrv {
// We don't check local items since their MSRV is supposed to always be valid.
return;
}
if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind {
let expn_data = span.ctxt().outer_expn_data();
if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = expn_data.kind {
// Desugared expressions get to cheat and stability is ignored.
// Intentionally not using `.from_expansion()`, since we do still care about macro expansions
return;
}
// Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the
// macros may have existed prior to the checked MSRV, but their expansion with a recent compiler
// might use recent functions or methods. Compiling with an older compiler would not use those.
if span.from_expansion()
&& cx.tcx.crate_name(def_id.krate) == sym::core
&& span
.ctxt()
.outer_expn_data()
.macro_def_id
.is_some_and(|def_id| cx.tcx.crate_name(def_id.krate) == sym::core)
{
if Some(def_id.krate) == self.core_crate && expn_data.macro_def_id.map(|did| did.krate) == self.core_crate {
return;
}

View file

@ -4,7 +4,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLet;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::is_copy;
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local, sym};
use clippy_utils::{is_lint_allowed, path_to_local};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -71,7 +71,7 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
&& (!expr.span.from_expansion() || is_expn_of(expr.span, sym::if_chain).is_some())
&& !expr.span.from_expansion()
&& !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
&& let found_slices = find_slice_values(cx, let_pat)
&& !found_slices.is_empty()

View file

@ -13,7 +13,7 @@ declare_clippy_lint! {
///
/// ### Why is this bad?
///
/// Infalliable conversions should be implemented via `From` with the blanket conversion.
/// Infallible conversions should be implemented via `From` with the blanket conversion.
///
/// ### Example
/// ```no_run
@ -35,7 +35,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
#[clippy::version = "1.88.0"]
#[clippy::version = "1.89.0"]
pub INFALLIBLE_TRY_FROM,
suspicious,
"TryFrom with infallible Error type"
@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom {
cx,
INFALLIBLE_TRY_FROM,
span,
"infallible TryFrom impl; consider implementing From, instead",
"infallible TryFrom impl; consider implementing From instead",
);
}
}

View file

@ -1,9 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::DiagExt;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_errors::Applicability;
use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{TraitFn, TraitItem, TraitItemKind, find_attr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use clippy_utils::source::{IntoSpan, SpanRangeExt};
use rustc_errors::Applicability;
use rustc_hir::{LetStmt, TyKind};
use rustc_lint::{LateContext, LateLintPass};
@ -30,9 +31,15 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
if let Some(ty) = local.ty // Ensure that it has a type defined
&& let TyKind::Infer(()) = &ty.kind // that type is '_'
&& local.span.eq_ctxt(ty.span)
&& !local.span.in_external_macro(cx.tcx.sess.source_map())
&& let sm = cx.tcx.sess.source_map()
&& !local.span.in_external_macro(sm)
&& !is_from_proc_macro(cx, ty)
{
let span_to_remove = sm
.span_extend_to_prev_char_before(ty.span, ':', true)
.with_leading_whitespace(cx)
.into_span();
span_lint_and_then(
cx,
LET_WITH_TYPE_UNDERSCORE,
@ -40,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
"variable declared with type underscore",
|diag| {
diag.span_suggestion_verbose(
ty.span.with_lo(local.pat.span.hi()),
span_to_remove,
"remove the explicit type `_` declaration",
"",
Applicability::MachineApplicable,

View file

@ -522,7 +522,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod));
store.register_late_pass(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf)));
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(unit_types::UnitTypes::new(format_args.clone())));
store.register_late_pass(move |_| Box::new(loops::Loops::new(conf)));
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
store.register_late_pass(move |_| Box::new(lifetimes::Lifetimes::new(conf)));
@ -663,7 +664,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
store.register_late_pass(|_| Box::new(empty_drop::EmptyDrop));
store.register_late_pass(|_| Box::new(strings::StrToString));
store.register_late_pass(|_| Box::new(strings::StringToString));
store.register_late_pass(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues));
store.register_late_pass(|_| Box::<vec_init_then_push::VecInitThenPush>::default());
store.register_late_pass(|_| Box::new(redundant_slicing::RedundantSlicing));
@ -796,7 +796,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
store.register_late_pass(|_| Box::<unconditional_recursion::UnconditionalRecursion>::default());
store.register_late_pass(move |_| Box::new(pub_underscore_fields::PubUnderscoreFields::new(conf)));
store.register_late_pass(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(conf)));
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(conf)));
store.register_late_pass(move |tcx| Box::new(incompatible_msrv::IncompatibleMsrv::new(tcx, conf)));
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf)));

View file

@ -202,7 +202,7 @@ fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec<Span> {
.iter()
.skip_while(|inner| inner.hir_id != stmt.hir_id)
.map(stmt_source_span)
.chain(if let Some(e) = block.expr { vec![e.span] } else { vec![] })
.chain(block.expr.map(|e| e.span))
.collect();
}

View file

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use hir::def::{DefKind, Res};
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, AmbigArg};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{self as hir, AmbigArg, find_attr};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::Span;

View file

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{is_panic, root_macro_call};
use clippy_utils::{is_else_clause, is_parent_stmt, peel_blocks_with_stmt, span_extract_comment, sugg};
use clippy_utils::{higher, is_else_clause, is_parent_stmt, peel_blocks_with_stmt, span_extract_comment, sugg};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
@ -35,7 +35,7 @@ declare_lint_pass!(ManualAssert => [MANUAL_ASSERT]);
impl<'tcx> LateLintPass<'tcx> for ManualAssert {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::If(cond, then, None) = expr.kind
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr)
&& !matches!(cond.kind, ExprKind::Let(_))
&& !expr.span.from_expansion()
&& let then = peel_blocks_with_stmt(then)
@ -51,19 +51,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert {
&& !is_else_clause(cx.tcx, expr)
{
let mut applicability = Applicability::MachineApplicable;
let cond = cond.peel_drop_temps();
let mut comments = span_extract_comment(cx.sess().source_map(), expr.span);
if !comments.is_empty() {
comments += "\n";
}
let (cond, not) = match cond.kind {
ExprKind::Unary(UnOp::Not, e) => (e, ""),
_ => (cond, "!"),
};
let cond_sugg =
sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability).maybe_paren();
let cond_sugg = !sugg::Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability);
let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" };
let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}");
let sugg = format!("assert!({cond_sugg}, {format_args_snip}){semicolon}");
// we show to the user the suggestion without the comments, but when applying the fix, include the
// comments in the block
span_lint_and_then(

View file

@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
};
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)).to_string();
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.multipart_suggestion(
"make the function `async` and return the output of the future directly",

View file

@ -4,12 +4,11 @@ use clippy_utils::is_doc_hidden;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_indent;
use itertools::Itertools;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData};
use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData, find_attr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;

View file

@ -1,11 +1,16 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::HirNode;
use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_context};
use clippy_utils::{is_refutable, peel_blocks};
use clippy_utils::source::{indent_of, reindent_multiline, snippet, snippet_block_with_context, snippet_with_context};
use clippy_utils::{is_expr_identity_of_pat, is_refutable, peel_blocks};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind};
use rustc_hir::def::Res;
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_path, walk_stmt};
use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Node, PatKind, Path, Stmt, StmtKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_span::{Span, Symbol};
use super::MATCH_SINGLE_BINDING;
@ -26,9 +31,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
let match_body = peel_blocks(arms[0].body);
let mut app = Applicability::MaybeIncorrect;
let ctxt = expr.span.ctxt();
let mut snippet_body = snippet_block_with_context(cx, match_body.span, ctxt, "..", Some(expr.span), &mut app)
.0
.to_string();
let mut snippet_body = snippet_block_with_context(cx, match_body.span, ctxt, "..", Some(expr.span), &mut app).0;
// Do we need to add ';' to suggestion ?
if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id)
@ -50,10 +53,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
cx,
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
snippet_body,
&mut app,
Some(span),
true,
is_var_binding_used_later(cx, expr, &arms[0]),
);
span_lint_and_sugg(
@ -78,15 +82,28 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
snippet_with_context(cx, pat_span, ctxt, "..", &mut app).0
),
),
None if is_expr_identity_of_pat(cx, arms[0].pat, ex, false) => {
span_lint_and_sugg(
cx,
MATCH_SINGLE_BINDING,
expr.span,
"this match could be replaced by its body itself",
"consider using the match body instead",
snippet_body,
Applicability::MachineApplicable,
);
return;
},
None => {
let sugg = sugg_with_curlies(
cx,
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
snippet_body,
&mut app,
None,
true,
is_var_binding_used_later(cx, expr, &arms[0]),
);
(expr.span, sugg)
},
@ -108,10 +125,11 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
cx,
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
snippet_body,
&mut app,
None,
false,
true,
);
span_lint_and_sugg(
@ -139,6 +157,125 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
}
}
struct VarBindingVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<Symbol>,
}
impl<'tcx> Visitor<'tcx> for VarBindingVisitor<'_, 'tcx> {
type Result = ControlFlow<()>;
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) -> Self::Result {
if let Res::Local(_) = path.res
&& let [segment] = path.segments
&& self.identifiers.contains(&segment.ident.name)
{
return ControlFlow::Break(());
}
walk_path(self, path)
}
fn visit_block(&mut self, block: &'tcx Block<'tcx>) -> Self::Result {
let before = self.identifiers.clone();
walk_block(self, block)?;
self.identifiers = before;
ControlFlow::Continue(())
}
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'tcx>) -> Self::Result {
if let StmtKind::Let(let_stmt) = stmt.kind {
if let Some(init) = let_stmt.init {
self.visit_expr(init)?;
}
let_stmt.pat.each_binding(|_, _, _, ident| {
self.identifiers.remove(&ident.name);
});
}
walk_stmt(self, stmt)
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result {
match expr.kind {
ExprKind::If(
Expr {
kind: ExprKind::Let(let_expr),
..
},
then,
else_,
) => {
self.visit_expr(let_expr.init)?;
let before = self.identifiers.clone();
let_expr.pat.each_binding(|_, _, _, ident| {
self.identifiers.remove(&ident.name);
});
self.visit_expr(then)?;
self.identifiers = before;
if let Some(else_) = else_ {
self.visit_expr(else_)?;
}
ControlFlow::Continue(())
},
ExprKind::Closure(closure) => {
let body = self.cx.tcx.hir_body(closure.body);
let before = self.identifiers.clone();
for param in body.params {
param.pat.each_binding(|_, _, _, ident| {
self.identifiers.remove(&ident.name);
});
}
self.visit_expr(body.value)?;
self.identifiers = before;
ControlFlow::Continue(())
},
ExprKind::Match(expr, arms, _) => {
self.visit_expr(expr)?;
for arm in arms {
let before = self.identifiers.clone();
arm.pat.each_binding(|_, _, _, ident| {
self.identifiers.remove(&ident.name);
});
if let Some(guard) = arm.guard {
self.visit_expr(guard)?;
}
self.visit_expr(arm.body)?;
self.identifiers = before;
}
ControlFlow::Continue(())
},
_ => walk_expr(self, expr),
}
}
}
fn is_var_binding_used_later(cx: &LateContext<'_>, expr: &Expr<'_>, arm: &Arm<'_>) -> bool {
let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) else {
return false;
};
let Node::Block(block) = cx.tcx.parent_hir_node(stmt.hir_id) else {
return false;
};
let mut identifiers = FxHashSet::default();
arm.pat.each_binding(|_, _, _, ident| {
identifiers.insert(ident.name);
});
let mut visitor = VarBindingVisitor { cx, identifiers };
block
.stmts
.iter()
.skip_while(|s| s.hir_id != stmt.hir_id)
.skip(1)
.any(|stmt| matches!(visitor.visit_stmt(stmt), ControlFlow::Break(())))
|| block
.expr
.is_some_and(|expr| matches!(visitor.visit_expr(expr), ControlFlow::Break(())))
}
/// Returns true if the `ex` match expression is in a local (`let`) or assign expression
fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<AssignmentExpr> {
if let Node::Expr(parent_arm_expr) = cx.tcx.parent_hir_node(ex.hir_id) {
@ -161,47 +298,66 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<Ass
None
}
fn expr_parent_requires_curlies<'a>(cx: &LateContext<'a>, match_expr: &Expr<'a>) -> bool {
fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool {
if let Node::Block(block) = cx.tcx.parent_hir_node(match_expr.hir_id) {
return block
.expr
.map_or_else(|| matches!(block.stmts, [_]), |_| block.stmts.is_empty());
}
false
}
fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool {
let parent = cx.tcx.parent_hir_node(match_expr.hir_id);
matches!(
parent,
Node::Expr(Expr {
kind: ExprKind::Closure { .. },
..
}) | Node::AnonConst(..)
if let Node::Expr(Expr {
kind: ExprKind::Closure(..) | ExprKind::Binary(..),
..
})
| Node::AnonConst(..) = parent
{
return true;
}
if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id)
&& let ExprKind::Match(..) = arm.body.kind
{
return true;
}
false
}
fn indent_of_nth_line(snippet: &str, nth: usize) -> Option<usize> {
snippet
.lines()
.nth(nth)
.and_then(|s| s.find(|c: char| !c.is_whitespace()))
}
fn reindent_snippet_if_in_block(snippet_body: &str, has_assignment: bool) -> String {
if has_assignment || !snippet_body.starts_with('{') {
return reindent_multiline(snippet_body, true, indent_of_nth_line(snippet_body, 1));
}
let snippet_body = snippet_body.trim_start_matches('{').trim_end_matches('}').trim();
reindent_multiline(
snippet_body,
false,
indent_of_nth_line(snippet_body, 0).map(|indent| indent.saturating_sub(4)),
)
}
#[expect(clippy::too_many_arguments)]
fn sugg_with_curlies<'a>(
cx: &LateContext<'a>,
(ex, match_expr): (&Expr<'a>, &Expr<'a>),
(bind_names, matched_vars): (Span, Span),
snippet_body: &str,
mut snippet_body: String,
applicability: &mut Applicability,
assignment: Option<Span>,
needs_var_binding: bool,
is_var_binding_used_later: bool,
) -> String {
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
if expr_parent_requires_curlies(cx, match_expr) {
cbrace_end = format!("\n{indent}}}");
// Fix body indent due to the closure
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
cbrace_start = format!("{{\n{indent}");
}
// If the parent is already an arm, and the body is another match statement,
// we need curly braces around suggestion
if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id)
&& let ExprKind::Match(..) = arm.body.kind
{
cbrace_end = format!("\n{indent}}}");
// Fix body indent due to the match
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
cbrace_start = format!("{{\n{indent}");
}
let assignment_str = assignment.map_or_else(String::new, |span| {
let mut s = snippet(cx, span, "..").to_string();
s.push_str(" = ");
@ -221,5 +377,17 @@ fn sugg_with_curlies<'a>(
.to_string()
};
let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new());
if !expr_in_nested_block(cx, match_expr)
&& ((needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies(cx, match_expr))
{
cbrace_end = format!("\n{indent}}}");
// Fix body indent due to the closure
indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
cbrace_start = format!("{{\n{indent}");
snippet_body = reindent_snippet_if_in_block(&snippet_body, !assignment_str.is_empty());
}
format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}")
}

View file

@ -112,9 +112,7 @@ fn report_single_pattern(
let (sugg, help) = if is_unit_expr(arm.body) {
(String::new(), "`match` expression can be removed")
} else {
let mut sugg = snippet_block_with_context(cx, arm.body.span, ctxt, "..", Some(expr.span), &mut app)
.0
.to_string();
let mut sugg = snippet_block_with_context(cx, arm.body.span, ctxt, "..", Some(expr.span), &mut app).0;
if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id)
&& let StmtKind::Expr(_) = stmt.kind
&& match arm.body.kind {
@ -127,7 +125,7 @@ fn report_single_pattern(
(sugg, "try")
};
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
diag.span_suggestion(expr.span, help, sugg.to_string(), app);
diag.span_suggestion(expr.span, help, sugg, app);
note(diag);
});
return;
@ -188,7 +186,7 @@ fn report_single_pattern(
};
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
diag.span_suggestion(expr.span, "try", sugg.to_string(), app);
diag.span_suggestion(expr.span, "try", sugg, app);
note(diag);
});
}

View file

@ -40,14 +40,12 @@ pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, re
}
/// Returns true if the named method can be used to clone the receiver.
/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call
/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g.,
/// `is_to_owned_like` in `unnecessary_to_owned.rs`.
pub fn is_clone_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: hir::def_id::DefId) -> bool {
match method_name {
sym::to_os_string => is_diag_item_method(cx, method_def_id, sym::OsStr),
sym::to_owned => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
sym::to_path_buf => is_diag_item_method(cx, method_def_id, sym::Path),
sym::to_string => is_diag_trait_item(cx, method_def_id, sym::ToString),
sym::to_vec => cx
.tcx
.impl_of_assoc(method_def_id)

View file

@ -1,7 +1,7 @@
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath, Ty, TyKind};
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_span::sym;
use smallvec::SmallVec;
@ -9,13 +9,8 @@ use smallvec::SmallVec;
use super::IP_CONSTANT;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) {
if let ExprKind::Path(QPath::TypeRelative(
Ty {
kind: TyKind::Path(QPath::Resolved(_, func_path)),
..
},
p,
)) = func.kind
if let ExprKind::Path(QPath::TypeRelative(ty, p)) = func.kind
&& let TyKind::Path(QPath::Resolved(_, func_path)) = ty.kind
&& p.ident.name == sym::new
&& let Some(func_def_id) = func_path.res.opt_def_id()
&& matches!(
@ -40,13 +35,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
_ => return,
};
let mut sugg = vec![(expr.span.with_lo(p.ident.span.lo()), constant_name.to_owned())];
let before_span = expr.span.shrink_to_lo().until(ty.span);
if !before_span.is_empty() {
// Remove everything before the type name
sugg.push((before_span, String::new()));
}
span_lint_and_then(cx, IP_CONSTANT, expr.span, "hand-coded well-known IP address", |diag| {
diag.span_suggestion_verbose(
expr.span.with_lo(p.ident.span.lo()),
"use",
constant_name,
Applicability::MachineApplicable,
);
diag.multipart_suggestion_verbose("use", sugg, Applicability::MachineApplicable);
});
}
}

View file

@ -2,14 +2,15 @@ use std::iter::once;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig};
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core, sym};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::HirId;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::Binder;
use rustc_span::Symbol;
use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS};
@ -32,24 +33,34 @@ impl IterType {
fn is_arg_ty_unified_in_fn<'tcx>(
cx: &LateContext<'tcx>,
fn_id: DefId,
fn_sig: ExprFnSig<'tcx>,
arg_id: HirId,
args: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
is_method: bool,
) -> bool {
let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity();
let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap();
let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder();
let Some(arg_ty_in_args) = fn_sig.input(arg_id_in_args).map(Binder::skip_binder) else {
return false;
};
cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| {
clause
.as_projection_clause()
.and_then(|p| p.map_bound(|p| p.term.as_type()).transpose())
.is_some_and(|ty| ty.skip_binder() == arg_ty_in_args)
}) || fn_sig
.inputs()
.iter()
.enumerate()
.any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args)))
fn_sig
.predicates_id()
.map(|def_id| cx.tcx.predicates_of(def_id))
.is_some_and(|generics| {
generics.predicates.iter().any(|(clause, _)| {
clause
.as_projection_clause()
.and_then(|p| p.map_bound(|p| p.term.as_type()).transpose())
.is_some_and(|ty| ty.skip_binder() == arg_ty_in_args)
})
})
|| (!is_method
&& fn_sig.input(arg_id_in_args).is_some_and(|binder| {
binder
.skip_binder()
.walk()
.any(|arg| arg.as_type() == Some(arg_ty_in_args))
}))
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, recv: &'tcx Expr<'tcx>) {
@ -70,25 +81,16 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method
let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) {
Some((Node::Expr(parent), child_id)) => match parent.kind {
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false,
ExprKind::Call(
Expr {
kind: ExprKind::Path(path),
hir_id,
..
},
args,
) => cx
ExprKind::Call(recv, args) => {
expr_sig(cx, recv).is_some_and(|fn_sig| is_arg_ty_unified_in_fn(cx, fn_sig, child_id, args, false))
},
ExprKind::MethodCall(_name, recv, args, _span) => cx
.typeck_results()
.qpath_res(path, *hir_id)
.opt_def_id()
.filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like())
.is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)),
ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn(
cx,
cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(),
child_id,
once(recv).chain(args.iter()),
),
.type_dependent_def_id(parent.hir_id)
.and_then(|def_id| ty_sig(cx, cx.tcx.type_of(def_id).instantiate_identity()))
.is_some_and(|fn_sig| {
is_arg_ty_unified_in_fn(cx, fn_sig, child_id, once(recv).chain(args.iter()), true)
}),
ExprKind::If(_, _, _)
| ExprKind::Match(_, _, _)
| ExprKind::Closure(_)
@ -96,7 +98,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method
| ExprKind::Break(_, _) => true,
_ => false,
},
Some((Node::Stmt(_) | Node::LetStmt(_), _)) => false,
Some((Node::LetStmt(let_stmt), _)) => let_stmt.ty.is_some(),
Some((Node::Stmt(_), _)) => false,
_ => true,
};

View file

@ -5450,7 +5450,7 @@ impl Methods {
implicit_clone::check(cx, name, expr, recv);
}
},
(sym::to_os_string | sym::to_path_buf | sym::to_vec, []) => {
(sym::to_os_string | sym::to_path_buf | sym::to_string | sym::to_vec, []) => {
implicit_clone::check(cx, name, expr, recv);
},
(sym::type_id, []) => {

View file

@ -109,10 +109,16 @@ pub(super) fn check<'a>(
);
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
match parent_expr.kind {
ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_paren(),
ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_paren(),
_ => binop,
if parent_expr.span.eq_ctxt(expr.span) {
match parent_expr.kind {
ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_paren(),
ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_paren(),
_ => binop,
}
} else {
// if our parent expr is created by a macro, then it should be the one taking care of
// parenthesising us if necessary
binop
}
} else {
binop

View file

@ -199,44 +199,50 @@ pub(super) fn check<'tcx>(
is_unstable: bool,
) {
match detect_lint(cx, expr, recv, arg) {
Some(LintTrigger::SortByKey(trigger)) => span_lint_and_sugg(
cx,
UNNECESSARY_SORT_BY,
expr.span,
"consider using `sort_by_key`",
"try",
format!(
"{}.sort{}_by_key(|{}| {})",
trigger.vec_name,
if is_unstable { "_unstable" } else { "" },
trigger.closure_arg,
if let Some(std_or_core) = std_or_core(cx)
&& trigger.reverse
{
format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body)
} else {
trigger.closure_body.to_string()
},
),
if trigger.reverse {
Applicability::MaybeIncorrect
Some(LintTrigger::SortByKey(trigger)) => {
let method = if is_unstable {
"sort_unstable_by_key"
} else {
Applicability::MachineApplicable
},
),
Some(LintTrigger::Sort(trigger)) => span_lint_and_sugg(
cx,
UNNECESSARY_SORT_BY,
expr.span,
"consider using `sort`",
"try",
format!(
"{}.sort{}()",
trigger.vec_name,
if is_unstable { "_unstable" } else { "" },
),
Applicability::MachineApplicable,
),
"sort_by_key"
};
span_lint_and_sugg(
cx,
UNNECESSARY_SORT_BY,
expr.span,
format!("consider using `{method}`"),
"try",
format!(
"{}.{}(|{}| {})",
trigger.vec_name,
method,
trigger.closure_arg,
if let Some(std_or_core) = std_or_core(cx)
&& trigger.reverse
{
format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body)
} else {
trigger.closure_body
},
),
if trigger.reverse {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
},
);
},
Some(LintTrigger::Sort(trigger)) => {
let method = if is_unstable { "sort_unstable" } else { "sort" };
span_lint_and_sugg(
cx,
UNNECESSARY_SORT_BY,
expr.span,
format!("consider using `{method}`"),
"try",
format!("{}.{}()", trigger.vec_name, method),
Applicability::MachineApplicable,
);
},
None => {},
}
}

View file

@ -621,8 +621,8 @@ fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id:
/// Returns true if the named method can be used to convert the receiver to its "owned"
/// representation.
fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool {
is_clone_like(cx, method_name, method_def_id)
|| is_cow_into_owned(cx, method_name, method_def_id)
is_cow_into_owned(cx, method_name, method_def_id)
|| (method_name != sym::to_string && is_clone_like(cx, method_name, method_def_id))
|| is_to_string_on_string_like(cx, call_expr, method_name, method_def_id)
}

View file

@ -4,10 +4,14 @@ use clippy_utils::is_from_proc_macro;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{Visitor, walk_item, walk_trait_item};
use rustc_hir::{GenericParamKind, HirId, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem, UsePath};
use rustc_hir::{
GenericParamKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, ItemLocalId, Node, Pat, PatKind, TraitItem,
UsePath,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::symbol::Ident;
use rustc_span::{Span, Symbol};
use std::borrow::Cow;
declare_clippy_lint! {
@ -32,6 +36,10 @@ declare_clippy_lint! {
/// let title = movie.title;
/// }
/// ```
///
/// ### Limitations
/// Trait implementations which use the same function or parameter name as the trait declaration will
/// not be warned about, even if the name is below the configured limit.
#[clippy::version = "1.72.0"]
pub MIN_IDENT_CHARS,
restriction,
@ -76,6 +84,18 @@ impl LateLintPass<'_> for MinIdentChars {
return;
}
// If the function is declared but not defined in a trait, check_pat isn't called so we need to
// check this explicitly
if matches!(&item.kind, rustc_hir::TraitItemKind::Fn(_, _)) {
let param_names = cx.tcx.fn_arg_idents(item.owner_id.to_def_id());
for ident in param_names.iter().flatten() {
let str = ident.as_str();
if self.is_ident_too_short(cx, str, ident.span) {
emit_min_ident_chars(self, cx, str, ident.span);
}
}
}
walk_trait_item(&mut IdentVisitor { conf: self, cx }, item);
}
@ -84,6 +104,7 @@ impl LateLintPass<'_> for MinIdentChars {
if let PatKind::Binding(_, _, ident, ..) = pat.kind
&& let str = ident.as_str()
&& self.is_ident_too_short(cx, str, ident.span)
&& is_not_in_trait_impl(cx, pat, ident)
{
emit_min_ident_chars(self, cx, str, ident.span);
}
@ -118,6 +139,11 @@ impl Visitor<'_> for IdentVisitor<'_, '_> {
let str = ident.as_str();
if conf.is_ident_too_short(cx, str, ident.span) {
// Check whether the node is part of a `impl` for a trait.
if matches!(cx.tcx.parent_hir_node(hir_id), Node::TraitRef(_)) {
return;
}
// Check whether the node is part of a `use` statement. We don't want to emit a warning if the user
// has no control over the type.
let usenode = opt_as_use_node(node).or_else(|| {
@ -201,3 +227,52 @@ fn opt_as_use_node(node: Node<'_>) -> Option<&'_ UsePath<'_>> {
None
}
}
/// Check if a pattern is a function param in an impl block for a trait and that the param name is
/// the same than in the trait definition.
fn is_not_in_trait_impl(cx: &LateContext<'_>, pat: &Pat<'_>, ident: Ident) -> bool {
let parent_node = cx.tcx.parent_hir_node(pat.hir_id);
if !matches!(parent_node, Node::Param(_)) {
return true;
}
for (_, parent_node) in cx.tcx.hir_parent_iter(pat.hir_id) {
if let Node::ImplItem(impl_item) = parent_node
&& matches!(impl_item.kind, ImplItemKind::Fn(_, _))
{
let impl_parent_node = cx.tcx.parent_hir_node(impl_item.hir_id());
if let Node::Item(parent_item) = impl_parent_node
&& let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = &parent_item.kind
&& let Some(name) = get_param_name(impl_item, cx, ident)
{
return name != ident.name;
}
return true;
}
}
true
}
fn get_param_name(impl_item: &ImplItem<'_>, cx: &LateContext<'_>, ident: Ident) -> Option<Symbol> {
if let Some(trait_item_def_id) = impl_item.trait_item_def_id {
let trait_param_names = cx.tcx.fn_arg_idents(trait_item_def_id);
let ImplItemKind::Fn(_, body_id) = impl_item.kind else {
return None;
};
if let Some(param_index) = cx
.tcx
.hir_body_param_idents(body_id)
.position(|param_ident| param_ident.is_some_and(|param_ident| param_ident.span == ident.span))
&& let Some(trait_param_name) = trait_param_names.get(param_index)
&& let Some(trait_param_ident) = trait_param_name
{
return Some(trait_param_ident.name);
}
}
None
}

View file

@ -1,8 +1,7 @@
use clippy_utils::diagnostics::span_lint;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def_id::DefId;
use rustc_hir::{self as hir, Attribute};
use rustc_hir::{self as hir, Attribute, find_attr};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::AssocItemContainer;
use rustc_session::declare_lint_pass;

View file

@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{
SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt,
is_receiver_of_method_call, peel_blocks, peel_blocks_with_stmt, span_extract_comment, sym,
is_receiver_of_method_call, peel_blocks, peel_blocks_with_stmt, span_contains_comment, sym,
};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -128,14 +128,13 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
use self::Expression::{Bool, RetBool};
if e.span.from_expansion() || !span_extract_comment(cx.tcx.sess.source_map(), e.span).is_empty() {
return;
}
if let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(e)
if !e.span.from_expansion()
&& let Some(higher::If {
cond,
then,
r#else: Some(else_expr),
}) = higher::If::hir(e)
&& !span_contains_comment(cx.tcx.sess.source_map(), e.span)
{
let reduce = |ret, not| {
let mut applicability = Applicability::MachineApplicable;
@ -167,7 +166,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
applicability,
);
};
if let Some((a, b)) = fetch_bool_block(then).and_then(|a| Some((a, fetch_bool_block(r#else)?))) {
if let Some((a, b)) = fetch_bool_block(then).and_then(|a| Some((a, fetch_bool_block(else_expr)?))) {
match (a, b) {
(RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
span_lint(
@ -193,7 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
}
}
if let Some((lhs_a, a)) = fetch_assign(then)
&& let Some((lhs_b, b)) = fetch_assign(r#else)
&& let Some((lhs_b, b)) = fetch_assign(else_expr)
&& SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
{
let mut applicability = Applicability::MachineApplicable;

View file

@ -311,9 +311,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
/// Functions marked with these attributes must have the exact signature.
pub(crate) fn requires_exact_signature(attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| {
attr.is_proc_macro_attr()
})
attrs.iter().any(Attribute::is_proc_macro_attr)
}
#[derive(Default)]

View file

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet, snippet_with_applicability};
use rustc_abi::ExternAbi;
use rustc_hir::attrs::AttributeKind;
use rustc_errors::Applicability;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{Attribute, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;

View file

@ -49,7 +49,7 @@ declare_clippy_lint! {
/// Some functions could be replaced as well if we have replaced `Lazy` to `LazyLock`,
/// therefore after suggesting replace the type, we need to make sure the function calls can be
/// replaced, otherwise the suggestions cannot be applied thus the applicability should be
/// `Unspecified` or `MaybeIncorret`.
/// [`Applicability::Unspecified`] or [`Applicability::MaybeIncorrect`].
static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[
("once_cell::sync::Lazy::force", Some("std::sync::LazyLock::force")),
("once_cell::sync::Lazy::get", None), // `std::sync::LazyLock::get` is experimental

View file

@ -127,7 +127,8 @@ fn try_get_option_occurrence<'tcx>(
if_else: &'tcx Expr<'_>,
) -> Option<OptionOccurrence> {
let cond_expr = match expr.kind {
ExprKind::Unary(UnOp::Deref, inner_expr) | ExprKind::AddrOf(_, _, inner_expr) => inner_expr,
ExprKind::AddrOf(_, _, inner_expr) => inner_expr,
ExprKind::Unary(UnOp::Deref, inner_expr) if !cx.typeck_results().expr_ty(inner_expr).is_raw_ptr() => inner_expr,
_ => expr,
};
let (inner_pat, is_result) = try_get_inner_pat_and_is_result(cx, pat)?;
@ -223,8 +224,8 @@ fn try_get_option_occurrence<'tcx>(
let mut app = Applicability::Unspecified;
let (none_body, is_argless_call) = match none_body.kind {
ExprKind::Call(call_expr, []) if !none_body.span.from_expansion() => (call_expr, true),
let (none_body, can_omit_arg) = match none_body.kind {
ExprKind::Call(call_expr, []) if !none_body.span.from_expansion() && !is_result => (call_expr, true),
_ => (none_body, false),
};
@ -241,7 +242,7 @@ fn try_get_option_occurrence<'tcx>(
),
none_expr: format!(
"{}{}",
if method_sugg == "map_or" || is_argless_call {
if method_sugg == "map_or" || can_omit_arg {
""
} else if is_result {
"|_| "

View file

@ -5,13 +5,12 @@ use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
use clippy_utils::{is_self, is_self_ty};
use core::ops::ControlFlow;
use rustc_abi::ExternAbi;
use rustc_hir::attrs::{AttributeKind, InlineAttr};
use rustc_hir::find_attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::attrs::{AttributeKind, InlineAttr};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingMode, Body, FnDecl, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
use rustc_hir::{BindingMode, Body, FnDecl, Impl, ItemKind, MutTy, Mutability, Node, PatKind, find_attr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, PointerCoercion};
use rustc_middle::ty::layout::LayoutOf;

View file

@ -97,7 +97,7 @@ impl EarlyLintPass for RedundantElse {
els.span.with_lo(then.span.hi()),
"redundant else block",
"remove the `else` block and move the contents out",
make_sugg(cx, els.span, "..", Some(expr.span)).to_string(),
make_sugg(cx, els.span, "..", Some(expr.span)),
app,
);
}

View file

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_must_use_ty;
use clippy_utils::{nth_arg, return_ty};
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl, OwnerId, TraitItem, TraitItemKind};
use rustc_hir::{Body, FnDecl, OwnerId, TraitItem, TraitItemKind, find_attr};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;

View file

@ -102,7 +102,7 @@ impl SemicolonBlock {
}
fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) {
let insert_span = block.span.with_lo(block.span.hi());
let insert_span = block.span.shrink_to_hi();
// For macro call semicolon statements (`mac!();`), the statement's span does not actually
// include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon
@ -144,28 +144,20 @@ impl LateLintPass<'_> for SemicolonBlock {
kind: ExprKind::Block(block, _),
..
}) if !block.span.from_expansion() && stmt.span.contains(block.span) => {
let Block {
expr: None,
stmts: [.., stmt],
..
} = block
else {
return;
};
let &Stmt {
kind: StmtKind::Semi(expr),
..
} = stmt
else {
return;
};
self.semicolon_outside_block(cx, block, expr);
if block.expr.is_none()
&& let [.., stmt] = block.stmts
&& let StmtKind::Semi(expr) = stmt.kind
{
self.semicolon_outside_block(cx, block, expr);
}
},
StmtKind::Semi(Expr {
kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _),
kind: ExprKind::Block(block, _),
..
}) if !block.span.from_expansion() => {
self.semicolon_inside_block(cx, block, tail, stmt.span);
if let Some(tail) = block.expr {
self.semicolon_inside_block(cx, block, tail, stmt.span);
}
},
_ => (),
}
@ -173,9 +165,5 @@ impl LateLintPass<'_> for SemicolonBlock {
}
fn get_line(cx: &LateContext<'_>, span: Span) -> Option<usize> {
if let Ok(line) = cx.sess().source_map().lookup_line(span.lo()) {
return Some(line.line);
}
None
cx.sess().source_map().lookup_line(span.lo()).ok().map(|line| line.line)
}

View file

@ -2,11 +2,10 @@ use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::Msrv;
use rustc_hir::{StabilityLevel, StableSince};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Block, Body, HirId, Path, PathSegment};
use rustc_hir::{Block, Body, HirId, Path, PathSegment, StabilityLevel, StableSince};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::symbol::kw;

View file

@ -13,8 +13,6 @@ use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::source_map::Spanned;
use std::ops::ControlFlow;
declare_clippy_lint! {
/// ### What it does
/// Checks for string appends of the form `x = x + y` (without
@ -411,125 +409,6 @@ impl<'tcx> LateLintPass<'tcx> for StrToString {
}
}
declare_clippy_lint! {
/// ### What it does
/// This lint checks for `.to_string()` method calls on values of type `String`.
///
/// ### Why restrict this?
/// The `to_string` method is also used on other types to convert them to a string.
/// When called on a `String` it only clones the `String`, which can be more specifically
/// expressed with `.clone()`.
///
/// ### Example
/// ```no_run
/// let msg = String::from("Hello World");
/// let _ = msg.to_string();
/// ```
/// Use instead:
/// ```no_run
/// let msg = String::from("Hello World");
/// let _ = msg.clone();
/// ```
#[clippy::version = "pre 1.29.0"]
pub STRING_TO_STRING,
restriction,
"using `to_string()` on a `String`, which should be `clone()`"
}
declare_lint_pass!(StringToString => [STRING_TO_STRING]);
fn is_parent_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<rustc_span::Span> {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(name, _, _, parent_span) = parent_expr.kind
&& name.ident.name == sym::map
&& let Some(caller_def_id) = cx.typeck_results().type_dependent_def_id(parent_expr.hir_id)
&& (clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Result)
|| clippy_utils::is_diag_item_method(cx, caller_def_id, sym::Option)
|| clippy_utils::is_diag_trait_item(cx, caller_def_id, sym::Iterator))
{
Some(parent_span)
} else {
None
}
}
fn is_called_from_map_like(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<rustc_span::Span> {
// Look for a closure as parent of `expr`, discarding simple blocks
let parent_closure = cx
.tcx
.hir_parent_iter(expr.hir_id)
.try_fold(expr.hir_id, |child_hir_id, (_, node)| match node {
// Check that the child expression is the only expression in the block
Node::Block(block) if block.stmts.is_empty() && block.expr.map(|e| e.hir_id) == Some(child_hir_id) => {
ControlFlow::Continue(block.hir_id)
},
Node::Expr(expr) if matches!(expr.kind, ExprKind::Block(..)) => ControlFlow::Continue(expr.hir_id),
Node::Expr(expr) if matches!(expr.kind, ExprKind::Closure(_)) => ControlFlow::Break(Some(expr)),
_ => ControlFlow::Break(None),
})
.break_value()?;
is_parent_map_like(cx, parent_closure?)
}
fn suggest_cloned_string_to_string(cx: &LateContext<'_>, span: rustc_span::Span) {
span_lint_and_sugg(
cx,
STRING_TO_STRING,
span,
"`to_string()` called on a `String`",
"try",
"cloned()".to_string(),
Applicability::MachineApplicable,
);
}
impl<'tcx> LateLintPass<'tcx> for StringToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
return;
}
match &expr.kind {
ExprKind::MethodCall(path, self_arg, [], _) => {
if path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)
&& is_type_lang_item(cx, ty.peel_refs(), LangItem::String)
{
if let Some(parent_span) = is_called_from_map_like(cx, expr) {
suggest_cloned_string_to_string(cx, parent_span);
} else {
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
STRING_TO_STRING,
expr.span,
"`to_string()` called on a `String`",
|diag| {
diag.help("consider using `.clone()`");
},
);
}
}
},
ExprKind::Path(QPath::TypeRelative(ty, segment)) => {
if segment.ident.name == sym::to_string
&& let rustc_hir::TyKind::Path(QPath::Resolved(_, path)) = ty.peel_refs().kind
&& let rustc_hir::def::Res::Def(_, def_id) = path.res
&& cx
.tcx
.lang_items()
.get(LangItem::String)
.is_some_and(|lang_id| lang_id == def_id)
&& let Some(parent_span) = is_parent_map_like(cx, expr)
{
suggest_cloned_string_to_string(cx, parent_span);
}
},
_ => {},
}
}
}
declare_clippy_lint! {
/// ### What it does
/// Warns about calling `str::trim` (or variants) before `str::split_whitespace`.

View file

@ -1,17 +1,20 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_context;
use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node};
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_context};
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
use core::ops::ControlFlow;
use rustc_ast::{FormatArgs, FormatArgumentKind};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{Visitor, walk_body};
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, LetStmt, MatchSource, Node, PatKind, QPath, TyKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty;
use rustc_span::Span;
use super::LET_UNIT_VALUE;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorage, local: &'tcx LetStmt<'_>) {
// skip `let () = { ... }`
if let PatKind::Tuple(fields, ..) = local.pat.kind
&& fields.is_empty()
@ -73,11 +76,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
let mut suggestions = Vec::new();
// Suggest omitting the `let` binding
if let Some(expr) = &local.init {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0;
suggestions.push((local.span, format!("{snip};")));
}
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, init.span, local.span.ctxt(), "()", &mut app).0;
// If this is a binding pattern, we need to add suggestions to remove any usages
// of the variable
@ -85,53 +85,102 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
&& let Some(body_id) = cx.enclosing_body.as_ref()
{
let body = cx.tcx.hir_body(*body_id);
// Collect variable usages
let mut visitor = UnitVariableCollector::new(binding_hir_id);
let mut visitor = UnitVariableCollector::new(cx, format_args, binding_hir_id);
walk_body(&mut visitor, body);
// Add suggestions for replacing variable usages
suggestions.extend(visitor.spans.into_iter().map(|span| (span, "()".to_string())));
let mut has_in_format_capture = false;
suggestions.extend(visitor.spans.iter().filter_map(|span| match span {
MaybeInFormatCapture::Yes => {
has_in_format_capture = true;
None
},
MaybeInFormatCapture::No(span) => Some((*span, "()".to_string())),
}));
if has_in_format_capture {
suggestions.push((
init.span,
format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))),
));
diag.multipart_suggestion(
"replace variable usages with `()`",
suggestions,
Applicability::MachineApplicable,
);
return;
}
}
// Emit appropriate diagnostic based on whether there are usages of the let binding
if !suggestions.is_empty() {
let message = if suggestions.len() == 1 {
"omit the `let` binding"
} else {
"omit the `let` binding and replace variable usages with `()`"
};
diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable);
}
suggestions.push((local.span, format!("{snip};")));
let message = if suggestions.len() == 1 {
"omit the `let` binding"
} else {
"omit the `let` binding and replace variable usages with `()`"
};
diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable);
},
);
}
}
}
struct UnitVariableCollector {
struct UnitVariableCollector<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
format_args: &'a FormatArgsStorage,
id: HirId,
spans: Vec<rustc_span::Span>,
spans: Vec<MaybeInFormatCapture>,
macro_call: Option<&'a FormatArgs>,
}
impl UnitVariableCollector {
fn new(id: HirId) -> Self {
Self { id, spans: vec![] }
enum MaybeInFormatCapture {
Yes,
No(Span),
}
impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>, format_args: &'a FormatArgsStorage, id: HirId) -> Self {
Self {
cx,
format_args,
id,
spans: Vec::new(),
macro_call: None,
}
}
}
/**
* Collect all instances where a variable is used based on its `HirId`.
*/
impl<'tcx> Visitor<'tcx> for UnitVariableCollector {
impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
if let Some(macro_call) = root_macro_call_first_node(self.cx, ex)
&& is_format_macro(self.cx, macro_call.def_id)
&& let Some(format_args) = self.format_args.get(self.cx, ex, macro_call.expn)
{
let parent_macro_call = self.macro_call;
self.macro_call = Some(format_args);
walk_expr(self, ex);
self.macro_call = parent_macro_call;
return;
}
if let ExprKind::Path(QPath::Resolved(None, path)) = ex.kind
&& let Res::Local(id) = path.res
&& id == self.id
{
self.spans.push(path.span);
if let Some(macro_call) = self.macro_call
&& macro_call.arguments.all_args().iter().any(|arg| {
matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some()
})
{
self.spans.push(MaybeInFormatCapture::Yes);
} else {
self.spans.push(MaybeInFormatCapture::No(path.span));
}
}
rustc_hir::intravisit::walk_expr(self, ex);
walk_expr(self, ex);
}
}

View file

@ -3,9 +3,10 @@ mod unit_arg;
mod unit_cmp;
mod utils;
use clippy_utils::macros::FormatArgsStorage;
use rustc_hir::{Expr, LetStmt};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
@ -96,11 +97,21 @@ declare_clippy_lint! {
"passing unit to a function"
}
declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]);
pub struct UnitTypes {
format_args: FormatArgsStorage,
}
impl_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]);
impl UnitTypes {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self { format_args }
}
}
impl<'tcx> LateLintPass<'tcx> for UnitTypes {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'tcx>) {
let_unit_value::check(cx, local);
let_unit_value::check(cx, &self.format_args, local);
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {

View file

@ -348,7 +348,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
IntTy::I128 => "I128",
};
format!("LitIntType::Signed(IntTy::{t})")
}
},
LitIntType::Unsigned(uint_ty) => {
let t = match uint_ty {
UintTy::Usize => "Usize",
@ -359,7 +359,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
UintTy::U128 => "U128",
};
format!("LitIntType::Unsigned(UintTy::{t})")
}
},
LitIntType::Unsuffixed => String::from("LitIntType::Unsuffixed"),
};
kind!("Int({i}, {int_ty})");
@ -374,7 +374,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
FloatTy::F128 => "F128",
};
format!("LitFloatType::Suffixed(FloatTy::{t})")
}
},
LitFloatType::Unsuffixed => String::from("LitFloatType::Unsuffixed"),
};
kind!("Float(_, {float_ty})");

View file

@ -118,7 +118,7 @@ impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
impl LateLintPass<'_> for WildcardImports {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if cx.sess().is_test_crate() {
if cx.sess().is_test_crate() || item.span.in_external_macro(cx.sess().source_map()) {
return;
}

View file

@ -2,12 +2,11 @@ use clippy_utils::diagnostics::span_lint;
use clippy_utils::paths;
use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_ast::{AttrStyle, DelimArgs};
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::Res;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{
AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitRef, Ty, TyKind,
AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitRef, Ty, TyKind, find_attr,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint_defs::declare_tool_lint;

View file

@ -70,7 +70,6 @@ name = "clippy_test_deps"
version = "0.1.0"
dependencies = [
"futures",
"if_chain",
"itertools",
"libc",
"parking_lot",
@ -182,12 +181,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "if_chain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]]
name = "io-uring"
version = "0.7.8"

View file

@ -9,7 +9,6 @@ edition = "2021"
libc = "0.2"
regex = "1.5.5"
serde = { version = "1.0.145", features = ["derive"] }
if_chain = "1.0"
quote = "1.0.25"
syn = { version = "2.0", features = ["full"] }
futures = "0.3"

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.90"
version = "0.1.91"
edition = "2024"
description = "Helpful tools for writing lints, provided as they are used in Clippy"
repository = "https://github.com/rust-lang/rust-clippy"

View file

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

View file

@ -2,9 +2,9 @@ use crate::source::SpanRangeExt;
use crate::{sym, tokenize_with_text};
use rustc_ast::attr;
use rustc_ast::attr::AttributeExt;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_errors::Applicability;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::find_attr;
use rustc_lexer::TokenKind;
use rustc_lint::LateContext;
use rustc_middle::ty::{AdtDef, TyCtxt};

View file

@ -83,19 +83,18 @@ pub use self::hir_utils::{
use core::mem;
use core::ops::ControlFlow;
use std::collections::hash_map::Entry;
use std::iter::{once, repeat_n};
use std::iter::{once, repeat_n, zip};
use std::sync::{Mutex, MutexGuard, OnceLock};
use itertools::Itertools;
use rustc_abi::Integer;
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_ast::join_path_syms;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::packed::Pu128;
use rustc_data_structures::unhash::UnindexMap;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
use rustc_hir::definitions::{DefPath, DefPathData};
@ -106,7 +105,7 @@ use rustc_hir::{
CoroutineKind, 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,
TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr,
};
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
use rustc_lint::{LateContext, Level, Lint, LintContext};
@ -582,7 +581,7 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
return false;
}
for (x1, x2) in s1.iter().zip(s2.iter()) {
for (x1, x2) in zip(&s1, &s2) {
if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
return false;
}
@ -1898,42 +1897,11 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// * `|x| { return x; }`
/// * `|(x, y)| (x, y)`
/// * `|[x, y]| [x, y]`
/// * `|Foo(bar, baz)| Foo(bar, baz)`
/// * `|Foo { bar, baz }| Foo { bar, baz }`
///
/// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
fn check_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
if cx
.typeck_results()
.pat_binding_modes()
.get(pat.hir_id)
.is_some_and(|mode| matches!(mode.0, ByRef::Yes(_)))
{
// If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then
// due to match ergonomics, the inner patterns become references. Don't consider this
// the identity function as that changes types.
return false;
}
match (pat.kind, expr.kind) {
(PatKind::Binding(_, id, _, _), _) => {
path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty()
},
(PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
{
pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr))
},
(PatKind::Slice(before, slice, after), ExprKind::Array(arr))
if slice.is_none() && before.len() + after.len() == arr.len() =>
{
(before.iter().chain(after))
.zip(arr)
.all(|(pat, expr)| check_pat(cx, pat, expr))
},
_ => false,
}
}
let [param] = func.params else {
return false;
};
@ -1966,11 +1934,81 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
return false;
}
},
_ => return check_pat(cx, param.pat, expr),
_ => return is_expr_identity_of_pat(cx, param.pat, expr, true),
}
}
}
/// Checks if the given expression is an identity representation of the given pattern:
/// * `x` is the identity representation of `x`
/// * `(x, y)` is the identity representation of `(x, y)`
/// * `[x, y]` is the identity representation of `[x, y]`
/// * `Foo(bar, baz)` is the identity representation of `Foo(bar, baz)`
/// * `Foo { bar, baz }` is the identity representation of `Foo { bar, baz }`
///
/// Note that `by_hir` is used to determine bindings are checked by their `HirId` or by their name.
/// This can be useful when checking patterns in `let` bindings or `match` arms.
pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>, by_hir: bool) -> bool {
if cx
.typeck_results()
.pat_binding_modes()
.get(pat.hir_id)
.is_some_and(|mode| matches!(mode.0, ByRef::Yes(_)))
{
// If the parameter is `(x, y)` of type `&(T, T)`, or `[x, y]` of type `&[T; 2]`, then
// due to match ergonomics, the inner patterns become references. Don't consider this
// the identity function as that changes types.
return false;
}
// NOTE: we're inside a (function) body, so this won't ICE
let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir);
match (pat.kind, expr.kind) {
(PatKind::Binding(_, id, _, _), _) if by_hir => {
path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty()
},
(PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => {
matches!(path.segments, [ segment] if segment.ident.name == ident.name)
},
(PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
{
zip(pats, tup).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
},
(PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
},
(PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields))
if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() =>
{
// check ident
if let ExprKind::Path(ident) = &ident.kind
&& qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
// check fields
&& zip(field_pats, fields).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr,by_hir))
{
true
} else {
false
}
},
(PatKind::Struct(pat_ident, field_pats, false), ExprKind::Struct(ident, fields, hir::StructTailExpr::None))
if field_pats.len() == fields.len() =>
{
// check ident
qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
// check fields
&& field_pats.iter().all(|field_pat| {
fields.iter().any(|field| {
field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
})
})
},
_ => false,
}
}
/// This is the same as [`is_expr_identity_function`], but does not consider closures
/// with type annotations for its bindings (or similar) as identity functions:
/// * `|x: u8| x`

View file

@ -1,8 +1,8 @@
use crate::sym;
use rustc_ast::Attribute;
use rustc_ast::attr::AttributeExt;
use rustc_hir::RustcVersion;
use rustc_attr_parsing::parse_version;
use rustc_hir::RustcVersion;
use rustc_lint::LateContext;
use rustc_session::Session;
use rustc_span::Symbol;

View file

@ -5,10 +5,10 @@
use crate::msrvs::{self, Msrv};
use hir::LangItem;
use rustc_hir::{RustcVersion, StableSince};
use rustc_const_eval::check_consts::ConstCx;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::{RustcVersion, StableSince};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::Obligation;
use rustc_lint::LateContext;

View file

@ -342,11 +342,8 @@ impl SourceFileRange {
/// Attempts to get the text from the source file. This can fail if the source text isn't
/// loaded.
pub fn as_str(&self) -> Option<&str> {
self.sf
.src
.as_ref()
.map(|src| src.as_str())
.or_else(|| self.sf.external_src.get().and_then(|src| src.get_source()))
(self.sf.src.as_ref().map(|src| src.as_str()))
.or_else(|| self.sf.external_src.get()?.get_source())
.and_then(|x| x.get(self.range.clone()))
}
}

View file

@ -4,11 +4,11 @@
use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context};
use crate::ty::expr_sig;
use crate::{get_parent_expr_for_hir, higher};
use rustc_ast::ast;
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{UnOp, ast};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind};
use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, TyKind};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
use rustc_lint::{EarlyContext, LateContext, LintContext};
use rustc_middle::hir::place::ProjectionKind;
@ -29,6 +29,11 @@ pub enum Sugg<'a> {
/// A binary operator expression, including `as`-casts and explicit type
/// coercion.
BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>),
/// A unary operator expression. This is used to sometimes represent `!`
/// or `-`, but only if the type with and without the operator is kept identical.
/// It means that doubling the operator can be used to remove it instead, in
/// order to provide better suggestions.
UnOp(UnOp, Box<Sugg<'a>>),
}
/// Literal constant `0`, for convenience.
@ -40,9 +45,10 @@ pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
impl Display for Sugg<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f),
Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f),
match self {
Sugg::NonParen(s) | Sugg::MaybeParen(s) => s.fmt(f),
Sugg::BinOp(op, lhs, rhs) => binop_to_string(*op, lhs, rhs).fmt(f),
Sugg::UnOp(op, inner) => write!(f, "{}{}", op.as_str(), inner.clone().maybe_inner_paren()),
}
}
}
@ -100,9 +106,19 @@ impl<'a> Sugg<'a> {
applicability: &mut Applicability,
) -> Self {
if expr.span.ctxt() == ctxt {
Self::hir_from_snippet(expr, |span| {
snippet_with_context(cx, span, ctxt, default, applicability).0
})
if let ExprKind::Unary(op, inner) = expr.kind
&& matches!(op, UnOp::Neg | UnOp::Not)
&& cx.typeck_results().expr_ty(expr) == cx.typeck_results().expr_ty(inner)
{
Sugg::UnOp(
op,
Box::new(Self::hir_with_context(cx, inner, ctxt, default, applicability)),
)
} else {
Self::hir_from_snippet(expr, |span| {
snippet_with_context(cx, span, ctxt, default, applicability).0
})
}
} else {
let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
Sugg::NonParen(snip)
@ -341,6 +357,7 @@ impl<'a> Sugg<'a> {
let sugg = binop_to_string(op, &lhs, &rhs);
Sugg::NonParen(format!("({sugg})").into())
},
Sugg::UnOp(op, inner) => Sugg::NonParen(format!("({}{})", op.as_str(), inner.maybe_inner_paren()).into()),
}
}
@ -348,6 +365,26 @@ impl<'a> Sugg<'a> {
match self {
Sugg::NonParen(p) | Sugg::MaybeParen(p) => p.into_owned(),
Sugg::BinOp(b, l, r) => binop_to_string(b, &l, &r),
Sugg::UnOp(op, inner) => format!("{}{}", op.as_str(), inner.maybe_inner_paren()),
}
}
/// Checks if `self` starts with a unary operator.
fn starts_with_unary_op(&self) -> bool {
match self {
Sugg::UnOp(..) => true,
Sugg::BinOp(..) => false,
Sugg::MaybeParen(s) | Sugg::NonParen(s) => s.starts_with(['*', '!', '-', '&']),
}
}
/// Call `maybe_paren` on `self` if it doesn't start with a unary operator,
/// don't touch it otherwise.
fn maybe_inner_paren(self) -> Self {
if self.starts_with_unary_op() {
self
} else {
self.maybe_paren()
}
}
}
@ -430,10 +467,11 @@ impl Sub for &Sugg<'_> {
forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
impl Neg for Sugg<'_> {
type Output = Sugg<'static>;
fn neg(self) -> Sugg<'static> {
match &self {
impl<'a> Neg for Sugg<'a> {
type Output = Sugg<'a>;
fn neg(self) -> Self::Output {
match self {
Self::UnOp(UnOp::Neg, sugg) => *sugg,
Self::BinOp(AssocOp::Cast, ..) => Sugg::MaybeParen(format!("-({self})").into()),
_ => make_unop("-", self),
}
@ -446,19 +484,21 @@ impl<'a> Not for Sugg<'a> {
use AssocOp::Binary;
use ast::BinOpKind::{Eq, Ge, Gt, Le, Lt, Ne};
if let Sugg::BinOp(op, lhs, rhs) = self {
let to_op = match op {
Binary(Eq) => Binary(Ne),
Binary(Ne) => Binary(Eq),
Binary(Lt) => Binary(Ge),
Binary(Ge) => Binary(Lt),
Binary(Gt) => Binary(Le),
Binary(Le) => Binary(Gt),
_ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
};
Sugg::BinOp(to_op, lhs, rhs)
} else {
make_unop("!", self)
match self {
Sugg::BinOp(op, lhs, rhs) => {
let to_op = match op {
Binary(Eq) => Binary(Ne),
Binary(Ne) => Binary(Eq),
Binary(Lt) => Binary(Ge),
Binary(Ge) => Binary(Lt),
Binary(Gt) => Binary(Le),
Binary(Le) => Binary(Gt),
_ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
};
Sugg::BinOp(to_op, lhs, rhs)
},
Sugg::UnOp(UnOp::Not, expr) => *expr,
_ => make_unop("!", self),
}
}
}
@ -491,20 +531,11 @@ impl<T: Display> Display for ParenHelper<T> {
/// Builds the string for `<op><expr>` adding parenthesis when necessary.
///
/// For convenience, the operator is taken as a string because all unary
/// operators have the same
/// precedence.
/// operators have the same precedence.
pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
// If the `expr` starts with `op` already, do not add wrap it in
// If the `expr` starts with a unary operator already, do not wrap it in
// parentheses.
let expr = if let Sugg::MaybeParen(ref sugg) = expr
&& !has_enclosing_paren(sugg)
&& sugg.starts_with(op)
{
expr
} else {
expr.maybe_paren()
};
Sugg::MaybeParen(format!("{op}{expr}").into())
Sugg::MaybeParen(format!("{op}{}", expr.maybe_inner_paren()).into())
}
/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
@ -753,8 +784,10 @@ pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Opti
let mut visitor = DerefDelegate {
cx,
closure_span: closure.span,
closure_arg_id: closure_body.params[0].pat.hir_id,
closure_arg_is_type_annotated_double_ref,
next_pos: closure.span.lo(),
checked_borrows: FxHashSet::default(),
suggestion_start: String::new(),
applicability: Applicability::MachineApplicable,
};
@ -780,10 +813,15 @@ struct DerefDelegate<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
/// The span of the input closure to adapt
closure_span: Span,
/// The `hir_id` of the closure argument being checked
closure_arg_id: HirId,
/// Indicates if the arg of the closure is a type annotated double reference
closure_arg_is_type_annotated_double_ref: bool,
/// last position of the span to gradually build the suggestion
next_pos: BytePos,
/// `hir_id`s that has been checked. This is used to avoid checking the same `hir_id` multiple
/// times when inside macro expansions.
checked_borrows: FxHashSet<HirId>,
/// starting part of the gradually built suggestion
suggestion_start: String,
/// confidence on the built suggestion
@ -847,9 +885,15 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
#[expect(clippy::too_many_lines)]
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
if let PlaceBase::Local(id) = cmt.place.base {
let span = self.cx.tcx.hir_span(cmt.hir_id);
if !self.checked_borrows.insert(cmt.hir_id) {
// already checked this span and hir_id, skip
return;
}
let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
@ -858,7 +902,12 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
// full identifier that includes projection (i.e.: `fp.field`)
let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
if cmt.place.projections.is_empty() {
// Make sure to get in all projections if we're on a `matches!`
if let Node::Pat(pat) = self.cx.tcx.hir_node(id)
&& pat.hir_id != self.closure_arg_id
{
let _ = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}");
} else if cmt.place.projections.is_empty() {
// handle item without any projection, that needs an explicit borrowing
// i.e.: suggest `&x` instead of `x`
let _: fmt::Result = write!(self.suggestion_start, "{start_snip}&{ident_str}");

View file

@ -171,7 +171,6 @@ generate! {
has_significant_drop,
hidden_glob_reexports,
hygiene,
if_chain,
insert,
inspect,
int_roundings,

View file

@ -6,13 +6,12 @@ use core::ops::ControlFlow;
use itertools::Itertools;
use rustc_abi::VariantIdx;
use rustc_ast::ast::Mutability;
use rustc_hir::attrs::{AttributeKind};
use rustc_hir::find_attr;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, FnDecl, LangItem, TyKind};
use rustc_hir::{Expr, FnDecl, LangItem, TyKind, find_attr};
use rustc_hir_analysis::lower_ty;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
@ -583,7 +582,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(t
}
/// A signature for a function like type.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum ExprFnSig<'tcx> {
Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>),
Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),

View file

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

View file

@ -117,7 +117,7 @@ pub fn read_crates(toml_path: &Path) -> (Vec<CrateWithSource>, RecursiveOptions)
crate_sources.push(CrateWithSource {
name: tk.name.clone(),
source: CrateSource::CratesIo {
version: version.to_string(),
version: version.clone(),
},
file_link: tk.file_link(DEFAULT_DOCS_LINK),
options: tk.options.clone(),

View file

@ -66,7 +66,7 @@ impl fmt::Display for Summary {
} in &self.0
{
let html_id = to_html_id(name);
writeln!(f, "| [`{name}`](#{html_id}) | {added} | {changed} | {removed} |")?;
writeln!(f, "| [`{name}`](#{html_id}) | {added} | {removed} | {changed} |")?;
}
Ok(())

View file

@ -220,7 +220,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
let same_in_both_hashmaps = old_stats
.iter()
.filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val))
.map(|(k, v)| (k.to_string(), *v))
.map(|(k, v)| (k.clone(), *v))
.collect::<Vec<(String, usize)>>();
let mut old_stats_deduped = old_stats;

View file

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

View file

@ -73,8 +73,7 @@ fn internal_extern_flags() -> Vec<String> {
&& INTERNAL_TEST_DEPENDENCIES.contains(&name)
{
// A dependency may be listed twice if it is available in sysroot,
// and the sysroot dependencies are listed first. As of the writing,
// this only seems to apply to if_chain.
// and the sysroot dependencies are listed first.
crates.insert(name, path);
}
}
@ -434,6 +433,7 @@ fn ui_cargo_toml_metadata() {
#[derive(Template)]
#[template(path = "index_template.html")]
struct Renderer<'a> {
count: usize,
lints: &'a Vec<LintMetadata>,
}
@ -513,7 +513,12 @@ impl DiagnosticCollector {
fs::write(
"util/gh-pages/index.html",
Renderer { lints: &metadata }.render().unwrap(),
Renderer {
count: LINTS.len(),
lints: &metadata,
}
.render()
.unwrap(),
)
.unwrap();
});

View file

@ -75,7 +75,7 @@ fn all_symbols_are_used() -> Result<()> {
for sym in extra {
eprintln!(" - {sym}");
}
Err(format!("extra symbols found — remove them {SYM_FILE}"))?;
Err(format!("extra symbols found — remove them from {SYM_FILE}"))?;
}
Ok(())
}

View file

@ -7,7 +7,7 @@ error: unnecessary structure name repetition
note: the lint level is defined here
--> src/main.rs:6:9
|
6 | #![deny(clippy::use_self)]
6 | #![deny(clippy::use_self)]
| ^^^^^^^^^^^^^^^^
error: unnecessary structure name repetition

View file

@ -14,10 +14,10 @@ error: file is loaded as a module multiple times: `src/b.rs`
error: file is loaded as a module multiple times: `src/c.rs`
--> src/main.rs:7:1
|
7 | mod c;
7 | mod c;
| ^^^^^^ first loaded here
8 | / #[path = "c.rs"]
9 | | mod c2;
8 | / #[path = "c.rs"]
9 | | mod c2;
| |_______^ loaded again here
10 | / #[path = "c.rs"]
11 | | mod c3;
@ -44,8 +44,8 @@ error: file is loaded as a module multiple times: `src/from_other_module.rs`
|
::: src/other_module/mod.rs:1:1
|
1 | / #[path = "../from_other_module.rs"]
2 | | mod m;
1 | / #[path = "../from_other_module.rs"]
2 | | mod m;
| |______^ loaded again here
|
= help: replace all but one `mod` item with `use` items

View file

@ -1,7 +1,7 @@
error: lint group `rust_2018_idioms` has the same priority (0) as a lint
--> Cargo.toml:7:1
|
7 | rust_2018_idioms = "warn"
7 | rust_2018_idioms = "warn"
| ^^^^^^^^^^^^^^^^ ------ has an implicit priority of 0
...
12 | unused_attributes = { level = "allow" }
@ -11,8 +11,8 @@ error: lint group `rust_2018_idioms` has the same priority (0) as a lint
= note: `#[deny(clippy::lint_groups_priority)]` on by default
help: to have lints override the group set `rust_2018_idioms` to a lower priority
|
7 - rust_2018_idioms = "warn"
7 + rust_2018_idioms = { level = "warn", priority = -1 }
7 - rust_2018_idioms = "warn"
7 + rust_2018_idioms = { level = "warn", priority = -1 }
|
error: lint group `unused` has the same priority (0) as a lint

View file

@ -1,3 +1,4 @@
//@aux-build:../../ui/auxiliary/proc_macros.rs
#![warn(clippy::wildcard_imports)]
mod prelude {
@ -13,6 +14,10 @@ mod my_crate {
pub mod utils {
pub fn my_util_fn() {}
}
pub mod utils2 {
pub const SOME_CONST: u32 = 1;
}
}
pub use utils::{BAR, print};
@ -22,6 +27,12 @@ use my_crate::utils::my_util_fn;
use prelude::FOO;
//~^ ERROR: usage of wildcard import
proc_macros::external! {
use my_crate::utils2::*;
static SOME_STATIC: u32 = SOME_CONST;
}
fn main() {
let _ = FOO;
let _ = BAR;

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