Merge commit '334fb906ae' into clippy-subtree-update
This commit is contained in:
commit
d9385437c2
181 changed files with 2803 additions and 1027 deletions
|
|
@ -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.
|
||||
|
|
|
|||
6
src/tools/clippy/.github/driver.sh
vendored
6
src/tools/clippy/.github/driver.sh
vendored
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.90"
|
||||
version = "0.1.91"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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(":")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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, []) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 => {},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
"|_| "
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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})");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ generate! {
|
|||
has_significant_drop,
|
||||
hidden_glob_reexports,
|
||||
hygiene,
|
||||
if_chain,
|
||||
insert,
|
||||
inspect,
|
||||
int_roundings,
|
||||
|
|
|
|||
|
|
@ -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>>),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
@ -22,6 +27,12 @@ use my_crate::utils::*;
|
|||
use prelude::*;
|
||||
//~^ 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
Loading…
Add table
Add a link
Reference in a new issue