mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-27 07:00:55 +00:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
d2f76f7e6e
139 changed files with 3930 additions and 650 deletions
15
.github/driver.sh
vendored
Normal file → Executable file
15
.github/driver.sh
vendored
Normal file → Executable file
|
@ -11,9 +11,16 @@ if [[ ${OS} == "Windows" ]]; then
|
|||
else
|
||||
desired_sysroot=/tmp
|
||||
fi
|
||||
# Set --sysroot in command line
|
||||
sysroot=$(./target/debug/clippy-driver --sysroot $desired_sysroot --print sysroot)
|
||||
test "$sysroot" = $desired_sysroot
|
||||
|
||||
# Set --sysroot in arg_file.txt and pass @arg_file.txt to command line
|
||||
echo "--sysroot=$desired_sysroot" > arg_file.txt
|
||||
sysroot=$(./target/debug/clippy-driver @arg_file.txt --print sysroot)
|
||||
test "$sysroot" = $desired_sysroot
|
||||
|
||||
# Setting SYSROOT in command line
|
||||
sysroot=$(SYSROOT=$desired_sysroot ./target/debug/clippy-driver --print sysroot)
|
||||
test "$sysroot" = $desired_sysroot
|
||||
|
||||
|
@ -24,6 +31,14 @@ test "$sysroot" = $desired_sysroot
|
|||
SYSROOT=/tmp RUSTFLAGS="--sysroot=$(rustc --print sysroot)" ../target/debug/cargo-clippy clippy --verbose
|
||||
)
|
||||
|
||||
# Check that the --sysroot argument is only passed once via arg_file.txt (SYSROOT is ignored)
|
||||
(
|
||||
echo "fn main() {}" > target/driver_test.rs
|
||||
echo "--sysroot="$(./target/debug/clippy-driver --print sysroot)"" > arg_file.txt
|
||||
echo "--verbose" >> arg_file.txt
|
||||
SYSROOT=/tmp ./target/debug/clippy-driver @arg_file.txt ./target/driver_test.rs
|
||||
)
|
||||
|
||||
# Make sure this isn't set - clippy-driver should cope without it
|
||||
unset CARGO_MANIFEST_DIR
|
||||
|
||||
|
|
66
CHANGELOG.md
66
CHANGELOG.md
|
@ -6,11 +6,65 @@ document.
|
|||
|
||||
## Unreleased / Beta / In Rust Nightly
|
||||
|
||||
[09ac14c9...master](https://github.com/rust-lang/rust-clippy/compare/09ac14c9...master)
|
||||
[a859e5cc...master](https://github.com/rust-lang/rust-clippy/compare/a859e5cc...master)
|
||||
|
||||
## Rust 1.76
|
||||
|
||||
Current stable, released 2024-02-08
|
||||
|
||||
[View all 85 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-11-02T20%3A23%3A40Z..2023-12-16T13%3A11%3A08Z+base%3Amaster)
|
||||
|
||||
### New Lints
|
||||
|
||||
- [`infinite_loop`]
|
||||
[#11829](https://github.com/rust-lang/rust-clippy/pull/11829)
|
||||
- [`ineffective_open_options`]
|
||||
[#11902](https://github.com/rust-lang/rust-clippy/pull/11902)
|
||||
- [`uninhabited_references`]
|
||||
[#11878](https://github.com/rust-lang/rust-clippy/pull/11878)
|
||||
- [`repeat_vec_with_capacity`]
|
||||
[#11597](https://github.com/rust-lang/rust-clippy/pull/11597)
|
||||
- [`test_attr_in_doctest`]
|
||||
[#11872](https://github.com/rust-lang/rust-clippy/pull/11872)
|
||||
- [`option_map_or_err_ok`]
|
||||
[#11864](https://github.com/rust-lang/rust-clippy/pull/11864)
|
||||
- [`join_absolute_paths`]
|
||||
[#11453](https://github.com/rust-lang/rust-clippy/pull/11453)
|
||||
- [`impl_hash_borrow_with_str_and_bytes`]
|
||||
[#11781](https://github.com/rust-lang/rust-clippy/pull/11781)
|
||||
- [`iter_over_hash_type`]
|
||||
[#11791](https://github.com/rust-lang/rust-clippy/pull/11791)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
- Renamed `blocks_in_if_conditions` to [`blocks_in_conditions`]
|
||||
[#11853](https://github.com/rust-lang/rust-clippy/pull/11853)
|
||||
- Moved [`implied_bounds_in_impls`] to `complexity` (Now warn-by-default)
|
||||
[#11867](https://github.com/rust-lang/rust-clippy/pull/11867)
|
||||
- Moved [`if_same_then_else`] to `style` (Now warn-by-default)
|
||||
[#11809](https://github.com/rust-lang/rust-clippy/pull/11809)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- [`missing_safety_doc`], [`unnecessary_safety_doc`], [`missing_panics_doc`], [`missing_errors_doc`]:
|
||||
Added the [`check-private-items`] configuration to enable lints on private items
|
||||
[#11842](https://github.com/rust-lang/rust-clippy/pull/11842)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
- [`impl_trait_in_params`]: No longer crashes when a function has generics but no function parameters
|
||||
[#11804](https://github.com/rust-lang/rust-clippy/pull/11804)
|
||||
- [`unused_enumerate_index`]: No longer crashes on empty tuples
|
||||
[#11756](https://github.com/rust-lang/rust-clippy/pull/11756)
|
||||
|
||||
### Others
|
||||
|
||||
- Clippy now respects the `CARGO` environment value
|
||||
[#11944](https://github.com/rust-lang/rust-clippy/pull/11944)
|
||||
|
||||
## Rust 1.75
|
||||
|
||||
Current stable, released 2023-12-28
|
||||
Released 2023-12-28
|
||||
|
||||
[View all 69 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-09-25T11%3A47%3A47Z..2023-11-02T16%3A41%3A59Z+base%3Amaster)
|
||||
|
||||
|
@ -5198,6 +5252,7 @@ Released 2018-09-13
|
|||
[`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls
|
||||
[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons
|
||||
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
|
||||
[`incompatible_msrv`]: https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
|
||||
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
|
||||
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
|
||||
[`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type
|
||||
|
@ -5276,6 +5331,7 @@ Released 2018-09-13
|
|||
[`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore
|
||||
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
|
||||
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
|
||||
[`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority
|
||||
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
|
||||
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
|
||||
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
|
||||
|
@ -5284,6 +5340,7 @@ Released 2018-09-13
|
|||
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
|
||||
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
|
||||
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
|
||||
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
|
||||
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
|
||||
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
|
||||
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
|
||||
|
@ -5523,6 +5580,7 @@ Released 2018-09-13
|
|||
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
|
||||
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
|
||||
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
|
||||
[`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr
|
||||
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
|
||||
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
|
||||
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
|
||||
|
@ -5622,6 +5680,7 @@ Released 2018-09-13
|
|||
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
|
||||
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
|
||||
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
|
||||
[`to_string_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_trait_impl
|
||||
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
|
||||
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
|
||||
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
|
||||
|
@ -5677,6 +5736,7 @@ Released 2018-09-13
|
|||
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
|
||||
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
|
||||
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
|
||||
[`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else
|
||||
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
|
||||
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
|
||||
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
|
||||
|
@ -5819,4 +5879,6 @@ Released 2018-09-13
|
|||
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
|
||||
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
|
||||
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
|
||||
[`allow-comparison-to-zero`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-comparison-to-zero
|
||||
[`allowed-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-wildcard-imports
|
||||
<!-- end autogenerated links to configuration documentation -->
|
||||
|
|
|
@ -151,6 +151,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
|
||||
* [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one)
|
||||
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
|
||||
* [`manual_c_str_literals`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals)
|
||||
|
||||
|
||||
## `cognitive-complexity-threshold`
|
||||
|
@ -828,3 +829,35 @@ exported visibility, or whether they are marked as "pub".
|
|||
* [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields)
|
||||
|
||||
|
||||
## `allow-comparison-to-zero`
|
||||
Don't lint when comparing the result of a modulo operation to zero.
|
||||
|
||||
**Default Value:** `true`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`modulo_arithmetic`](https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic)
|
||||
|
||||
|
||||
## `allowed-wildcard-imports`
|
||||
List of path segments allowed to have wildcard imports.
|
||||
|
||||
#### Example
|
||||
|
||||
```toml
|
||||
allowed-wildcard-imports = [ "utils", "common" ]
|
||||
```
|
||||
|
||||
#### Noteworthy
|
||||
|
||||
1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
|
||||
2. Paths with any segment that containing the word 'prelude'
|
||||
are already allowed by default.
|
||||
|
||||
**Default Value:** `[]`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)
|
||||
|
||||
|
||||
|
|
|
@ -260,7 +260,7 @@ define_Conf! {
|
|||
///
|
||||
/// Suppress lints whenever the suggested change would cause breakage for other crates.
|
||||
(avoid_breaking_exported_api: bool = true),
|
||||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP.
|
||||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS.
|
||||
///
|
||||
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
|
||||
#[default_text = ""]
|
||||
|
@ -567,6 +567,26 @@ define_Conf! {
|
|||
/// Lint "public" fields in a struct that are prefixed with an underscore based on their
|
||||
/// exported visibility, or whether they are marked as "pub".
|
||||
(pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported),
|
||||
/// Lint: MODULO_ARITHMETIC.
|
||||
///
|
||||
/// Don't lint when comparing the result of a modulo operation to zero.
|
||||
(allow_comparison_to_zero: bool = true),
|
||||
/// Lint: WILDCARD_IMPORTS.
|
||||
///
|
||||
/// List of path segments allowed to have wildcard imports.
|
||||
///
|
||||
/// #### Example
|
||||
///
|
||||
/// ```toml
|
||||
/// allowed-wildcard-imports = [ "utils", "common" ]
|
||||
/// ```
|
||||
///
|
||||
/// #### Noteworthy
|
||||
///
|
||||
/// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
|
||||
/// 2. Paths with any segment that containing the word 'prelude'
|
||||
/// are already allowed by default.
|
||||
(allowed_wildcard_imports: FxHashSet<String> = FxHashSet::default()),
|
||||
}
|
||||
|
||||
/// Search for the configuration file.
|
||||
|
|
|
@ -3,6 +3,7 @@ use rustc_semver::RustcVersion;
|
|||
use rustc_session::Session;
|
||||
use rustc_span::{sym, Symbol};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
|
||||
macro_rules! msrv_aliases {
|
||||
($($major:literal,$minor:literal,$patch:literal {
|
||||
|
@ -16,6 +17,8 @@ macro_rules! msrv_aliases {
|
|||
|
||||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,77,0 { C_STR_LITERALS }
|
||||
1,76,0 { PTR_FROM_REF }
|
||||
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
|
||||
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
|
||||
1,68,0 { PATH_MAIN_SEPARATOR_STR }
|
||||
|
@ -58,6 +61,16 @@ pub struct Msrv {
|
|||
stack: Vec<RustcVersion>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Msrv {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(msrv) = self.current() {
|
||||
write!(f, "{msrv}")
|
||||
} else {
|
||||
f.write_str("1.0.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Msrv {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
|
|
|
@ -499,6 +499,7 @@ struct NotSimplificationVisitor<'a, 'tcx> {
|
|||
impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
|
||||
&& !expr.span.from_expansion()
|
||||
&& !inner.span.from_expansion()
|
||||
&& let Some(suggestion) = simplify_not(self.cx, inner)
|
||||
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
|
||||
|
|
168
clippy_lints/src/cargo/lint_groups_priority.rs
Normal file
168
clippy_lints/src/cargo/lint_groups_priority.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
use super::LINT_GROUPS_PRIORITY;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{unerased_lint_store, LateContext};
|
||||
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use toml::Spanned;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct LintConfigTable {
|
||||
level: String,
|
||||
priority: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum LintConfig {
|
||||
Level(String),
|
||||
Table(LintConfigTable),
|
||||
}
|
||||
|
||||
impl LintConfig {
|
||||
fn level(&self) -> &str {
|
||||
match self {
|
||||
LintConfig::Level(level) => level,
|
||||
LintConfig::Table(table) => &table.level,
|
||||
}
|
||||
}
|
||||
|
||||
fn priority(&self) -> i64 {
|
||||
match self {
|
||||
LintConfig::Level(_) => 0,
|
||||
LintConfig::Table(table) => table.priority.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_implicit(&self) -> bool {
|
||||
if let LintConfig::Table(table) = self {
|
||||
table.priority.is_none()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LintTable = BTreeMap<Spanned<String>, Spanned<LintConfig>>;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Lints {
|
||||
#[serde(default)]
|
||||
rust: LintTable,
|
||||
#[serde(default)]
|
||||
clippy: LintTable,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct CargoToml {
|
||||
lints: Lints,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct LintsAndGroups {
|
||||
lints: Vec<Spanned<String>>,
|
||||
groups: Vec<(Spanned<String>, Spanned<LintConfig>)>,
|
||||
}
|
||||
|
||||
fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
|
||||
Span::new(
|
||||
file.start_pos + BytePos::from_usize(range.start),
|
||||
file.start_pos + BytePos::from_usize(range.end),
|
||||
SyntaxContext::root(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_table(cx: &LateContext<'_>, table: LintTable, groups: &FxHashSet<&str>, file: &SourceFile) {
|
||||
let mut by_priority = BTreeMap::<_, LintsAndGroups>::new();
|
||||
for (name, config) in table {
|
||||
let lints_and_groups = by_priority.entry(config.as_ref().priority()).or_default();
|
||||
if groups.contains(name.get_ref().as_str()) {
|
||||
lints_and_groups.groups.push((name, config));
|
||||
} else {
|
||||
lints_and_groups.lints.push(name);
|
||||
}
|
||||
}
|
||||
let low_priority = by_priority
|
||||
.iter()
|
||||
.find(|(_, lints_and_groups)| !lints_and_groups.lints.is_empty())
|
||||
.map_or(-1, |(&lowest_lint_priority, _)| lowest_lint_priority - 1);
|
||||
|
||||
for (priority, LintsAndGroups { lints, groups }) in by_priority {
|
||||
let Some(last_lint_alphabetically) = lints.last() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for (group, config) in groups {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LINT_GROUPS_PRIORITY,
|
||||
toml_span(group.span(), file),
|
||||
&format!(
|
||||
"lint group `{}` has the same priority ({priority}) as a lint",
|
||||
group.as_ref()
|
||||
),
|
||||
|diag| {
|
||||
let config_span = toml_span(config.span(), file);
|
||||
if config.as_ref().is_implicit() {
|
||||
diag.span_label(config_span, "has an implicit priority of 0");
|
||||
}
|
||||
// add the label to next lint after this group that has the same priority
|
||||
let lint = lints
|
||||
.iter()
|
||||
.filter(|lint| lint.span().start > group.span().start)
|
||||
.min_by_key(|lint| lint.span().start)
|
||||
.unwrap_or(last_lint_alphabetically);
|
||||
diag.span_label(toml_span(lint.span(), file), "has the same priority as this lint");
|
||||
diag.note("the order of the lints in the table is ignored by Cargo");
|
||||
let mut suggestion = String::new();
|
||||
Serialize::serialize(
|
||||
&LintConfigTable {
|
||||
level: config.as_ref().level().into(),
|
||||
priority: Some(low_priority),
|
||||
},
|
||||
toml::ser::ValueSerializer::new(&mut suggestion),
|
||||
)
|
||||
.unwrap();
|
||||
diag.span_suggestion_verbose(
|
||||
config_span,
|
||||
format!(
|
||||
"to have lints override the group set `{}` to a lower priority",
|
||||
group.as_ref()
|
||||
),
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(cx: &LateContext<'_>) {
|
||||
if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml"))
|
||||
&& let Some(src) = file.src.as_deref()
|
||||
&& let Ok(cargo_toml) = toml::from_str::<CargoToml>(src)
|
||||
{
|
||||
let mut rustc_groups = FxHashSet::default();
|
||||
let mut clippy_groups = FxHashSet::default();
|
||||
for (group, ..) in unerased_lint_store(cx.tcx.sess).get_lint_groups() {
|
||||
match group.split_once("::") {
|
||||
None => {
|
||||
rustc_groups.insert(group);
|
||||
},
|
||||
Some(("clippy", group)) => {
|
||||
clippy_groups.insert(group);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file);
|
||||
check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod common_metadata;
|
||||
mod feature_name;
|
||||
mod lint_groups_priority;
|
||||
mod multiple_crate_versions;
|
||||
mod wildcard_dependencies;
|
||||
|
||||
|
@ -165,6 +166,43 @@ declare_clippy_lint! {
|
|||
"wildcard dependencies being used"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for lint groups with the same priority as lints in the `Cargo.toml`
|
||||
/// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section).
|
||||
///
|
||||
/// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918)
|
||||
/// is resolved.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The order of lints in the `[lints]` is ignored, to have a lint override a group the
|
||||
/// `priority` field needs to be used, otherwise the sort order is undefined.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Does not check lints inherited using `lints.workspace = true`
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic`
|
||||
/// # which results in `similar_names` being `warn`
|
||||
/// [lints.clippy]
|
||||
/// pedantic = "warn"
|
||||
/// similar_names = "allow"
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names`
|
||||
/// # which results in `similar_names` being `allow`
|
||||
/// [lints.clippy]
|
||||
/// pedantic = { level = "warn", priority = -1 }
|
||||
/// similar_names = "allow"
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub LINT_GROUPS_PRIORITY,
|
||||
correctness,
|
||||
"a lint group in `Cargo.toml` at the same priority as a lint"
|
||||
}
|
||||
|
||||
pub struct Cargo {
|
||||
pub allowed_duplicate_crates: FxHashSet<String>,
|
||||
pub ignore_publish: bool,
|
||||
|
@ -175,7 +213,8 @@ impl_lint_pass!(Cargo => [
|
|||
REDUNDANT_FEATURE_NAMES,
|
||||
NEGATIVE_FEATURE_NAMES,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
WILDCARD_DEPENDENCIES
|
||||
WILDCARD_DEPENDENCIES,
|
||||
LINT_GROUPS_PRIORITY,
|
||||
]);
|
||||
|
||||
impl LateLintPass<'_> for Cargo {
|
||||
|
@ -188,6 +227,8 @@ impl LateLintPass<'_> for Cargo {
|
|||
];
|
||||
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
|
||||
|
||||
lint_groups_priority::check(cx);
|
||||
|
||||
if !NO_DEPS_LINTS
|
||||
.iter()
|
||||
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
|
||||
|
|
|
@ -18,6 +18,7 @@ mod fn_to_numeric_cast_any;
|
|||
mod fn_to_numeric_cast_with_truncation;
|
||||
mod ptr_as_ptr;
|
||||
mod ptr_cast_constness;
|
||||
mod ref_as_ptr;
|
||||
mod unnecessary_cast;
|
||||
mod utils;
|
||||
mod zero_ptr;
|
||||
|
@ -689,6 +690,30 @@ declare_clippy_lint! {
|
|||
"using `0 as *{const, mut} T`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts of references to pointer using `as`
|
||||
/// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `as` casts may result in silently changing mutability or type.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let a_ref = &1;
|
||||
/// let a_ptr = a_ref as *const _;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let a_ref = &1;
|
||||
/// let a_ptr = std::ptr::from_ref(a_ref);
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub REF_AS_PTR,
|
||||
pedantic,
|
||||
"using `as` to cast a reference to pointer"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
@ -724,6 +749,7 @@ impl_lint_pass!(Casts => [
|
|||
AS_PTR_CAST_MUT,
|
||||
CAST_NAN_TO_INT,
|
||||
ZERO_PTR,
|
||||
REF_AS_PTR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
|
@ -771,7 +797,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
|
||||
as_underscore::check(cx, expr, cast_to_hir);
|
||||
|
||||
if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
if self.msrv.meets(msrvs::PTR_FROM_REF) {
|
||||
ref_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
}
|
||||
}
|
||||
|
|
55
clippy_lints/src/casts/ref_as_ptr.rs
Normal file
55
clippy_lints/src/casts/ref_as_ptr.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_no_std_crate;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, TypeAndMut};
|
||||
|
||||
use super::REF_AS_PTR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to_hir_ty: &Ty<'_>) {
|
||||
let (cast_from, cast_to) = (
|
||||
cx.typeck_results().expr_ty(cast_expr),
|
||||
cx.typeck_results().expr_ty(expr),
|
||||
);
|
||||
|
||||
if matches!(cast_from.kind(), ty::Ref(..))
|
||||
&& let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind()
|
||||
{
|
||||
let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
|
||||
let fn_name = match to_mutbl {
|
||||
Mutability::Not => "from_ref",
|
||||
Mutability::Mut => "from_mut",
|
||||
};
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let turbofish = match &cast_to_hir_ty.kind {
|
||||
TyKind::Infer => String::new(),
|
||||
TyKind::Ptr(mut_ty) => {
|
||||
if matches!(mut_ty.ty.kind, TyKind::Infer) {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
"::<{}>",
|
||||
snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app)
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REF_AS_PTR,
|
||||
expr.span,
|
||||
"reference as raw pointer",
|
||||
"try",
|
||||
format!("{core_or_std}::ptr::{fn_name}{turbofish}({cast_expr_sugg})"),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -71,6 +71,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
|
||||
crate::box_default::BOX_DEFAULT_INFO,
|
||||
crate::cargo::CARGO_COMMON_METADATA_INFO,
|
||||
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
|
||||
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
|
||||
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
|
||||
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
|
||||
|
@ -96,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
|
||||
crate::casts::PTR_AS_PTR_INFO,
|
||||
crate::casts::PTR_CAST_CONSTNESS_INFO,
|
||||
crate::casts::REF_AS_PTR_INFO,
|
||||
crate::casts::UNNECESSARY_CAST_INFO,
|
||||
crate::casts::ZERO_PTR_INFO,
|
||||
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
|
||||
|
@ -212,6 +214,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
|
||||
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
|
||||
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
|
||||
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
|
||||
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
|
||||
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
|
||||
crate::indexing_slicing::INDEXING_SLICING_INFO,
|
||||
|
@ -384,6 +387,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::ITER_SKIP_ZERO_INFO,
|
||||
crate::methods::ITER_WITH_DRAIN_INFO,
|
||||
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
|
||||
crate::methods::MANUAL_C_STR_LITERALS_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
crate::methods::MANUAL_FIND_MAP_INFO,
|
||||
crate::methods::MANUAL_IS_VARIANT_AND_INFO,
|
||||
|
@ -452,6 +456,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::UNNECESSARY_JOIN_INFO,
|
||||
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
|
||||
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
|
||||
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
|
||||
crate::methods::UNNECESSARY_SORT_BY_INFO,
|
||||
crate::methods::UNNECESSARY_TO_OWNED_INFO,
|
||||
crate::methods::UNWRAP_OR_DEFAULT_INFO,
|
||||
|
@ -656,6 +661,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
|
||||
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
|
||||
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
|
||||
crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,
|
||||
|
|
|
@ -226,7 +226,7 @@ declare_clippy_lint! {
|
|||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.40.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub TEST_ATTR_IN_DOCTEST,
|
||||
suspicious,
|
||||
"presence of `#[test]` in code examples"
|
||||
|
|
|
@ -3,15 +3,14 @@ use clippy_utils::higher::VecArgs;
|
|||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::type_diagnostic_name;
|
||||
use clippy_utils::usage::{local_used_after_expr, local_used_in};
|
||||
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use clippy_utils::{get_path_from_caller_to_method_type, higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{
|
||||
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
|
||||
ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
|
||||
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, ImplPolarity, List, Region, RegionKind,
|
||||
Ty, TypeVisitableExt, TypeckResults,
|
||||
};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
|
@ -21,8 +20,8 @@ use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for closures which just call another function where
|
||||
/// the function can be called directly. `unsafe` functions or calls where types
|
||||
/// get adjusted are ignored.
|
||||
/// the function can be called directly. `unsafe` functions, calls where types
|
||||
/// get adjusted or where the callee is marked `#[track_caller]` are ignored.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Needlessly creating a closure adds code for no benefit
|
||||
|
@ -136,7 +135,14 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
.map_or(callee_ty, |a| a.target.peel_refs());
|
||||
|
||||
let sig = match callee_ty_adjusted.kind() {
|
||||
ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(),
|
||||
ty::FnDef(def, _) => {
|
||||
// Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
|
||||
if cx.tcx.has_attr(*def, sym::track_caller) {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.tcx.fn_sig(def).skip_binder().skip_binder()
|
||||
},
|
||||
ty::FnPtr(sig) => sig.skip_binder(),
|
||||
ty::Closure(_, subs) => cx
|
||||
.tcx
|
||||
|
@ -186,6 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
},
|
||||
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
|
||||
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
|
||||
&& !cx.tcx.has_attr(method_def_id, sym::track_caller)
|
||||
&& check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder())
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
@ -195,11 +202,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
"redundant closure",
|
||||
|diag| {
|
||||
let args = typeck.node_args(body.value.hir_id);
|
||||
let name = get_ufcs_type_name(cx, method_def_id, args);
|
||||
let caller = self_.hir_id.owner.def_id;
|
||||
let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the method itself",
|
||||
format!("{}::{}", name, path.ident.name),
|
||||
format!("{}::{}", type_name, path.ident.name),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
|
@ -301,27 +309,3 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
|
|||
.zip(to_sig.inputs_and_output)
|
||||
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
|
||||
}
|
||||
|
||||
fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String {
|
||||
let assoc_item = cx.tcx.associated_item(method_def_id);
|
||||
let def_id = assoc_item.container_id(cx.tcx);
|
||||
match assoc_item.container {
|
||||
ty::TraitContainer => cx.tcx.def_path_str(def_id),
|
||||
ty::ImplContainer => {
|
||||
let ty = cx.tcx.type_of(def_id).instantiate_identity();
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
|
||||
ty::Array(..)
|
||||
| ty::Dynamic(..)
|
||||
| ty::Never
|
||||
| ty::RawPtr(_)
|
||||
| ty::Ref(..)
|
||||
| ty::Slice(_)
|
||||
| ty::Tuple(_) => {
|
||||
format!("<{}>", EarlyBinder::bind(ty).instantiate(cx.tcx, args))
|
||||
},
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,6 +181,9 @@ fn convert_to_from(
|
|||
let from = snippet_opt(cx, self_ty.span)?;
|
||||
let into = snippet_opt(cx, target_ty.span)?;
|
||||
|
||||
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
|
||||
.then_some(String::from("Self"))
|
||||
.unwrap_or_default();
|
||||
let mut suggestions = vec![
|
||||
// impl Into<T> for U -> impl From<T> for U
|
||||
// ~~~~ ~~~~
|
||||
|
@ -197,13 +200,10 @@ fn convert_to_from(
|
|||
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
|
||||
// ~~~~ ~~~~
|
||||
(self_ident.span, format!("val: {from}")),
|
||||
];
|
||||
|
||||
if let FnRetTy::Return(_) = sig.decl.output {
|
||||
// fn into(self) -> T -> fn into(self) -> Self
|
||||
// ~ ~~~~
|
||||
suggestions.push((sig.decl.output.span(), String::from("Self")));
|
||||
}
|
||||
(sig.decl.output.span(), return_type),
|
||||
];
|
||||
|
||||
let mut finder = SelfFinder {
|
||||
cx,
|
||||
|
|
133
clippy_lints/src/incompatible_msrv.rs
Normal file
133
clippy_lints/src/incompatible_msrv.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use clippy_config::msrvs::Msrv;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_attr::{StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// This lint checks that no function newer than the defined MSRV (minimum
|
||||
/// supported rust version) is used in the crate.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// It would prevent the crate to be actually used with the specified MSRV.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // MSRV of 1.3.0
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Sleep was defined in `1.4.0`.
|
||||
/// sleep(Duration::new(1, 0));
|
||||
/// ```
|
||||
///
|
||||
/// To fix this problem, either increase your MSRV or use another item
|
||||
/// available in your current MSRV.
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub INCOMPATIBLE_MSRV,
|
||||
suspicious,
|
||||
"ensures that all items used in the crate are available for the current MSRV"
|
||||
}
|
||||
|
||||
pub struct IncompatibleMsrv {
|
||||
msrv: Msrv,
|
||||
is_above_msrv: FxHashMap<DefId, RustcVersion>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
|
||||
|
||||
impl IncompatibleMsrv {
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
is_above_msrv: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_lossless)]
|
||||
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
|
||||
if let Some(version) = self.is_above_msrv.get(&def_id) {
|
||||
return *version;
|
||||
}
|
||||
let version = if let Some(version) = tcx
|
||||
.lookup_stability(def_id)
|
||||
.and_then(|stability| match stability.level {
|
||||
StabilityLevel::Stable {
|
||||
since: StableSince::Version(version),
|
||||
..
|
||||
} => Some(RustcVersion::new(
|
||||
version.major as _,
|
||||
version.minor as _,
|
||||
version.patch as _,
|
||||
)),
|
||||
_ => None,
|
||||
}) {
|
||||
version
|
||||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
|
||||
self.get_def_id_version(tcx, parent_def_id)
|
||||
} else {
|
||||
RustcVersion::new(1, 0, 0)
|
||||
};
|
||||
self.is_above_msrv.insert(def_id, version);
|
||||
version
|
||||
}
|
||||
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) {
|
||||
if def_id.is_local() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
return;
|
||||
}
|
||||
let version = self.get_def_id_version(cx.tcx, def_id);
|
||||
if self.msrv.meets(version) {
|
||||
return;
|
||||
}
|
||||
self.emit_lint_for(cx, span, version);
|
||||
}
|
||||
|
||||
fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) {
|
||||
span_lint(
|
||||
cx,
|
||||
INCOMPATIBLE_MSRV,
|
||||
span,
|
||||
&format!(
|
||||
"current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`",
|
||||
self.msrv
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
||||
extract_msrv_attr!(LateContext);
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if self.msrv.current().is_none() {
|
||||
// If there is no MSRV, then no need to check anything...
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, span) => {
|
||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
self.emit_lint_if_under_msrv(cx, method_did, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(call, [_]) => {
|
||||
if let ExprKind::Path(qpath) = call.kind
|
||||
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, call.span);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ declare_clippy_lint! {
|
|||
/// let value = &my_map[key];
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub ITER_OVER_HASH_TYPE,
|
||||
restriction,
|
||||
"iterating over unordered hash-based types (`HashMap` and `HashSet`)"
|
||||
|
|
|
@ -26,6 +26,7 @@ extern crate rustc_abi;
|
|||
extern crate rustc_arena;
|
||||
extern crate rustc_ast;
|
||||
extern crate rustc_ast_pretty;
|
||||
extern crate rustc_attr;
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_errors;
|
||||
|
@ -153,6 +154,7 @@ mod implicit_return;
|
|||
mod implicit_saturating_add;
|
||||
mod implicit_saturating_sub;
|
||||
mod implied_bounds_in_impls;
|
||||
mod incompatible_msrv;
|
||||
mod inconsistent_struct_constructor;
|
||||
mod index_refutable_slice;
|
||||
mod indexing_slicing;
|
||||
|
@ -325,6 +327,7 @@ mod temporary_assignment;
|
|||
mod tests_outside_test_module;
|
||||
mod thread_local_initializer_can_be_made_const;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_trait_impl;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
mod transmute;
|
||||
|
@ -521,6 +524,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
ref allowed_dotfiles,
|
||||
ref allowed_idents_below_min_chars,
|
||||
ref allowed_scripts,
|
||||
ref allowed_wildcard_imports,
|
||||
ref arithmetic_side_effects_allowed_binary,
|
||||
ref arithmetic_side_effects_allowed_unary,
|
||||
ref arithmetic_side_effects_allowed,
|
||||
|
@ -575,6 +579,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
check_private_items,
|
||||
pub_underscore_fields_behavior,
|
||||
ref allowed_duplicate_crates,
|
||||
allow_comparison_to_zero,
|
||||
|
||||
blacklisted_names: _,
|
||||
cyclomatic_complexity_threshold: _,
|
||||
|
@ -872,7 +877,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
))
|
||||
});
|
||||
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
|
||||
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(wildcard_imports::WildcardImports::new(
|
||||
warn_on_all_wildcard_imports,
|
||||
allowed_wildcard_imports.clone(),
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
|
||||
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
|
||||
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
|
||||
|
@ -968,7 +978,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
|
||||
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(operators::Operators::new(
|
||||
verbose_bit_mask_threshold,
|
||||
allow_comparison_to_zero,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
|
||||
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
|
||||
|
@ -1094,6 +1109,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(move |_| {
|
||||
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
|
||||
});
|
||||
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
@ -672,7 +672,7 @@ declare_clippy_lint! {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub INFINITE_LOOP,
|
||||
restriction,
|
||||
"possibly unintended infinite loop"
|
||||
|
|
|
@ -201,12 +201,12 @@ fn never_loop_expr<'tcx>(
|
|||
})
|
||||
})
|
||||
},
|
||||
ExprKind::Block(b, l) => {
|
||||
if l.is_some() {
|
||||
ExprKind::Block(b, _) => {
|
||||
if b.targeted_by_break {
|
||||
local_labels.push((b.hir_id, false));
|
||||
}
|
||||
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
|
||||
let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
|
||||
let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1;
|
||||
match ret {
|
||||
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
|
||||
_ => ret,
|
||||
|
|
|
@ -36,7 +36,8 @@ struct PathAndSpan {
|
|||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
|
|
@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
const ACCEPTABLE_METHODS: [&[&str]; 5] = [
|
||||
&paths::BINARYHEAP_ITER,
|
||||
|
@ -28,6 +29,7 @@ const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [
|
|||
(sym::Vec, None),
|
||||
(sym::VecDeque, None),
|
||||
];
|
||||
const MAP_TYPES: [rustc_span::Symbol; 2] = [sym::BTreeMap, sym::HashMap];
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -44,6 +46,7 @@ declare_clippy_lint! {
|
|||
/// ```no_run
|
||||
/// let mut vec = vec![0, 1, 2];
|
||||
/// vec.retain(|x| x % 2 == 0);
|
||||
/// vec.retain(|x| x % 2 == 0);
|
||||
/// ```
|
||||
#[clippy::version = "1.64.0"]
|
||||
pub MANUAL_RETAIN,
|
||||
|
@ -74,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
|
|||
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
|
||||
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
|
||||
{
|
||||
check_into_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_to_owned(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_into_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
check_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
check_to_owned(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,9 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
|
|||
|
||||
fn check_into_iter(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
|
||||
|
@ -98,16 +101,39 @@ fn check_into_iter(
|
|||
&& Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn()
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = target_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, target_expr);
|
||||
if match_map_type(cx, left_expr) {
|
||||
if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind {
|
||||
if let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) {
|
||||
make_span_lint_and_sugg(cx, parent_expr_span, sugg);
|
||||
}
|
||||
}
|
||||
// Cannot lint other cases because `retain` requires two parameters
|
||||
} else {
|
||||
// Can always move because `retain` and `filter` have the same bound on the predicate
|
||||
// for other types
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain({})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, closure_expr.span, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_iter(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
|
||||
|
@ -122,16 +148,50 @@ fn check_iter(
|
|||
&& match_acceptable_def_path(cx, iter_expr_def_id)
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, filter_expr);
|
||||
match filter_params.pat.kind {
|
||||
// hir::PatKind::Binding(_, _, _, None) => {
|
||||
// // Be conservative now. Do nothing here.
|
||||
// // TODO: Ideally, we can rewrite the lambda by stripping one level of reference
|
||||
// },
|
||||
hir::PatKind::Tuple([_, _], _) => {
|
||||
// the `&&` reference for the `filter` method will be auto derefed to `ref`
|
||||
// so, we can directly use the lambda
|
||||
// https://doc.rust-lang.org/reference/patterns.html#binding-modes
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain({})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, closure_expr.span, "..")
|
||||
),
|
||||
);
|
||||
},
|
||||
hir::PatKind::Ref(pat, _) => make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain(|{}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, pat.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
),
|
||||
),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_to_owned(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if msrv.meets(msrvs::STRING_RETAIN)
|
||||
|
@ -147,43 +207,25 @@ fn check_to_owned(
|
|||
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
|
||||
&& is_type_lang_item(cx, ty, hir::LangItem::String)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr)
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, filter_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) {
|
||||
if let hir::ExprKind::MethodCall(_, _, [closure], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = closure.kind
|
||||
&& let filter_body = cx.tcx.hir().body(body)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
&& let Some(sugg) = match filter_params.pat.kind {
|
||||
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
|
||||
"{}.retain(|{filter_param_ident}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
)),
|
||||
hir::PatKind::Tuple([key_pat, value_pat], _) => make_sugg(cx, key_pat, value_pat, left_expr, filter_body),
|
||||
hir::PatKind::Ref(pat, _) => match pat.kind {
|
||||
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
|
||||
"{}.retain(|{filter_param_ident}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RETAIN,
|
||||
parent_expr.span,
|
||||
"this expression can be written more simply using `.retain()`",
|
||||
"consider calling `.retain()` instead",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind {
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain(|{}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, pat.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
// Be conservative now. Do nothing for the `Binding` case.
|
||||
// TODO: Ideally, we can rewrite the lambda by stripping one level of reference
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,3 +271,20 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
|
|||
&& acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv))
|
||||
})
|
||||
}
|
||||
|
||||
fn match_map_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
|
||||
MAP_TYPES.iter().any(|ty| is_type_diagnostic_item(cx, expr_ty, *ty))
|
||||
}
|
||||
|
||||
fn make_span_lint_and_sugg(cx: &LateContext<'_>, span: Span, sugg: String) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RETAIN,
|
||||
span,
|
||||
"this expression can be written more simply using `.retain()`",
|
||||
"consider calling `.retain()` instead",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
197
clippy_lints/src/methods/manual_c_str_literals.rs
Normal file
197
clippy_lints/src/methods/manual_c_str_literals.rs
Normal file
|
@ -0,0 +1,197 @@
|
|||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_ast::{LitKind, StrStyle};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
use super::MANUAL_C_STR_LITERALS;
|
||||
|
||||
/// Checks:
|
||||
/// - `b"...".as_ptr()`
|
||||
/// - `b"...".as_ptr().cast()`
|
||||
/// - `"...".as_ptr()`
|
||||
/// - `"...".as_ptr().cast()`
|
||||
///
|
||||
/// Iff the parent call of `.cast()` isn't `CStr::from_ptr`, to avoid linting twice.
|
||||
pub(super) fn check_as_ptr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
receiver: &'tcx Expr<'tcx>,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let ExprKind::Lit(lit) = receiver.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node
|
||||
&& let casts_removed = peel_ptr_cast_ancestors(cx, expr)
|
||||
&& !get_parent_expr(cx, casts_removed).is_some_and(
|
||||
|parent| matches!(parent.kind, ExprKind::Call(func, _) if is_c_str_function(cx, func).is_some()),
|
||||
)
|
||||
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
|
||||
&& msrv.meets(msrvs::C_STR_LITERALS)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
receiver.span,
|
||||
"manually constructing a nul-terminated string",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
// an additional cast may be needed, since the type of `CStr::as_ptr` and
|
||||
// `"".as_ptr()` can differ and is platform dependent
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the callee is a "relevant" `CStr` function considered by this lint.
|
||||
/// Returns the function name.
|
||||
fn is_c_str_function(cx: &LateContext<'_>, func: &Expr<'_>) -> Option<Symbol> {
|
||||
if let ExprKind::Path(QPath::TypeRelative(cstr, fn_name)) = &func.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, ty_path)) = &cstr.kind
|
||||
&& cx.tcx.lang_items().c_str() == ty_path.res.opt_def_id()
|
||||
{
|
||||
Some(fn_name.ident.name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks calls to the `CStr` constructor functions:
|
||||
/// - `CStr::from_bytes_with_nul(..)`
|
||||
/// - `CStr::from_bytes_with_nul_unchecked(..)`
|
||||
/// - `CStr::from_ptr(..)`
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
|
||||
if let Some(fn_name) = is_c_str_function(cx, func)
|
||||
&& let [arg] = args
|
||||
&& msrv.meets(msrvs::C_STR_LITERALS)
|
||||
{
|
||||
match fn_name.as_str() {
|
||||
name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
|
||||
if !arg.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = arg.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node =>
|
||||
{
|
||||
check_from_bytes(cx, expr, arg, name);
|
||||
},
|
||||
"from_ptr" => check_from_ptr(cx, expr, arg),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
|
||||
fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(method, lit, ..) = peel_ptr_cast(arg).kind
|
||||
&& method.ident.name == sym::as_ptr
|
||||
&& !lit.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = lit.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) = lit.node
|
||||
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
expr.span,
|
||||
"calling `CStr::from_ptr` with a byte string literal",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
|
||||
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
|
||||
let (span, applicability) = if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::MethodCall(method, ..) = parent.kind
|
||||
&& [sym::unwrap, sym::expect].contains(&method.ident.name)
|
||||
{
|
||||
(parent.span, Applicability::MachineApplicable)
|
||||
} else if method == "from_bytes_with_nul_unchecked" {
|
||||
// `*_unchecked` returns `&CStr` directly, nothing needs to be changed
|
||||
(expr.span, Applicability::MachineApplicable)
|
||||
} else {
|
||||
// User needs to remove error handling, can't be machine applicable
|
||||
(expr.span, Applicability::HasPlaceholders)
|
||||
};
|
||||
|
||||
let Some(sugg) = rewrite_as_cstr(cx, arg.span) else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
span,
|
||||
"calling `CStr::new` with a byte string literal",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
/// Rewrites a byte string literal to a c-str literal.
|
||||
/// `b"foo\0"` -> `c"foo"`
|
||||
///
|
||||
/// Returns `None` if it doesn't end in a NUL byte.
|
||||
fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> Option<String> {
|
||||
let mut sugg = String::from("c") + snippet(cx, span.source_callsite(), "..").trim_start_matches('b');
|
||||
|
||||
// NUL byte should always be right before the closing quote.
|
||||
if let Some(quote_pos) = sugg.rfind('"') {
|
||||
// Possible values right before the quote:
|
||||
// - literal NUL value
|
||||
if sugg.as_bytes()[quote_pos - 1] == b'\0' {
|
||||
sugg.remove(quote_pos - 1);
|
||||
}
|
||||
// - \x00
|
||||
else if sugg[..quote_pos].ends_with("\\x00") {
|
||||
sugg.replace_range(quote_pos - 4..quote_pos, "");
|
||||
}
|
||||
// - \0
|
||||
else if sugg[..quote_pos].ends_with("\\0") {
|
||||
sugg.replace_range(quote_pos - 2..quote_pos, "");
|
||||
}
|
||||
// No known suffix, so assume it's not a C-string.
|
||||
else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(sugg)
|
||||
}
|
||||
|
||||
fn get_cast_target<'tcx>(e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match &e.kind {
|
||||
ExprKind::MethodCall(method, receiver, [], _) if method.ident.as_str() == "cast" => Some(receiver),
|
||||
ExprKind::Cast(expr, _) => Some(expr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `x.cast()` -> `x`
|
||||
/// `x as *const _` -> `x`
|
||||
/// `x` -> `x` (returns the same expression for non-cast exprs)
|
||||
fn peel_ptr_cast<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
get_cast_target(e).map_or(e, peel_ptr_cast)
|
||||
}
|
||||
|
||||
/// Same as `peel_ptr_cast`, but the other way around, by walking up the ancestor cast expressions:
|
||||
///
|
||||
/// `foo(x.cast() as *const _)`
|
||||
/// ^ given this `x` expression, returns the `foo(...)` expression
|
||||
fn peel_ptr_cast_ancestors<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
let mut prev = e;
|
||||
for (_, node) in cx.tcx.hir().parent_iter(e.hir_id) {
|
||||
if let Node::Expr(e) = node
|
||||
&& get_cast_target(e).is_some()
|
||||
{
|
||||
prev = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
prev
|
||||
}
|
|
@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
|
|||
unwrap_arg: &'tcx hir::Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
) -> bool {
|
||||
// lint if the caller of `map()` is an `Option`
|
||||
// lint if the caller of `map()` is an `Option` or a `Result`.
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ mod iter_skip_zero;
|
|||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod manual_c_str_literals;
|
||||
mod manual_is_variant_and;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
|
@ -113,6 +114,7 @@ mod unnecessary_iter_cloned;
|
|||
mod unnecessary_join;
|
||||
mod unnecessary_lazy_eval;
|
||||
mod unnecessary_literal_unwrap;
|
||||
mod unnecessary_result_map_or_else;
|
||||
mod unnecessary_sort_by;
|
||||
mod unnecessary_to_owned;
|
||||
mod unwrap_expect_used;
|
||||
|
@ -3951,6 +3953,64 @@ declare_clippy_lint! {
|
|||
"cloning an `Option` via `as_ref().cloned()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely by using `unwrap_or_else()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # fn handle_error(_: ()) -> u32 { 0 }
|
||||
/// let x: Result<u32, ()> = Ok(0);
|
||||
/// let y = x.map_or_else(|err| handle_error(err), |n| n);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # fn handle_error(_: ()) -> u32 { 0 }
|
||||
/// let x: Result<u32, ()> = Ok(0);
|
||||
/// let y = x.unwrap_or_else(|err| handle_error(err));
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
suspicious,
|
||||
"making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either
|
||||
/// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()`
|
||||
/// on a (byte) string literal with a hardcoded `\0` byte at the end.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely using `c"str"` literals and is also less error-prone,
|
||||
/// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CStr;
|
||||
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
|
||||
/// fn needs_cstr(_: &CStr) {}
|
||||
///
|
||||
/// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||
/// unsafe { libc::puts("World\0".as_ptr().cast()) }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CStr;
|
||||
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
|
||||
/// fn needs_cstr(_: &CStr) {}
|
||||
///
|
||||
/// needs_cstr(c"Hello");
|
||||
/// unsafe { libc::puts(c"World".as_ptr()) }
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub MANUAL_C_STR_LITERALS,
|
||||
pedantic,
|
||||
r#"creating a `CStr` through functions when `c""` literals can be used"#
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
@ -4109,6 +4169,8 @@ impl_lint_pass!(Methods => [
|
|||
MANUAL_IS_VARIANT_AND,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
OPTION_AS_REF_CLONED,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
@ -4136,6 +4198,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
hir::ExprKind::Call(func, args) => {
|
||||
from_iter_instead_of_collect::check(cx, expr, args, func);
|
||||
unnecessary_fallible_conversions::check_function(cx, expr, func);
|
||||
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
|
||||
},
|
||||
hir::ExprKind::MethodCall(method_call, receiver, args, _) => {
|
||||
let method_span = method_call.ident.span;
|
||||
|
@ -4354,6 +4417,7 @@ impl Methods {
|
|||
}
|
||||
},
|
||||
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
|
||||
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
|
||||
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
|
||||
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
|
||||
("cloned", []) => {
|
||||
|
@ -4592,6 +4656,7 @@ impl Methods {
|
|||
},
|
||||
("map_or_else", [def, map]) => {
|
||||
result_map_or_else_none::check(cx, expr, recv, def, map);
|
||||
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
|
||||
},
|
||||
("next", []) => {
|
||||
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
|
||||
|
|
|
@ -99,7 +99,6 @@ fn check_fold_with_op(
|
|||
cx,
|
||||
UNNECESSARY_FOLD,
|
||||
fold_span.with_hi(expr.span.hi()),
|
||||
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
|
||||
"this `.fold` can be written more succinctly using another method",
|
||||
"try",
|
||||
sugg,
|
||||
|
|
95
clippy_lints/src/methods/unnecessary_result_map_or_else.rs
Normal file
95
clippy_lints/src/methods/unnecessary_result_map_or_else.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::peel_blocks;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
|
||||
|
||||
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
|
||||
let msg = "unused \"map closure\" when calling `Result::map_or_else` value";
|
||||
let self_snippet = snippet(cx, recv.span, "..");
|
||||
let err_snippet = snippet(cx, def_arg.span, "..");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
expr.span,
|
||||
msg,
|
||||
"consider using `unwrap_or_else`",
|
||||
format!("{self_snippet}.unwrap_or_else({err_snippet})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
|
||||
for stmt in statements {
|
||||
if let StmtKind::Local(local) = stmt.kind
|
||||
&& let Some(init) = local.init
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
|
||||
&& let hir::def::Res::Local(local_hir_id) = path.res
|
||||
&& local_hir_id == hir_id
|
||||
{
|
||||
hir_id = local.pat.hir_id;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(hir_id)
|
||||
}
|
||||
|
||||
fn handle_qpath(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
def_arg: &Expr<'_>,
|
||||
expected_hir_id: HirId,
|
||||
qpath: QPath<'_>,
|
||||
) {
|
||||
if let QPath::Resolved(_, path) = qpath
|
||||
&& let hir::def::Res::Local(hir_id) = path.res
|
||||
&& expected_hir_id == hir_id
|
||||
{
|
||||
emit_lint(cx, expr, recv, def_arg);
|
||||
}
|
||||
}
|
||||
|
||||
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Result`s.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
recv: &'tcx Expr<'_>,
|
||||
def_arg: &'tcx Expr<'_>,
|
||||
map_arg: &'tcx Expr<'_>,
|
||||
) {
|
||||
// lint if the caller of `map_or_else()` is a `Result`
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
|
||||
&& let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind
|
||||
&& let body = cx.tcx.hir().body(body)
|
||||
&& let Some(first_param) = body.params.first()
|
||||
{
|
||||
let body_expr = peel_blocks(body.value);
|
||||
|
||||
match body_expr.kind {
|
||||
ExprKind::Path(qpath) => {
|
||||
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
|
||||
},
|
||||
// If this is a block (that wasn't peeled off), then it means there are statements.
|
||||
ExprKind::Block(block, _) => {
|
||||
if let Some(block_expr) = block.expr
|
||||
// First we ensure that this is a "binding chain" (each statement is a binding
|
||||
// of the previous one) and that it is a binding of the closure argument.
|
||||
&& let Some(last_chain_binding_id) =
|
||||
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
|
||||
&& let ExprKind::Path(qpath) = block_expr.kind
|
||||
{
|
||||
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::iter;
|
||||
|
|
|
@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
|||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
|
|
@ -357,7 +357,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
|
|||
}
|
||||
},
|
||||
ExprKind::Block(block, _) => {
|
||||
if block.stmts.is_empty() {
|
||||
if block.stmts.is_empty() && !block.targeted_by_break {
|
||||
block.expr.as_ref().and_then(|e| {
|
||||
match block.rules {
|
||||
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,
|
||||
|
|
|
@ -252,7 +252,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
|
|||
{
|
||||
(
|
||||
trait_item_id,
|
||||
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.args) as *const _ as usize),
|
||||
FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_regions(trait_ref.args)) as usize),
|
||||
usize::from(sig.decl.implicit_self.has_implicit_self()),
|
||||
)
|
||||
} else {
|
||||
|
@ -390,7 +390,6 @@ fn has_matching_args(kind: FnKind, args: GenericArgsRef<'_>) -> bool {
|
|||
GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx),
|
||||
GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
|
||||
}),
|
||||
#[allow(trivial_casts)]
|
||||
FnKind::ImplTraitFn(expected_args) => args as *const _ as usize == expected_args,
|
||||
FnKind::ImplTraitFn(expected_args) => std::ptr::from_ref(args) as usize == expected_args,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -771,6 +771,7 @@ declare_clippy_lint! {
|
|||
pub struct Operators {
|
||||
arithmetic_context: numeric_arithmetic::Context,
|
||||
verbose_bit_mask_threshold: u64,
|
||||
modulo_arithmetic_allow_comparison_to_zero: bool,
|
||||
}
|
||||
impl_lint_pass!(Operators => [
|
||||
ABSURD_EXTREME_COMPARISONS,
|
||||
|
@ -801,10 +802,11 @@ impl_lint_pass!(Operators => [
|
|||
SELF_ASSIGNMENT,
|
||||
]);
|
||||
impl Operators {
|
||||
pub fn new(verbose_bit_mask_threshold: u64) -> Self {
|
||||
pub fn new(verbose_bit_mask_threshold: u64, modulo_arithmetic_allow_comparison_to_zero: bool) -> Self {
|
||||
Self {
|
||||
arithmetic_context: numeric_arithmetic::Context::default(),
|
||||
verbose_bit_mask_threshold,
|
||||
modulo_arithmetic_allow_comparison_to_zero,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -835,12 +837,19 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
cmp_owned::check(cx, op.node, lhs, rhs);
|
||||
float_cmp::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_one::check(cx, e, op.node, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(
|
||||
cx,
|
||||
e,
|
||||
op.node,
|
||||
lhs,
|
||||
rhs,
|
||||
self.modulo_arithmetic_allow_comparison_to_zero,
|
||||
);
|
||||
},
|
||||
ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
|
||||
misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs, false);
|
||||
},
|
||||
ExprKind::Assign(lhs, rhs, _) => {
|
||||
assign_op_pattern::check(cx, e, lhs, rhs);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sext;
|
||||
use rustc_hir::{BinOpKind, Expr};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use std::fmt::Display;
|
||||
|
@ -14,8 +14,13 @@ pub(super) fn check<'tcx>(
|
|||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
allow_comparison_to_zero: bool,
|
||||
) {
|
||||
if op == BinOpKind::Rem {
|
||||
if allow_comparison_to_zero && used_in_comparison_with_zero(cx, e) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lhs_operand = analyze_operand(lhs, cx, e);
|
||||
let rhs_operand = analyze_operand(rhs, cx, e);
|
||||
if let Some(lhs_operand) = lhs_operand
|
||||
|
@ -28,6 +33,26 @@ pub(super) fn check<'tcx>(
|
|||
};
|
||||
}
|
||||
|
||||
fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find_parent(expr.hir_id) else {
|
||||
return false;
|
||||
};
|
||||
let ExprKind::Binary(op, lhs, rhs) = parent_expr.kind else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if op.node == BinOpKind::Eq || op.node == BinOpKind::Ne {
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), rhs) {
|
||||
return true;
|
||||
}
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), lhs) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
struct OperandInfo {
|
||||
string_representation: Option<String>,
|
||||
is_negative: bool,
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::rustc_lint::LintContext;
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use hir::Param;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
|
||||
|
@ -13,6 +14,7 @@ use rustc_middle::hir::nested_filter;
|
|||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::ExpnKind;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -89,7 +91,12 @@ fn find_innermost_closure<'tcx>(
|
|||
cx: &LateContext<'tcx>,
|
||||
mut expr: &'tcx hir::Expr<'tcx>,
|
||||
mut steps: usize,
|
||||
) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, ty::Asyncness)> {
|
||||
) -> Option<(
|
||||
&'tcx hir::Expr<'tcx>,
|
||||
&'tcx hir::FnDecl<'tcx>,
|
||||
ty::Asyncness,
|
||||
&'tcx [Param<'tcx>],
|
||||
)> {
|
||||
let mut data = None;
|
||||
|
||||
while let hir::ExprKind::Closure(closure) = expr.kind
|
||||
|
@ -110,6 +117,7 @@ fn find_innermost_closure<'tcx>(
|
|||
} else {
|
||||
ty::Asyncness::No
|
||||
},
|
||||
body.params,
|
||||
));
|
||||
steps -= 1;
|
||||
}
|
||||
|
@ -152,7 +160,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
|||
// without this check, we'd end up linting twice.
|
||||
&& !matches!(recv.kind, hir::ExprKind::Call(..))
|
||||
&& let (full_expr, call_depth) = get_parent_call_exprs(cx, expr)
|
||||
&& let Some((body, fn_decl, coroutine_kind)) = find_innermost_closure(cx, recv, call_depth)
|
||||
&& let Some((body, fn_decl, coroutine_kind, params)) = find_innermost_closure(cx, recv, call_depth)
|
||||
// outside macros we lint properly. Inside macros, we lint only ||() style closures.
|
||||
&& (!matches!(expr.span.ctxt().outer_expn_data().kind, ExpnKind::Macro(_, _)) || params.is_empty())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
|
@ -4,8 +4,10 @@ use clippy_utils::ty::needs_ordered_drop;
|
|||
use rustc_ast::Mutability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{BindingAnnotation, ByRef, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
|
||||
use rustc_hir_typeck::expr_use_visitor::PlaceBase;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::UpvarCapture;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::DesugaringKind;
|
||||
|
@ -69,6 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
// the local is user-controlled
|
||||
&& !in_external_macro(cx.sess(), local.span)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& !is_by_value_closure_capture(cx, local.hir_id, binding_id)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -82,6 +85,29 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the enclosing body is a closure and if the given local is captured by value.
|
||||
///
|
||||
/// In those cases, the redefinition may be necessary to force a move:
|
||||
/// ```
|
||||
/// fn assert_static<T: 'static>(_: T) {}
|
||||
///
|
||||
/// let v = String::new();
|
||||
/// let closure = || {
|
||||
/// let v = v; // <- removing this redefinition makes `closure` no longer `'static`
|
||||
/// dbg!(&v);
|
||||
/// };
|
||||
/// assert_static(closure);
|
||||
/// ```
|
||||
fn is_by_value_closure_capture(cx: &LateContext<'_>, redefinition: HirId, root_variable: HirId) -> bool {
|
||||
let closure_def_id = cx.tcx.hir().enclosing_body_owner(redefinition);
|
||||
|
||||
cx.tcx.is_closure_or_coroutine(closure_def_id.to_def_id())
|
||||
&& cx.tcx.closure_captures(closure_def_id).iter().any(|c| {
|
||||
matches!(c.info.capture_kind, UpvarCapture::ByValue)
|
||||
&& matches!(c.place.base, PlaceBase::Upvar(upvar) if upvar.var_path.hir_id == root_variable)
|
||||
})
|
||||
}
|
||||
|
||||
/// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced.
|
||||
fn find_binding(pat: &Pat<'_>, name: Ident) -> Option<BindingAnnotation> {
|
||||
let mut ret = None;
|
||||
|
|
|
@ -188,7 +188,6 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
|
|||
match init_lit.node {
|
||||
// In these cases the annotation is redundant
|
||||
LitKind::Str(..)
|
||||
| LitKind::ByteStr(..)
|
||||
| LitKind::Byte(..)
|
||||
| LitKind::Char(..)
|
||||
| LitKind::Bool(..)
|
||||
|
@ -202,6 +201,16 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
|
|||
}
|
||||
},
|
||||
LitKind::Err => (),
|
||||
LitKind::ByteStr(..) => {
|
||||
// We only lint if the type annotation is an array type (e.g. &[u8; 4]).
|
||||
// If instead it is a slice (e.g. &[u8]) it may not be redundant, so we
|
||||
// don't lint.
|
||||
if let hir::TyKind::Ref(_, mut_ty) = ty.kind
|
||||
&& matches!(mut_ty.ty.kind, hir::TyKind::Array(..))
|
||||
{
|
||||
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
@ -42,7 +42,7 @@ declare_clippy_lint! {
|
|||
/// // ^^^ this closure executes 123 times
|
||||
/// // and the vecs will have the expected capacity
|
||||
/// ```
|
||||
#[clippy::version = "1.74.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub REPEAT_VEC_WITH_CAPACITY,
|
||||
suspicious,
|
||||
"repeating a `Vec::with_capacity` expression which does not retain capacity"
|
||||
|
|
|
@ -2,10 +2,14 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lin
|
|||
use clippy_utils::source::{snippet_opt, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
|
||||
use clippy_utils::{fn_def_id, is_from_proc_macro, is_inside_let_else, path_to_local_id, span_find_starting_semi};
|
||||
use clippy_utils::{
|
||||
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id,
|
||||
span_find_starting_semi,
|
||||
};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::LangItem::ResultErr;
|
||||
use rustc_hir::{
|
||||
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
|
||||
StmtKind,
|
||||
|
@ -18,6 +22,7 @@ use rustc_session::declare_lint_pass;
|
|||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{BytePos, Pos, Span};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -146,14 +151,14 @@ impl<'tcx> RetReplacement<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ToString for RetReplacement<'tcx> {
|
||||
fn to_string(&self) -> String {
|
||||
impl<'tcx> Display for RetReplacement<'tcx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Empty => String::new(),
|
||||
Self::Block => "{}".to_string(),
|
||||
Self::Unit => "()".to_string(),
|
||||
Self::IfSequence(inner, _) => format!("({inner})"),
|
||||
Self::Expr(inner, _) => inner.to_string(),
|
||||
Self::Empty => write!(f, ""),
|
||||
Self::Block => write!(f, "{{}}"),
|
||||
Self::Unit => write!(f, "()"),
|
||||
Self::IfSequence(inner, _) => write!(f, "({inner})"),
|
||||
Self::Expr(inner, _) => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +186,15 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
|||
if !in_external_macro(cx.sess(), stmt.span)
|
||||
&& let StmtKind::Semi(expr) = stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
||||
&& let ExprKind::Match(.., MatchSource::TryDesugar(_)) = ret.kind
|
||||
// return Err(...)? desugars to a match
|
||||
// over a Err(...).branch()
|
||||
// which breaks down to a branch call, with the callee being
|
||||
// the constructor of the Err variant
|
||||
&& let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind
|
||||
&& let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind
|
||||
&& let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr)
|
||||
|
||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir().get_parent_item(expr.hir_id))
|
||||
&& let ItemKind::Fn(_, _, body) = item.kind
|
||||
|
|
67
clippy_lints/src/to_string_trait_impl.rs
Normal file
67
clippy_lints/src/to_string_trait_impl.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for direct implementations of `ToString`.
|
||||
/// ### Why is this bad?
|
||||
/// This trait is automatically implemented for any type which implements the `Display` trait.
|
||||
/// As such, `ToString` shouldn’t be implemented directly: `Display` should be implemented instead,
|
||||
/// and you get the `ToString` implementation for free.
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// struct Point {
|
||||
/// x: usize,
|
||||
/// y: usize,
|
||||
/// }
|
||||
///
|
||||
/// impl ToString for Point {
|
||||
/// fn to_string(&self) -> String {
|
||||
/// format!("({}, {})", self.x, self.y)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// struct Point {
|
||||
/// x: usize,
|
||||
/// y: usize,
|
||||
/// }
|
||||
///
|
||||
/// impl std::fmt::Display for Point {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// write!(f, "({}, {})", self.x, self.y)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub TO_STRING_TRAIT_IMPL,
|
||||
style,
|
||||
"check for direct implementations of `ToString`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(ToStringTraitImpl => [TO_STRING_TRAIT_IMPL]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'tcx>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(trait_ref),
|
||||
..
|
||||
}) = it.kind
|
||||
&& let Some(trait_did) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_did)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
TO_STRING_TRAIT_IMPL,
|
||||
it.span,
|
||||
"direct implementation of `ToString`",
|
||||
None,
|
||||
"prefer implementing `Display` instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,14 +69,6 @@ fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) {
|
|||
);
|
||||
}
|
||||
|
||||
fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
|
||||
match ty.peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()),
|
||||
ty::Foreign(def_id) => Some(*def_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> {
|
||||
let TyKind::Path(qpath) = hir_ty.kind else { return None };
|
||||
match qpath {
|
||||
|
@ -131,21 +123,49 @@ fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Opt
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
/// When we have `x == y` where `x = &T` and `y = &T`, then that resolves to
|
||||
/// `<&T as PartialEq<&T>>::eq`, which is not the same as `<T as PartialEq<T>>::eq`,
|
||||
/// however we still would want to treat it the same, because we know that it's a blanket impl
|
||||
/// that simply delegates to the `PartialEq` impl with one reference removed.
|
||||
///
|
||||
/// Still, we can't just do `lty.peel_refs() == rty.peel_refs()` because when we have `x = &T` and
|
||||
/// `y = &&T`, this is not necessarily the same as `<T as PartialEq<T>>::eq`
|
||||
///
|
||||
/// So to avoid these FNs and FPs, we keep removing a layer of references from *both* sides
|
||||
/// until both sides match the expected LHS and RHS type (or they don't).
|
||||
fn matches_ty<'tcx>(
|
||||
mut left: Ty<'tcx>,
|
||||
mut right: Ty<'tcx>,
|
||||
expected_left: Ty<'tcx>,
|
||||
expected_right: Ty<'tcx>,
|
||||
) -> bool {
|
||||
while let (&ty::Ref(_, lty, _), &ty::Ref(_, rty, _)) = (left.kind(), right.kind()) {
|
||||
if lty == expected_left && rty == expected_right {
|
||||
return true;
|
||||
}
|
||||
left = lty;
|
||||
right = rty;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
|
||||
let args = cx
|
||||
.tcx
|
||||
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
|
||||
.inputs();
|
||||
let Some(sig) = cx
|
||||
.typeck_results()
|
||||
.liberated_fn_sigs()
|
||||
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// That has two arguments.
|
||||
if let [self_arg, other_arg] = args
|
||||
&& let Some(self_arg) = get_ty_def_id(*self_arg)
|
||||
&& let Some(other_arg) = get_ty_def_id(*other_arg)
|
||||
if let [self_arg, other_arg] = sig.inputs()
|
||||
&& let &ty::Ref(_, self_arg, _) = self_arg.kind()
|
||||
&& let &ty::Ref(_, other_arg, _) = other_arg.kind()
|
||||
// The two arguments are of the same type.
|
||||
&& self_arg == other_arg
|
||||
&& let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id)
|
||||
// The trait is `PartialEq`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
|
||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, trait_def_id)
|
||||
{
|
||||
let to_check_op = if name.name == sym::eq {
|
||||
BinOpKind::Eq
|
||||
|
@ -154,31 +174,19 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
|
|||
};
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::Binary(op, left, right) if op.node == to_check_op => {
|
||||
// Then we check if the left-hand element is of the same type as `self`.
|
||||
if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left)
|
||||
&& let Some(left_id) = get_ty_def_id(left_ty)
|
||||
&& self_arg == left_id
|
||||
&& let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
|
||||
&& let Some(right_id) = get_ty_def_id(right_ty)
|
||||
&& other_arg == right_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// Then we check if the LHS matches self_arg and RHS matches other_arg
|
||||
let left_ty = cx.typeck_results().expr_ty_adjusted(left);
|
||||
let right_ty = cx.typeck_results().expr_ty_adjusted(right);
|
||||
matches_ty(left_ty, right_ty, self_arg, other_arg)
|
||||
},
|
||||
ExprKind::MethodCall(segment, receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||
if let Some(ty) = cx.typeck_results().expr_ty_opt(receiver)
|
||||
&& let Some(ty_id) = get_ty_def_id(ty)
|
||||
&& self_arg != ty_id
|
||||
{
|
||||
// Since this called on a different type, the lint should not be
|
||||
// triggered here.
|
||||
return;
|
||||
}
|
||||
ExprKind::MethodCall(segment, receiver, [arg], _) if segment.ident.name == name.name => {
|
||||
let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver);
|
||||
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
|
||||
|
||||
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& trait_id == trait_def_id
|
||||
&& matches_ty(receiver_ty, arg_ty, self_arg, other_arg)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
|
||||
use hir::{ExprKind, PatKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
@ -82,37 +83,72 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
|
|||
}
|
||||
|
||||
if let Some(exp) = block.expr
|
||||
&& matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _))
|
||||
&& matches!(
|
||||
exp.kind,
|
||||
hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, hir::MatchSource::Normal)
|
||||
)
|
||||
{
|
||||
check_expr(cx, exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
||||
// if there is a guard, we consider the result to be consumed
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
if is_unreachable_or_panic(cx, arm.body) {
|
||||
// if the body is unreachable or there is a panic,
|
||||
// we consider the result to be consumed
|
||||
return false;
|
||||
}
|
||||
|
||||
if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind {
|
||||
return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn non_consuming_ok_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
||||
// if there is a guard, we consider the result to be consumed
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
if is_unreachable_or_panic(cx, arm.body) {
|
||||
// if the body is unreachable or there is a panic,
|
||||
// we consider the result to be consumed
|
||||
return false;
|
||||
}
|
||||
|
||||
if is_ok_wild_or_dotdot_pattern(cx, arm.pat) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::If(cond, _, _)
|
||||
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
|
||||
&& pattern_is_ignored_ok(cx, pat)
|
||||
&& is_ok_wild_or_dotdot_pattern(cx, pat)
|
||||
&& let Some(op) = should_lint(cx, init) =>
|
||||
{
|
||||
emit_lint(cx, cond.span, op, &[pat.span]);
|
||||
},
|
||||
hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||
let found_arms: Vec<_> = arms
|
||||
.iter()
|
||||
.filter_map(|arm| {
|
||||
if pattern_is_ignored_ok(cx, arm.pat) {
|
||||
Some(arm.span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !found_arms.is_empty() {
|
||||
emit_lint(cx, expr.span, op, found_arms.as_slice());
|
||||
// we will capture only the case where the match is Ok( ) or Err( )
|
||||
// prefer to match the minimum possible, and expand later if needed
|
||||
// to avoid false positives on something as used as this
|
||||
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
|
||||
emit_lint(cx, expr.span, op, &[arm1.pat.span]);
|
||||
}
|
||||
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
|
||||
emit_lint(cx, expr.span, op, &[arm2.pat.span]);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
|
||||
_ if let Some(op) = should_lint(cx, expr) => {
|
||||
emit_lint(cx, expr.span, op, &[]);
|
||||
},
|
||||
|
@ -130,25 +166,40 @@ fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option
|
|||
check_io_mode(cx, inner)
|
||||
}
|
||||
|
||||
fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool {
|
||||
fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> bool {
|
||||
// the if checks whether we are in a result Ok( ) pattern
|
||||
// and the return checks whether it is unhandled
|
||||
|
||||
if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind
|
||||
if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind
|
||||
// we check against Result::Ok to avoid linting on Err(_) or something else.
|
||||
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
|
||||
{
|
||||
return match (inner_pat, ddp.as_opt_usize()) {
|
||||
// Ok(_) pattern
|
||||
([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true,
|
||||
// Ok(..) pattern
|
||||
([], Some(0)) => true,
|
||||
_ => false,
|
||||
};
|
||||
if matches!(inner_pat, []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let [cons_pat] = inner_pat
|
||||
&& matches!(cons_pat.kind, PatKind::Wild)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// this is partially taken from panic_unimplemented
|
||||
fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
let expr = peel_blocks(expr);
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return false;
|
||||
};
|
||||
if is_panic(cx, macro_call.def_id) {
|
||||
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
|
||||
}
|
||||
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
|
||||
}
|
||||
|
||||
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
|
||||
if matches!(
|
||||
|
|
|
@ -490,9 +490,9 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
|||
format!("ClosureKind::Coroutine(CoroutineKind::Coroutine(Movability::{movability:?})")
|
||||
},
|
||||
},
|
||||
ClosureKind::CoroutineClosure(desugaring) => format!(
|
||||
"ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})"
|
||||
),
|
||||
ClosureKind::CoroutineClosure(desugaring) => {
|
||||
format!("ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})")
|
||||
},
|
||||
};
|
||||
|
||||
let ret_ty = match fn_decl.output {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_test_module_or_function;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
|
||||
|
@ -100,13 +101,15 @@ declare_clippy_lint! {
|
|||
pub struct WildcardImports {
|
||||
warn_on_all: bool,
|
||||
test_modules_deep: u32,
|
||||
allowed_segments: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl WildcardImports {
|
||||
pub fn new(warn_on_all: bool) -> Self {
|
||||
pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self {
|
||||
Self {
|
||||
warn_on_all,
|
||||
test_modules_deep: 0,
|
||||
allowed_segments: allowed_wildcard_imports,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +193,7 @@ impl WildcardImports {
|
|||
item.span.from_expansion()
|
||||
|| is_prelude_import(segments)
|
||||
|| (is_super_only_import(segments) && self.test_modules_deep > 0)
|
||||
|| is_allowed_via_config(segments, &self.allowed_segments)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,10 +202,18 @@ impl WildcardImports {
|
|||
fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
|
||||
segments
|
||||
.iter()
|
||||
.any(|ps| ps.ident.name.as_str().contains(sym::prelude.as_str()))
|
||||
.any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
|
||||
}
|
||||
|
||||
// Allow "super::*" imports in tests.
|
||||
fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
|
||||
segments.len() == 1 && segments[0].ident.name == kw::Super
|
||||
}
|
||||
|
||||
// Allow skipping imports containing user configured segments,
|
||||
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
|
||||
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
|
||||
// segment matching need to be exact instead of using 'contains', in case user unintentionaly put
|
||||
// a single character in the config thus skipping most of the warnings.
|
||||
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
|
||||
}
|
||||
|
|
|
@ -84,9 +84,9 @@ pub fn span_lint_and_help<T: LintContext>(
|
|||
cx.span_lint(lint, span, msg.to_string(), |diag| {
|
||||
let help = help.to_string();
|
||||
if let Some(help_span) = help_span {
|
||||
diag.span_help(help_span, help.to_string());
|
||||
diag.span_help(help_span, help);
|
||||
} else {
|
||||
diag.help(help.to_string());
|
||||
diag.help(help);
|
||||
}
|
||||
docs_link(diag, lint);
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![feature(array_chunks)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(control_flow_enum)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(lint_reasons)]
|
||||
|
@ -75,6 +76,7 @@ use core::mem;
|
|||
use core::ops::ControlFlow;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::iter::{once, repeat};
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
@ -84,6 +86,7 @@ use rustc_data_structures::packed::Pu128;
|
|||
use rustc_data_structures::unhash::UnhashMap;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE};
|
||||
use rustc_hir::definitions::{DefPath, DefPathData};
|
||||
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
|
||||
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
|
||||
|
@ -102,8 +105,8 @@ use rustc_middle::ty::binding::BindingMode;
|
|||
use rustc_middle::ty::fast_reject::SimplifiedType;
|
||||
use rustc_middle::ty::layout::IntegerExt;
|
||||
use rustc_middle::ty::{
|
||||
self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut,
|
||||
TypeVisitableExt, UintTy, UpvarCapture,
|
||||
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, ParamEnv,
|
||||
ParamEnvAnd, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture,
|
||||
};
|
||||
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
||||
use rustc_span::source_map::SourceMap;
|
||||
|
@ -114,10 +117,7 @@ use visitors::Visitable;
|
|||
|
||||
use crate::consts::{constant, mir_to_const, Constant};
|
||||
use crate::higher::Range;
|
||||
use crate::ty::{
|
||||
adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
|
||||
ty_is_fn_once_param,
|
||||
};
|
||||
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
|
||||
use crate::visitors::for_each_expr;
|
||||
|
||||
use rustc_middle::hir::nested_filter;
|
||||
|
@ -1353,46 +1353,12 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
|
|||
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
match node {
|
||||
Node::Expr(e) => match e.kind {
|
||||
ExprKind::Closure { .. } => {
|
||||
ExprKind::Closure { .. }
|
||||
if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
|
||||
&& subs.as_closure().kind() == ClosureKind::FnOnce
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let is_once = walk_to_expr_usage(cx, e, |node, id| {
|
||||
let Node::Expr(e) = node else {
|
||||
return None;
|
||||
};
|
||||
match e.kind {
|
||||
ExprKind::Call(f, _) if f.hir_id == id => Some(()),
|
||||
ExprKind::Call(f, args) => {
|
||||
let i = args.iter().position(|arg| arg.hir_id == id)?;
|
||||
let sig = expr_sig(cx, f)?;
|
||||
let predicates = sig
|
||||
.predicates_id()
|
||||
.map_or(cx.param_env, |id| cx.tcx.param_env(id))
|
||||
.caller_bounds();
|
||||
sig.input(i).and_then(|ty| {
|
||||
ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(())
|
||||
})
|
||||
},
|
||||
ExprKind::MethodCall(_, receiver, args, _) => {
|
||||
let i = std::iter::once(receiver)
|
||||
.chain(args.iter())
|
||||
.position(|arg| arg.hir_id == id)?;
|
||||
let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
|
||||
let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_binder().inputs()[i];
|
||||
ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
if !is_once {
|
||||
return Some(e);
|
||||
}
|
||||
},
|
||||
ExprKind::Loop(..) => return Some(e),
|
||||
&& subs.as_closure().kind() == ClosureKind::FnOnce => {},
|
||||
|
||||
// Note: A closure's kind is determined by how it's used, not it's captures.
|
||||
ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
|
||||
_ => (),
|
||||
},
|
||||
Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
|
||||
|
@ -2590,26 +2556,30 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
|
|||
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
|
||||
}
|
||||
|
||||
/// Walks the HIR tree from the given expression, up to the node where the value produced by the
|
||||
/// expression is consumed. Calls the function for every node encountered this way until it returns
|
||||
/// `Some`.
|
||||
/// Walks up the HIR tree from the given expression in an attempt to find where the value is
|
||||
/// consumed.
|
||||
///
|
||||
/// This allows walking through `if`, `match`, `break`, block expressions to find where the value
|
||||
/// produced by the expression is consumed.
|
||||
/// Termination has three conditions:
|
||||
/// - The given function returns `Break`. This function will return the value.
|
||||
/// - The consuming node is found. This function will return `Continue(use_node, child_id)`.
|
||||
/// - No further parent nodes are found. This will trigger a debug assert or return `None`.
|
||||
///
|
||||
/// This allows walking through `if`, `match`, `break`, and block expressions to find where the
|
||||
/// value produced by the expression is consumed.
|
||||
pub fn walk_to_expr_usage<'tcx, T>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &Expr<'tcx>,
|
||||
mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>,
|
||||
) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> {
|
||||
let map = cx.tcx.hir();
|
||||
let mut iter = map.parent_iter(e.hir_id);
|
||||
let mut child_id = e.hir_id;
|
||||
|
||||
while let Some((parent_id, parent)) = iter.next() {
|
||||
if let Some(x) = f(parent, child_id) {
|
||||
return Some(x);
|
||||
if let ControlFlow::Break(x) = f(parent_id, parent, child_id) {
|
||||
return Some(ControlFlow::Break(x));
|
||||
}
|
||||
let parent = match parent {
|
||||
let parent_expr = match parent {
|
||||
Node::Expr(e) => e,
|
||||
Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
|
||||
child_id = parent_id;
|
||||
|
@ -2619,18 +2589,19 @@ pub fn walk_to_expr_usage<'tcx, T>(
|
|||
child_id = parent_id;
|
||||
continue;
|
||||
},
|
||||
_ => return None,
|
||||
_ => return Some(ControlFlow::Continue((parent, child_id))),
|
||||
};
|
||||
match parent.kind {
|
||||
match parent_expr.kind {
|
||||
ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
|
||||
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
|
||||
child_id = id;
|
||||
iter = map.parent_iter(id);
|
||||
},
|
||||
ExprKind::Block(..) => child_id = parent_id,
|
||||
_ => return None,
|
||||
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id,
|
||||
_ => return Some(ControlFlow::Continue((parent, child_id))),
|
||||
}
|
||||
}
|
||||
debug_assert!(false, "no parent node found for `{child_id:?}`");
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -2674,6 +2645,8 @@ pub enum ExprUseNode<'tcx> {
|
|||
Callee,
|
||||
/// Access of a field.
|
||||
FieldAccess(Ident),
|
||||
Expr,
|
||||
Other,
|
||||
}
|
||||
impl<'tcx> ExprUseNode<'tcx> {
|
||||
/// Checks if the value is returned from the function.
|
||||
|
@ -2750,144 +2723,104 @@ impl<'tcx> ExprUseNode<'tcx> {
|
|||
let sig = cx.tcx.fn_sig(id).skip_binder();
|
||||
Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
|
||||
},
|
||||
Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None,
|
||||
Self::Local(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the context an expression's value is used in.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> {
|
||||
let mut adjustments = [].as_slice();
|
||||
let mut is_ty_unified = false;
|
||||
let mut moved_before_use = false;
|
||||
let ctxt = e.span.ctxt();
|
||||
walk_to_expr_usage(cx, e, &mut |parent, child_id| {
|
||||
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
|
||||
walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| {
|
||||
if adjustments.is_empty()
|
||||
&& let Node::Expr(e) = cx.tcx.hir_node(child_id)
|
||||
{
|
||||
adjustments = cx.typeck_results().expr_adjustments(e);
|
||||
}
|
||||
match parent {
|
||||
Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Local(l),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
if !cx.tcx.hir().opt_span(parent_id).is_some_and(|x| x.ctxt() == ctxt) {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
if let Node::Expr(e) = parent {
|
||||
match e.kind {
|
||||
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
},
|
||||
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
},
|
||||
ExprKind::Block(..) => moved_before_use = true,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
})?
|
||||
.continue_value()
|
||||
.map(|(use_node, child_id)| {
|
||||
let node = match use_node {
|
||||
Node::Local(l) => ExprUseNode::Local(l),
|
||||
Node::ExprField(field) => ExprUseNode::Field(field),
|
||||
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Static(..) | ItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Const(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::ConstStatic(owner_id),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
}) => ExprUseNode::ConstStatic(owner_id),
|
||||
|
||||
Node::Item(&Item {
|
||||
kind: ItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::TraitItem(&TraitItem {
|
||||
kind: TraitItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
})
|
||||
| Node::ImplItem(&ImplItem {
|
||||
kind: ImplItemKind::Fn(..),
|
||||
owner_id,
|
||||
span,
|
||||
..
|
||||
}) if span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(owner_id),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
}) => ExprUseNode::Return(owner_id),
|
||||
|
||||
Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Field(field),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
|
||||
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
|
||||
ExprKind::Ret(_) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(OwnerId {
|
||||
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
||||
}),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
Node::Expr(use_expr) => match use_expr.kind {
|
||||
ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
|
||||
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
||||
}),
|
||||
ExprKind::Closure(closure) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Call(func, args) => Some(ExprUseCtxt {
|
||||
node: match args.iter().position(|arg| arg.hir_id == child_id) {
|
||||
Some(i) => ExprUseNode::FnArg(func, i),
|
||||
None => ExprUseNode::Callee,
|
||||
},
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::MethodArg(
|
||||
parent.hir_id,
|
||||
name.args,
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
|
||||
),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt {
|
||||
node: ExprUseNode::FieldAccess(name),
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}),
|
||||
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
None
|
||||
ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
|
||||
ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) {
|
||||
Some(i) => ExprUseNode::FnArg(func, i),
|
||||
None => ExprUseNode::Callee,
|
||||
},
|
||||
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
|
||||
is_ty_unified = true;
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
ExprKind::Block(..) => {
|
||||
moved_before_use = true;
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
|
||||
use_expr.hir_id,
|
||||
name.args,
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
|
||||
),
|
||||
ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name),
|
||||
_ => ExprUseNode::Expr,
|
||||
},
|
||||
_ => None,
|
||||
_ => ExprUseNode::Other,
|
||||
};
|
||||
ExprUseCtxt {
|
||||
node,
|
||||
adjustments,
|
||||
is_ty_unified,
|
||||
moved_before_use,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -3264,3 +3197,131 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a path from a local caller to the type of the called method. Suitable for user
|
||||
/// output/suggestions.
|
||||
///
|
||||
/// Returned path can be either absolute (for methods defined non-locally), or relative (for local
|
||||
/// methods).
|
||||
pub fn get_path_from_caller_to_method_type<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
from: LocalDefId,
|
||||
method: DefId,
|
||||
args: GenericArgsRef<'tcx>,
|
||||
) -> String {
|
||||
let assoc_item = tcx.associated_item(method);
|
||||
let def_id = assoc_item.container_id(tcx);
|
||||
match assoc_item.container {
|
||||
rustc_ty::TraitContainer => get_path_to_callee(tcx, from, def_id),
|
||||
rustc_ty::ImplContainer => {
|
||||
let ty = tcx.type_of(def_id).instantiate_identity();
|
||||
get_path_to_ty(tcx, from, ty, args)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
|
||||
match ty.kind() {
|
||||
rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
|
||||
// TODO these types need to be recursively resolved as well
|
||||
rustc_ty::Array(..)
|
||||
| rustc_ty::Dynamic(..)
|
||||
| rustc_ty::Never
|
||||
| rustc_ty::RawPtr(_)
|
||||
| rustc_ty::Ref(..)
|
||||
| rustc_ty::Slice(_)
|
||||
| rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)),
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce a path from some local caller to the callee. Suitable for user output/suggestions.
|
||||
fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
|
||||
// only search for a relative path if the call is fully local
|
||||
if callee.is_local() {
|
||||
let callee_path = tcx.def_path(callee);
|
||||
let caller_path = tcx.def_path(from.to_def_id());
|
||||
maybe_get_relative_path(&caller_path, &callee_path, 2)
|
||||
} else {
|
||||
tcx.def_path_str(callee)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to produce a relative path from `from` to `to`; if such a path would contain more than
|
||||
/// `max_super` `super` items, produces an absolute path instead. Both `from` and `to` should be in
|
||||
/// the local crate.
|
||||
///
|
||||
/// Suitable for user output/suggestions.
|
||||
///
|
||||
/// This ignores use items, and assumes that the target path is visible from the source
|
||||
/// path (which _should_ be a reasonable assumption since we in order to be able to use an object of
|
||||
/// certain type T, T is required to be visible).
|
||||
///
|
||||
/// TODO make use of `use` items. Maybe we should have something more sophisticated like
|
||||
/// rust-analyzer does? <https://docs.rs/ra_ap_hir_def/0.0.169/src/ra_ap_hir_def/find_path.rs.html#19-27>
|
||||
fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
|
||||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
|
||||
// 1. skip the segments common for both paths (regardless of their type)
|
||||
let unique_parts = to
|
||||
.data
|
||||
.iter()
|
||||
.zip_longest(from.data.iter())
|
||||
.skip_while(|el| matches!(el, Both(l, r) if l == r))
|
||||
.map(|el| match el {
|
||||
Both(l, r) => Both(l.data, r.data),
|
||||
Left(l) => Left(l.data),
|
||||
Right(r) => Right(r.data),
|
||||
});
|
||||
|
||||
// 2. for the remaning segments, construct relative path using only mod names and `super`
|
||||
let mut go_up_by = 0;
|
||||
let mut path = Vec::new();
|
||||
for el in unique_parts {
|
||||
match el {
|
||||
Both(l, r) => {
|
||||
// consider:
|
||||
// a::b::sym:: :: refers to
|
||||
// c::d::e ::f::sym
|
||||
// result should be super::super::c::d::e::f
|
||||
//
|
||||
// alternatively:
|
||||
// a::b::c ::d::sym refers to
|
||||
// e::f::sym:: ::
|
||||
// result should be super::super::super::super::e::f
|
||||
if let DefPathData::TypeNs(s) = l {
|
||||
path.push(s.to_string());
|
||||
}
|
||||
if let DefPathData::TypeNs(_) = r {
|
||||
go_up_by += 1;
|
||||
}
|
||||
},
|
||||
// consider:
|
||||
// a::b::sym:: :: refers to
|
||||
// c::d::e ::f::sym
|
||||
// when looking at `f`
|
||||
Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()),
|
||||
// consider:
|
||||
// a::b::c ::d::sym refers to
|
||||
// e::f::sym:: ::
|
||||
// when looking at `d`
|
||||
Right(DefPathData::TypeNs(_)) => go_up_by += 1,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if go_up_by > max_super {
|
||||
// `super` chain would be too long, just use the absolute path instead
|
||||
once(String::from("crate"))
|
||||
.chain(to.data.iter().filter_map(|el| {
|
||||
if let DefPathData::TypeNs(sym) = el.data {
|
||||
Some(sym.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
.join("::")
|
||||
} else {
|
||||
repeat(String::from("super")).take(go_up_by).chain(path).join("::")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
|
|||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::source_map::{original_sp, SourceMap};
|
||||
use rustc_span::{hygiene, BytePos, SourceFileAndLine, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use rustc_span::{hygiene, BytePos, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Range;
|
||||
|
||||
|
|
|
@ -1000,35 +1000,6 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`.
|
||||
pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [ty::Clause<'_>]) -> bool {
|
||||
let ty::Param(ty) = *ty.kind() else {
|
||||
return false;
|
||||
};
|
||||
let lang = tcx.lang_items();
|
||||
let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id)) = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
predicates
|
||||
.iter()
|
||||
.try_fold(false, |found, p| {
|
||||
if let ty::ClauseKind::Trait(p) = p.kind().skip_binder()
|
||||
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
|
||||
&& ty.index == self_ty.index
|
||||
{
|
||||
// This should use `super_traits_of`, but that's a private function.
|
||||
if p.trait_ref.def_id == fn_once_id {
|
||||
return Some(true);
|
||||
} else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(found)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Comes up with an "at least" guesstimate for the type's size, not taking into
|
||||
/// account the layout of type parameters.
|
||||
pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
|
||||
|
|
|
@ -22,9 +22,11 @@ use rustc_session::EarlyDiagCtxt;
|
|||
use rustc_span::symbol::Symbol;
|
||||
|
||||
use std::env;
|
||||
use std::fs::read_to_string;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::string::ToString;
|
||||
|
||||
use anstream::println;
|
||||
|
||||
|
@ -188,12 +190,31 @@ pub fn main() {
|
|||
|
||||
exit(rustc_driver::catch_with_exit_code(move || {
|
||||
let mut orig_args: Vec<String> = env::args().collect();
|
||||
let has_sysroot_arg = arg_value(&orig_args, "--sysroot", |_| true).is_some();
|
||||
|
||||
let has_sysroot_arg = |args: &mut [String]| -> bool {
|
||||
if arg_value(args, "--sysroot", |_| true).is_some() {
|
||||
return true;
|
||||
}
|
||||
// https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path
|
||||
// Beside checking for existence of `--sysroot` on the command line, we need to
|
||||
// check for the arg files that are prefixed with @ as well to be consistent with rustc
|
||||
for arg in args.iter() {
|
||||
if let Some(arg_file_path) = arg.strip_prefix('@') {
|
||||
if let Ok(arg_file) = read_to_string(arg_file_path) {
|
||||
let split_arg_file: Vec<String> = arg_file.lines().map(ToString::to_string).collect();
|
||||
if arg_value(&split_arg_file, "--sysroot", |_| true).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
let sys_root_env = std::env::var("SYSROOT").ok();
|
||||
let pass_sysroot_env_if_given = |args: &mut Vec<String>, sys_root_env| {
|
||||
if let Some(sys_root) = sys_root_env {
|
||||
if !has_sysroot_arg {
|
||||
if !has_sysroot_arg(args) {
|
||||
args.extend(vec!["--sysroot".into(), sys_root]);
|
||||
}
|
||||
};
|
||||
|
|
45
tests/ui-cargo/lint_groups_priority/fail/Cargo.stderr
Normal file
45
tests/ui-cargo/lint_groups_priority/fail/Cargo.stderr
Normal file
|
@ -0,0 +1,45 @@
|
|||
error: lint group `rust_2018_idioms` has the same priority (0) as a lint
|
||||
--> Cargo.toml:7:1
|
||||
|
|
||||
7 | rust_2018_idioms = "warn"
|
||||
| ^^^^^^^^^^^^^^^^ ------ has an implicit priority of 0
|
||||
8 | bare_trait_objects = "allow"
|
||||
| ------------------ has the same priority as this lint
|
||||
|
|
||||
= note: the order of the lints in the table is ignored by Cargo
|
||||
= 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 = { level = "warn", priority = -1 }
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: lint group `unused` has the same priority (0) as a lint
|
||||
--> Cargo.toml:10:1
|
||||
|
|
||||
10 | unused = { level = "deny" }
|
||||
| ^^^^^^ ------------------ has an implicit priority of 0
|
||||
11 | unused_braces = { level = "allow", priority = 1 }
|
||||
12 | unused_attributes = { level = "allow" }
|
||||
| ----------------- has the same priority as this lint
|
||||
|
|
||||
= note: the order of the lints in the table is ignored by Cargo
|
||||
help: to have lints override the group set `unused` to a lower priority
|
||||
|
|
||||
10 | unused = { level = "deny", priority = -1 }
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: lint group `pedantic` has the same priority (-1) as a lint
|
||||
--> Cargo.toml:19:1
|
||||
|
|
||||
19 | pedantic = { level = "warn", priority = -1 }
|
||||
| ^^^^^^^^
|
||||
20 | similar_names = { level = "allow", priority = -1 }
|
||||
| ------------- has the same priority as this lint
|
||||
|
|
||||
= note: the order of the lints in the table is ignored by Cargo
|
||||
help: to have lints override the group set `pedantic` to a lower priority
|
||||
|
|
||||
19 | pedantic = { level = "warn", priority = -2 }
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: could not compile `fail` (lib) due to 3 previous errors
|
20
tests/ui-cargo/lint_groups_priority/fail/Cargo.toml
Normal file
20
tests/ui-cargo/lint_groups_priority/fail/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "fail"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
|
||||
[lints.rust]
|
||||
rust_2018_idioms = "warn"
|
||||
bare_trait_objects = "allow"
|
||||
|
||||
unused = { level = "deny" }
|
||||
unused_braces = { level = "allow", priority = 1 }
|
||||
unused_attributes = { level = "allow" }
|
||||
|
||||
# `warnings` is not a group so the order it is passed does not matter
|
||||
warnings = "deny"
|
||||
deprecated = "allow"
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
similar_names = { level = "allow", priority = -1 }
|
1
tests/ui-cargo/lint_groups_priority/fail/src/lib.rs
Normal file
1
tests/ui-cargo/lint_groups_priority/fail/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
10
tests/ui-cargo/lint_groups_priority/pass/Cargo.toml
Normal file
10
tests/ui-cargo/lint_groups_priority/pass/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "pass"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
style = { level = "warn", priority = 1 }
|
||||
similar_names = "allow"
|
||||
dbg_macro = { level = "warn", priority = 2 }
|
1
tests/ui-cargo/lint_groups_priority/pass/src/lib.rs
Normal file
1
tests/ui-cargo/lint_groups_priority/pass/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -19,8 +19,8 @@ LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"]
|
|||
error: hardcoded path to a diagnostic item
|
||||
--> $DIR/unnecessary_def_path_hardcoded_path.rs:12:43
|
||||
|
|
||||
LL | const OPS_MOD: [&str; 5] = ["core", "ops"];
|
||||
| ^^^^^^^^^^^^^^^
|
||||
LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: convert all references to use `sym::deref_method`
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)]
|
||||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
|
||||
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
|
||||
|
||||
use std::mem::{size_of, size_of_val};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)]
|
||||
#![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
|
||||
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
|
||||
|
||||
use std::mem::{size_of, size_of_val};
|
||||
|
|
1
tests/ui-toml/modulo_arithmetic/clippy.toml
Normal file
1
tests/ui-toml/modulo_arithmetic/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
allow-comparison-to-zero = false
|
10
tests/ui-toml/modulo_arithmetic/modulo_arithmetic.rs
Normal file
10
tests/ui-toml/modulo_arithmetic/modulo_arithmetic.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![warn(clippy::modulo_arithmetic)]
|
||||
|
||||
fn main() {
|
||||
let a = -1;
|
||||
let b = 2;
|
||||
let c = a % b == 0;
|
||||
let c = a % b != 0;
|
||||
let c = 0 == a % b;
|
||||
let c = 0 != a % b;
|
||||
}
|
40
tests/ui-toml/modulo_arithmetic/modulo_arithmetic.stderr
Normal file
40
tests/ui-toml/modulo_arithmetic/modulo_arithmetic.stderr
Normal file
|
@ -0,0 +1,40 @@
|
|||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:6:13
|
||||
|
|
||||
LL | let c = a % b == 0;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
= note: `-D clippy::modulo-arithmetic` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::modulo_arithmetic)]`
|
||||
|
||||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:7:13
|
||||
|
|
||||
LL | let c = a % b != 0;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
|
||||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:8:18
|
||||
|
|
||||
LL | let c = 0 == a % b;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
|
||||
error: you are using modulo operator on types that might have different signs
|
||||
--> $DIR/modulo_arithmetic.rs:9:18
|
||||
|
|
||||
LL | let c = 0 != a % b;
|
||||
| ^^^^^
|
||||
|
|
||||
= note: double check for expected result especially when interoperating with different languages
|
||||
= note: or consider using `rem_euclid` or similar function
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
|
@ -3,6 +3,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
|||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
|
@ -14,6 +15,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
|||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
allowed-wildcard-imports
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
|
@ -80,6 +82,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
|||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
|
@ -91,6 +94,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
|||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
allowed-wildcard-imports
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
|
@ -157,6 +161,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
|
|||
absolute-paths-max-segments
|
||||
accept-comment-above-attributes
|
||||
accept-comment-above-statement
|
||||
allow-comparison-to-zero
|
||||
allow-dbg-in-tests
|
||||
allow-expect-in-tests
|
||||
allow-mixed-uninlined-format-args
|
||||
|
@ -168,6 +173,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
|
|||
allowed-duplicate-crates
|
||||
allowed-idents-below-min-chars
|
||||
allowed-scripts
|
||||
allowed-wildcard-imports
|
||||
arithmetic-side-effects-allowed
|
||||
arithmetic-side-effects-allowed-binary
|
||||
arithmetic-side-effects-allowed-unary
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
warn-on-all-wildcard-imports = true
|
||||
|
||||
# This should be ignored since `warn-on-all-wildcard-imports` has higher precedence
|
||||
allowed-wildcard-imports = ["utils"]
|
||||
|
|
|
@ -3,9 +3,28 @@
|
|||
mod prelude {
|
||||
pub const FOO: u8 = 1;
|
||||
}
|
||||
|
||||
mod utils {
|
||||
pub const BAR: u8 = 1;
|
||||
pub fn print() {}
|
||||
}
|
||||
|
||||
mod my_crate {
|
||||
pub mod utils {
|
||||
pub fn my_util_fn() {}
|
||||
}
|
||||
}
|
||||
|
||||
use utils::{BAR, print};
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use my_crate::utils::my_util_fn;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use prelude::FOO;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
|
||||
fn main() {
|
||||
let _ = FOO;
|
||||
let _ = BAR;
|
||||
print();
|
||||
my_util_fn();
|
||||
}
|
||||
|
|
|
@ -3,9 +3,28 @@
|
|||
mod prelude {
|
||||
pub const FOO: u8 = 1;
|
||||
}
|
||||
|
||||
mod utils {
|
||||
pub const BAR: u8 = 1;
|
||||
pub fn print() {}
|
||||
}
|
||||
|
||||
mod my_crate {
|
||||
pub mod utils {
|
||||
pub fn my_util_fn() {}
|
||||
}
|
||||
}
|
||||
|
||||
use utils::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use my_crate::utils::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
use prelude::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
|
||||
fn main() {
|
||||
let _ = FOO;
|
||||
let _ = BAR;
|
||||
print();
|
||||
my_util_fn();
|
||||
}
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
error: usage of wildcard import
|
||||
--> $DIR/wildcard_imports.rs:6:5
|
||||
--> $DIR/wildcard_imports.rs:18:5
|
||||
|
|
||||
LL | use prelude::*;
|
||||
| ^^^^^^^^^^ help: try: `prelude::FOO`
|
||||
LL | use utils::*;
|
||||
| ^^^^^^^^ help: try: `utils::{BAR, print}`
|
||||
|
|
||||
= note: `-D clippy::wildcard-imports` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::wildcard_imports)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
error: usage of wildcard import
|
||||
--> $DIR/wildcard_imports.rs:20:5
|
||||
|
|
||||
LL | use my_crate::utils::*;
|
||||
| ^^^^^^^^^^^^^^^^^^ help: try: `my_crate::utils::my_util_fn`
|
||||
|
||||
error: usage of wildcard import
|
||||
--> $DIR/wildcard_imports.rs:22:5
|
||||
|
|
||||
LL | use prelude::*;
|
||||
| ^^^^^^^^^^ help: try: `prelude::FOO`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
|
|
1
tests/ui-toml/wildcard_imports_whitelist/clippy.toml
Normal file
1
tests/ui-toml/wildcard_imports_whitelist/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
|||
allowed-wildcard-imports = ["utils"]
|
|
@ -0,0 +1,26 @@
|
|||
#![warn(clippy::wildcard_imports)]
|
||||
|
||||
mod utils {
|
||||
pub fn print() {}
|
||||
}
|
||||
|
||||
mod utils_plus {
|
||||
pub fn do_something() {}
|
||||
}
|
||||
|
||||
mod my_crate {
|
||||
pub mod utils {
|
||||
pub fn my_util_fn() {}
|
||||
}
|
||||
}
|
||||
|
||||
use my_crate::utils::*;
|
||||
use utils::*;
|
||||
use utils_plus::do_something;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
|
||||
fn main() {
|
||||
print();
|
||||
my_util_fn();
|
||||
do_something();
|
||||
}
|
26
tests/ui-toml/wildcard_imports_whitelist/wildcard_imports.rs
Normal file
26
tests/ui-toml/wildcard_imports_whitelist/wildcard_imports.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
#![warn(clippy::wildcard_imports)]
|
||||
|
||||
mod utils {
|
||||
pub fn print() {}
|
||||
}
|
||||
|
||||
mod utils_plus {
|
||||
pub fn do_something() {}
|
||||
}
|
||||
|
||||
mod my_crate {
|
||||
pub mod utils {
|
||||
pub fn my_util_fn() {}
|
||||
}
|
||||
}
|
||||
|
||||
use my_crate::utils::*;
|
||||
use utils::*;
|
||||
use utils_plus::*;
|
||||
//~^ ERROR: usage of wildcard import
|
||||
|
||||
fn main() {
|
||||
print();
|
||||
my_util_fn();
|
||||
do_something();
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
error: usage of wildcard import
|
||||
--> $DIR/wildcard_imports.rs:19:5
|
||||
|
|
||||
LL | use utils_plus::*;
|
||||
| ^^^^^^^^^^^^^ help: try: `utils_plus::do_something`
|
||||
|
|
||||
= note: `-D clippy::wildcard-imports` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::wildcard_imports)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
|
@ -5,6 +5,7 @@ fn a() -> i32 {
|
|||
0
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.75"]
|
||||
fn main() {
|
||||
let val = 1;
|
||||
let _p = std::ptr::addr_of!(val);
|
||||
|
|
|
@ -5,6 +5,7 @@ fn a() -> i32 {
|
|||
0
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.75"]
|
||||
fn main() {
|
||||
let val = 1;
|
||||
let _p = &val as *const i32;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: borrow as raw pointer
|
||||
--> $DIR/borrow_as_ptr.rs:10:14
|
||||
--> $DIR/borrow_as_ptr.rs:11:14
|
||||
|
|
||||
LL | let _p = &val as *const i32;
|
||||
| ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of!(val)`
|
||||
|
@ -8,7 +8,7 @@ LL | let _p = &val as *const i32;
|
|||
= help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]`
|
||||
|
||||
error: borrow as raw pointer
|
||||
--> $DIR/borrow_as_ptr.rs:17:18
|
||||
--> $DIR/borrow_as_ptr.rs:18:18
|
||||
|
|
||||
LL | let _p_mut = &mut val_mut as *mut i32;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(val_mut)`
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#![feature(lang_items, start, libc)]
|
||||
#![no_std]
|
||||
|
||||
#[clippy::msrv = "1.75"]
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let val = 1;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#![feature(lang_items, start, libc)]
|
||||
#![no_std]
|
||||
|
||||
#[clippy::msrv = "1.75"]
|
||||
#[start]
|
||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||
let val = 1;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: borrow as raw pointer
|
||||
--> $DIR/borrow_as_ptr_no_std.rs:8:14
|
||||
--> $DIR/borrow_as_ptr_no_std.rs:9:14
|
||||
|
|
||||
LL | let _p = &val as *const i32;
|
||||
| ^^^^^^^^^^^^^^^^^^ help: try: `core::ptr::addr_of!(val)`
|
||||
|
@ -8,7 +8,7 @@ LL | let _p = &val as *const i32;
|
|||
= help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]`
|
||||
|
||||
error: borrow as raw pointer
|
||||
--> $DIR/borrow_as_ptr_no_std.rs:11:18
|
||||
--> $DIR/borrow_as_ptr_no_std.rs:12:18
|
||||
|
|
||||
LL | let _p_mut = &mut val_mut as *mut i32;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `core::ptr::addr_of_mut!(val_mut)`
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#![allow(clippy::non_canonical_clone_impl, clippy::non_canonical_partial_ord_impl, dead_code)]
|
||||
#![warn(clippy::expl_impl_clone_on_copy)]
|
||||
|
||||
|
||||
#[derive(Copy)]
|
||||
struct Qux;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:8:1
|
||||
--> $DIR/derive.rs:7:1
|
||||
|
|
||||
LL | / impl Clone for Qux {
|
||||
LL | |
|
||||
|
@ -10,7 +10,7 @@ LL | | }
|
|||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:8:1
|
||||
--> $DIR/derive.rs:7:1
|
||||
|
|
||||
LL | / impl Clone for Qux {
|
||||
LL | |
|
||||
|
@ -23,7 +23,7 @@ LL | | }
|
|||
= help: to override `-D warnings` add `#[allow(clippy::expl_impl_clone_on_copy)]`
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:33:1
|
||||
--> $DIR/derive.rs:32:1
|
||||
|
|
||||
LL | / impl<'a> Clone for Lt<'a> {
|
||||
LL | |
|
||||
|
@ -34,7 +34,7 @@ LL | | }
|
|||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:33:1
|
||||
--> $DIR/derive.rs:32:1
|
||||
|
|
||||
LL | / impl<'a> Clone for Lt<'a> {
|
||||
LL | |
|
||||
|
@ -45,7 +45,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:45:1
|
||||
--> $DIR/derive.rs:44:1
|
||||
|
|
||||
LL | / impl Clone for BigArray {
|
||||
LL | |
|
||||
|
@ -56,7 +56,7 @@ LL | | }
|
|||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:45:1
|
||||
--> $DIR/derive.rs:44:1
|
||||
|
|
||||
LL | / impl Clone for BigArray {
|
||||
LL | |
|
||||
|
@ -67,7 +67,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:57:1
|
||||
--> $DIR/derive.rs:56:1
|
||||
|
|
||||
LL | / impl Clone for FnPtr {
|
||||
LL | |
|
||||
|
@ -78,7 +78,7 @@ LL | | }
|
|||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:57:1
|
||||
--> $DIR/derive.rs:56:1
|
||||
|
|
||||
LL | / impl Clone for FnPtr {
|
||||
LL | |
|
||||
|
@ -89,7 +89,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:78:1
|
||||
--> $DIR/derive.rs:77:1
|
||||
|
|
||||
LL | / impl<T: Clone> Clone for Generic2<T> {
|
||||
LL | |
|
||||
|
@ -100,7 +100,7 @@ LL | | }
|
|||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:78:1
|
||||
--> $DIR/derive.rs:77:1
|
||||
|
|
||||
LL | / impl<T: Clone> Clone for Generic2<T> {
|
||||
LL | |
|
||||
|
|
|
@ -346,6 +346,23 @@ fn angle_brackets_and_args() {
|
|||
dyn_opt.map(<dyn TestTrait>::method_on_dyn);
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/12199
|
||||
fn track_caller_fp() {
|
||||
struct S;
|
||||
impl S {
|
||||
#[track_caller]
|
||||
fn add_location(self) {}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn add_location() {}
|
||||
|
||||
fn foo(_: fn()) {}
|
||||
fn foo2(_: fn(S)) {}
|
||||
foo(|| add_location());
|
||||
foo2(|s| s.add_location());
|
||||
}
|
||||
|
||||
fn _late_bound_to_early_bound_regions() {
|
||||
struct Foo<'a>(&'a u32);
|
||||
impl<'a> Foo<'a> {
|
||||
|
@ -400,3 +417,57 @@ fn _closure_with_types() {
|
|||
let _ = f2(|x: u32| f(x));
|
||||
let _ = f2(|x| -> u32 { f(x) });
|
||||
}
|
||||
|
||||
/// https://github.com/rust-lang/rust-clippy/issues/10854
|
||||
/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative.
|
||||
mod issue_10854 {
|
||||
pub mod test_mod {
|
||||
pub struct Test;
|
||||
|
||||
impl Test {
|
||||
pub fn method(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calls_test(test: Option<Test>) -> Option<i32> {
|
||||
test.map(Test::method)
|
||||
}
|
||||
|
||||
pub fn calls_outer(test: Option<super::Outer>) -> Option<i32> {
|
||||
test.map(super::Outer::method)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Outer;
|
||||
|
||||
impl Outer {
|
||||
pub fn method(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calls_into_mod(test: Option<test_mod::Test>) -> Option<i32> {
|
||||
test.map(test_mod::Test::method)
|
||||
}
|
||||
|
||||
mod a {
|
||||
pub mod b {
|
||||
pub mod c {
|
||||
pub fn extreme_nesting(test: Option<super::super::super::d::Test>) -> Option<i32> {
|
||||
test.map(crate::issue_10854::d::Test::method)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod d {
|
||||
pub struct Test;
|
||||
|
||||
impl Test {
|
||||
pub fn method(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -346,6 +346,23 @@ fn angle_brackets_and_args() {
|
|||
dyn_opt.map(|d| d.method_on_dyn());
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/12199
|
||||
fn track_caller_fp() {
|
||||
struct S;
|
||||
impl S {
|
||||
#[track_caller]
|
||||
fn add_location(self) {}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn add_location() {}
|
||||
|
||||
fn foo(_: fn()) {}
|
||||
fn foo2(_: fn(S)) {}
|
||||
foo(|| add_location());
|
||||
foo2(|s| s.add_location());
|
||||
}
|
||||
|
||||
fn _late_bound_to_early_bound_regions() {
|
||||
struct Foo<'a>(&'a u32);
|
||||
impl<'a> Foo<'a> {
|
||||
|
@ -400,3 +417,57 @@ fn _closure_with_types() {
|
|||
let _ = f2(|x: u32| f(x));
|
||||
let _ = f2(|x| -> u32 { f(x) });
|
||||
}
|
||||
|
||||
/// https://github.com/rust-lang/rust-clippy/issues/10854
|
||||
/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative.
|
||||
mod issue_10854 {
|
||||
pub mod test_mod {
|
||||
pub struct Test;
|
||||
|
||||
impl Test {
|
||||
pub fn method(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calls_test(test: Option<Test>) -> Option<i32> {
|
||||
test.map(|t| t.method())
|
||||
}
|
||||
|
||||
pub fn calls_outer(test: Option<super::Outer>) -> Option<i32> {
|
||||
test.map(|t| t.method())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Outer;
|
||||
|
||||
impl Outer {
|
||||
pub fn method(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calls_into_mod(test: Option<test_mod::Test>) -> Option<i32> {
|
||||
test.map(|t| t.method())
|
||||
}
|
||||
|
||||
mod a {
|
||||
pub mod b {
|
||||
pub mod c {
|
||||
pub fn extreme_nesting(test: Option<super::super::super::d::Test>) -> Option<i32> {
|
||||
test.map(|t| t.method())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod d {
|
||||
pub struct Test;
|
||||
|
||||
impl Test {
|
||||
pub fn method(self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,10 +161,34 @@ LL | dyn_opt.map(|d| d.method_on_dyn());
|
|||
| ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<dyn TestTrait>::method_on_dyn`
|
||||
|
||||
error: redundant closure
|
||||
--> $DIR/eta.rs:389:19
|
||||
--> $DIR/eta.rs:406:19
|
||||
|
|
||||
LL | let _ = f(&0, |x, y| f2(x, y));
|
||||
| ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2`
|
||||
|
||||
error: aborting due to 27 previous errors
|
||||
error: redundant closure
|
||||
--> $DIR/eta.rs:434:22
|
||||
|
|
||||
LL | test.map(|t| t.method())
|
||||
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method`
|
||||
|
||||
error: redundant closure
|
||||
--> $DIR/eta.rs:438:22
|
||||
|
|
||||
LL | test.map(|t| t.method())
|
||||
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method`
|
||||
|
||||
error: redundant closure
|
||||
--> $DIR/eta.rs:451:18
|
||||
|
|
||||
LL | test.map(|t| t.method())
|
||||
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method`
|
||||
|
||||
error: redundant closure
|
||||
--> $DIR/eta.rs:458:30
|
||||
|
|
||||
LL | test.map(|t| t.method())
|
||||
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method`
|
||||
|
||||
error: aborting due to 31 previous errors
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ use std::panic::Location;
|
|||
|
||||
struct Somewhere;
|
||||
|
||||
#[allow(clippy::to_string_trait_impl)]
|
||||
impl ToString for Somewhere {
|
||||
fn to_string(&self) -> String {
|
||||
String::from("somewhere")
|
||||
|
|
|
@ -14,6 +14,7 @@ use std::panic::Location;
|
|||
|
||||
struct Somewhere;
|
||||
|
||||
#[allow(clippy::to_string_trait_impl)]
|
||||
impl ToString for Somewhere {
|
||||
fn to_string(&self) -> String {
|
||||
String::from("somewhere")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: `to_string` applied to a type that implements `Display` in `format!` args
|
||||
--> $DIR/format_args.rs:76:72
|
||||
--> $DIR/format_args.rs:77:72
|
||||
|
|
||||
LL | let _ = format!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
@ -8,145 +8,145 @@ LL | let _ = format!("error: something failed at {}", Location::caller().to_
|
|||
= help: to override `-D warnings` add `#[allow(clippy::to_string_in_format_args)]`
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `write!` args
|
||||
--> $DIR/format_args.rs:80:27
|
||||
--> $DIR/format_args.rs:81:27
|
||||
|
|
||||
LL | Location::caller().to_string()
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `writeln!` args
|
||||
--> $DIR/format_args.rs:85:27
|
||||
--> $DIR/format_args.rs:86:27
|
||||
|
|
||||
LL | Location::caller().to_string()
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `print!` args
|
||||
--> $DIR/format_args.rs:87:63
|
||||
--> $DIR/format_args.rs:88:63
|
||||
|
|
||||
LL | print!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:88:65
|
||||
--> $DIR/format_args.rs:89:65
|
||||
|
|
||||
LL | println!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `eprint!` args
|
||||
--> $DIR/format_args.rs:89:64
|
||||
--> $DIR/format_args.rs:90:64
|
||||
|
|
||||
LL | eprint!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `eprintln!` args
|
||||
--> $DIR/format_args.rs:90:66
|
||||
--> $DIR/format_args.rs:91:66
|
||||
|
|
||||
LL | eprintln!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `format_args!` args
|
||||
--> $DIR/format_args.rs:91:77
|
||||
--> $DIR/format_args.rs:92:77
|
||||
|
|
||||
LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `assert!` args
|
||||
--> $DIR/format_args.rs:92:70
|
||||
--> $DIR/format_args.rs:93:70
|
||||
|
|
||||
LL | assert!(true, "error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `assert_eq!` args
|
||||
--> $DIR/format_args.rs:93:73
|
||||
--> $DIR/format_args.rs:94:73
|
||||
|
|
||||
LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `assert_ne!` args
|
||||
--> $DIR/format_args.rs:94:73
|
||||
--> $DIR/format_args.rs:95:73
|
||||
|
|
||||
LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `panic!` args
|
||||
--> $DIR/format_args.rs:95:63
|
||||
--> $DIR/format_args.rs:96:63
|
||||
|
|
||||
LL | panic!("error: something failed at {}", Location::caller().to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:96:20
|
||||
--> $DIR/format_args.rs:97:20
|
||||
|
|
||||
LL | println!("{}", X(1).to_string());
|
||||
| ^^^^^^^^^^^^^^^^ help: use this: `*X(1)`
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:97:20
|
||||
--> $DIR/format_args.rs:98:20
|
||||
|
|
||||
LL | println!("{}", Y(&X(1)).to_string());
|
||||
| ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))`
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:98:24
|
||||
--> $DIR/format_args.rs:99:24
|
||||
|
|
||||
LL | println!("{}", Z(1).to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:99:20
|
||||
--> $DIR/format_args.rs:100:20
|
||||
|
|
||||
LL | println!("{}", x.to_string());
|
||||
| ^^^^^^^^^^^^^ help: use this: `**x`
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:100:20
|
||||
--> $DIR/format_args.rs:101:20
|
||||
|
|
||||
LL | println!("{}", x_ref.to_string());
|
||||
| ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:102:39
|
||||
--> $DIR/format_args.rs:103:39
|
||||
|
|
||||
LL | println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:103:52
|
||||
--> $DIR/format_args.rs:104:52
|
||||
|
|
||||
LL | println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:104:39
|
||||
--> $DIR/format_args.rs:105:39
|
||||
|
|
||||
LL | println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:105:52
|
||||
--> $DIR/format_args.rs:106:52
|
||||
|
|
||||
LL | println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `print!` args
|
||||
--> $DIR/format_args.rs:117:37
|
||||
--> $DIR/format_args.rs:118:37
|
||||
|
|
||||
LL | print!("{}", (Location::caller().to_string()));
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `print!` args
|
||||
--> $DIR/format_args.rs:118:39
|
||||
--> $DIR/format_args.rs:119:39
|
||||
|
|
||||
LL | print!("{}", ((Location::caller()).to_string()));
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `format!` args
|
||||
--> $DIR/format_args.rs:146:38
|
||||
--> $DIR/format_args.rs:147:38
|
||||
|
|
||||
LL | let x = format!("{} {}", a, b.to_string());
|
||||
| ^^^^^^^^^^^^ help: remove this
|
||||
|
||||
error: `to_string` applied to a type that implements `Display` in `println!` args
|
||||
--> $DIR/format_args.rs:160:24
|
||||
--> $DIR/format_args.rs:161:24
|
||||
|
|
||||
LL | println!("{}", original[..10].to_string());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use this: `&original[..10]`
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
//@aux-build:proc_macro_derive.rs
|
||||
#![warn(clippy::ignored_unit_patterns)]
|
||||
#![allow(clippy::let_unit_value, clippy::redundant_pattern_matching, clippy::single_match)]
|
||||
#![allow(
|
||||
clippy::let_unit_value,
|
||||
clippy::redundant_pattern_matching,
|
||||
clippy::single_match,
|
||||
clippy::needless_borrow
|
||||
)]
|
||||
|
||||
fn foo() -> Result<(), ()> {
|
||||
unimplemented!()
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
//@aux-build:proc_macro_derive.rs
|
||||
#![warn(clippy::ignored_unit_patterns)]
|
||||
#![allow(clippy::let_unit_value, clippy::redundant_pattern_matching, clippy::single_match)]
|
||||
#![allow(
|
||||
clippy::let_unit_value,
|
||||
clippy::redundant_pattern_matching,
|
||||
clippy::single_match,
|
||||
clippy::needless_borrow
|
||||
)]
|
||||
|
||||
fn foo() -> Result<(), ()> {
|
||||
unimplemented!()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:11:12
|
||||
--> $DIR/ignored_unit_patterns.rs:16:12
|
||||
|
|
||||
LL | Ok(_) => {},
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
@ -8,49 +8,49 @@ LL | Ok(_) => {},
|
|||
= help: to override `-D warnings` add `#[allow(clippy::ignored_unit_patterns)]`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:12:13
|
||||
--> $DIR/ignored_unit_patterns.rs:17:13
|
||||
|
|
||||
LL | Err(_) => {},
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:14:15
|
||||
--> $DIR/ignored_unit_patterns.rs:19:15
|
||||
|
|
||||
LL | if let Ok(_) = foo() {}
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:16:28
|
||||
--> $DIR/ignored_unit_patterns.rs:21:28
|
||||
|
|
||||
LL | let _ = foo().map_err(|_| todo!());
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:22:16
|
||||
--> $DIR/ignored_unit_patterns.rs:27:16
|
||||
|
|
||||
LL | Ok(_) => {},
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:24:17
|
||||
--> $DIR/ignored_unit_patterns.rs:29:17
|
||||
|
|
||||
LL | Err(_) => {},
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:36:9
|
||||
--> $DIR/ignored_unit_patterns.rs:41:9
|
||||
|
|
||||
LL | let _ = foo().unwrap();
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:45:13
|
||||
--> $DIR/ignored_unit_patterns.rs:50:13
|
||||
|
|
||||
LL | (1, _) => unimplemented!(),
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
||||
error: matching over `()` is more explicit
|
||||
--> $DIR/ignored_unit_patterns.rs:52:13
|
||||
--> $DIR/ignored_unit_patterns.rs:57:13
|
||||
|
|
||||
LL | for (x, _) in v {
|
||||
| ^ help: use `()` instead of `_`: `()`
|
||||
|
|
23
tests/ui/incompatible_msrv.rs
Normal file
23
tests/ui/incompatible_msrv.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
#![warn(clippy::incompatible_msrv)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![clippy::msrv = "1.3.0"]
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
fn foo() {
|
||||
let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
assert_eq!(map.entry("poneyland").key(), &"poneyland");
|
||||
//~^ ERROR: is `1.3.0` but this item is stable since `1.10.0`
|
||||
if let Entry::Vacant(v) = map.entry("poneyland") {
|
||||
v.into_key();
|
||||
//~^ ERROR: is `1.3.0` but this item is stable since `1.12.0`
|
||||
}
|
||||
// Should warn for `sleep` but not for `Duration` (which was added in `1.3.0`).
|
||||
sleep(Duration::new(1, 0));
|
||||
//~^ ERROR: is `1.3.0` but this item is stable since `1.4.0`
|
||||
}
|
||||
|
||||
fn main() {}
|
23
tests/ui/incompatible_msrv.stderr
Normal file
23
tests/ui/incompatible_msrv.stderr
Normal file
|
@ -0,0 +1,23 @@
|
|||
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0`
|
||||
--> $DIR/incompatible_msrv.rs:12:39
|
||||
|
|
||||
LL | assert_eq!(map.entry("poneyland").key(), &"poneyland");
|
||||
| ^^^^^
|
||||
|
|
||||
= note: `-D clippy::incompatible-msrv` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]`
|
||||
|
||||
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.12.0`
|
||||
--> $DIR/incompatible_msrv.rs:15:11
|
||||
|
|
||||
LL | v.into_key();
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0`
|
||||
--> $DIR/incompatible_msrv.rs:19:5
|
||||
|
|
||||
LL | sleep(Duration::new(1, 0));
|
||||
| ^^^^^
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
60
tests/ui/manual_c_str_literals.fixed
Normal file
60
tests/ui/manual_c_str_literals.fixed
Normal file
|
@ -0,0 +1,60 @@
|
|||
#![warn(clippy::manual_c_str_literals)]
|
||||
#![allow(clippy::no_effect)]
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
macro_rules! cstr {
|
||||
($s:literal) => {
|
||||
CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_returns_c_str {
|
||||
() => {
|
||||
CStr::from_bytes_with_nul(b"foo\0").unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_returns_byte_string {
|
||||
() => {
|
||||
b"foo\0"
|
||||
};
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.76.0"]
|
||||
fn pre_stabilization() {
|
||||
CStr::from_bytes_with_nul(b"foo\0");
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.77.0"]
|
||||
fn post_stabilization() {
|
||||
c"foo";
|
||||
}
|
||||
|
||||
fn main() {
|
||||
c"foo";
|
||||
c"foo";
|
||||
c"foo";
|
||||
c"foo\\0sdsd";
|
||||
CStr::from_bytes_with_nul(br"foo\\0sdsd\0").unwrap();
|
||||
CStr::from_bytes_with_nul(br"foo\x00").unwrap();
|
||||
CStr::from_bytes_with_nul(br##"foo#a\0"##).unwrap();
|
||||
|
||||
unsafe { c"foo" };
|
||||
unsafe { c"foo" };
|
||||
let _: *const _ = c"foo".as_ptr();
|
||||
let _: *const _ = c"foo".as_ptr();
|
||||
let _: *const _ = "foo".as_ptr(); // not a C-string
|
||||
let _: *const _ = "".as_ptr();
|
||||
let _: *const _ = c"foo".as_ptr().cast::<i8>();
|
||||
let _ = "电脑".as_ptr();
|
||||
let _ = "电脑\\".as_ptr();
|
||||
let _ = c"电脑\\".as_ptr();
|
||||
let _ = c"电脑".as_ptr();
|
||||
let _ = c"电脑".as_ptr();
|
||||
|
||||
// Macro cases, don't lint:
|
||||
cstr!("foo");
|
||||
macro_returns_c_str!();
|
||||
CStr::from_bytes_with_nul(macro_returns_byte_string!()).unwrap();
|
||||
}
|
60
tests/ui/manual_c_str_literals.rs
Normal file
60
tests/ui/manual_c_str_literals.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
#![warn(clippy::manual_c_str_literals)]
|
||||
#![allow(clippy::no_effect)]
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
macro_rules! cstr {
|
||||
($s:literal) => {
|
||||
CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_returns_c_str {
|
||||
() => {
|
||||
CStr::from_bytes_with_nul(b"foo\0").unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_returns_byte_string {
|
||||
() => {
|
||||
b"foo\0"
|
||||
};
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.76.0"]
|
||||
fn pre_stabilization() {
|
||||
CStr::from_bytes_with_nul(b"foo\0");
|
||||
}
|
||||
|
||||
#[clippy::msrv = "1.77.0"]
|
||||
fn post_stabilization() {
|
||||
CStr::from_bytes_with_nul(b"foo\0");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
CStr::from_bytes_with_nul(b"foo\0");
|
||||
CStr::from_bytes_with_nul(b"foo\x00");
|
||||
CStr::from_bytes_with_nul(b"foo\0").unwrap();
|
||||
CStr::from_bytes_with_nul(b"foo\\0sdsd\0").unwrap();
|
||||
CStr::from_bytes_with_nul(br"foo\\0sdsd\0").unwrap();
|
||||
CStr::from_bytes_with_nul(br"foo\x00").unwrap();
|
||||
CStr::from_bytes_with_nul(br##"foo#a\0"##).unwrap();
|
||||
|
||||
unsafe { CStr::from_ptr(b"foo\0".as_ptr().cast()) };
|
||||
unsafe { CStr::from_ptr(b"foo\0".as_ptr() as *const _) };
|
||||
let _: *const _ = b"foo\0".as_ptr();
|
||||
let _: *const _ = "foo\0".as_ptr();
|
||||
let _: *const _ = "foo".as_ptr(); // not a C-string
|
||||
let _: *const _ = "".as_ptr();
|
||||
let _: *const _ = b"foo\0".as_ptr().cast::<i8>();
|
||||
let _ = "电脑".as_ptr();
|
||||
let _ = "电脑\\".as_ptr();
|
||||
let _ = "电脑\\\0".as_ptr();
|
||||
let _ = "电脑\0".as_ptr();
|
||||
let _ = "电脑\x00".as_ptr();
|
||||
|
||||
// Macro cases, don't lint:
|
||||
cstr!("foo");
|
||||
macro_returns_c_str!();
|
||||
CStr::from_bytes_with_nul(macro_returns_byte_string!()).unwrap();
|
||||
}
|
83
tests/ui/manual_c_str_literals.stderr
Normal file
83
tests/ui/manual_c_str_literals.stderr
Normal file
|
@ -0,0 +1,83 @@
|
|||
error: calling `CStr::new` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:31:5
|
||||
|
|
||||
LL | CStr::from_bytes_with_nul(b"foo\0");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
|
||||
= note: `-D clippy::manual-c-str-literals` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::manual_c_str_literals)]`
|
||||
|
||||
error: calling `CStr::new` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:35:5
|
||||
|
|
||||
LL | CStr::from_bytes_with_nul(b"foo\0");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: calling `CStr::new` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:36:5
|
||||
|
|
||||
LL | CStr::from_bytes_with_nul(b"foo\x00");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: calling `CStr::new` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:37:5
|
||||
|
|
||||
LL | CStr::from_bytes_with_nul(b"foo\0").unwrap();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: calling `CStr::new` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:38:5
|
||||
|
|
||||
LL | CStr::from_bytes_with_nul(b"foo\\0sdsd\0").unwrap();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo\\0sdsd"`
|
||||
|
||||
error: calling `CStr::from_ptr` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:43:14
|
||||
|
|
||||
LL | unsafe { CStr::from_ptr(b"foo\0".as_ptr().cast()) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: calling `CStr::from_ptr` with a byte string literal
|
||||
--> $DIR/manual_c_str_literals.rs:44:14
|
||||
|
|
||||
LL | unsafe { CStr::from_ptr(b"foo\0".as_ptr() as *const _) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: manually constructing a nul-terminated string
|
||||
--> $DIR/manual_c_str_literals.rs:45:23
|
||||
|
|
||||
LL | let _: *const _ = b"foo\0".as_ptr();
|
||||
| ^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: manually constructing a nul-terminated string
|
||||
--> $DIR/manual_c_str_literals.rs:46:23
|
||||
|
|
||||
LL | let _: *const _ = "foo\0".as_ptr();
|
||||
| ^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: manually constructing a nul-terminated string
|
||||
--> $DIR/manual_c_str_literals.rs:49:23
|
||||
|
|
||||
LL | let _: *const _ = b"foo\0".as_ptr().cast::<i8>();
|
||||
| ^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
||||
|
||||
error: manually constructing a nul-terminated string
|
||||
--> $DIR/manual_c_str_literals.rs:52:13
|
||||
|
|
||||
LL | let _ = "电脑\\\0".as_ptr();
|
||||
| ^^^^^^^^^^ help: use a `c""` literal: `c"电脑\\"`
|
||||
|
||||
error: manually constructing a nul-terminated string
|
||||
--> $DIR/manual_c_str_literals.rs:53:13
|
||||
|
|
||||
LL | let _ = "电脑\0".as_ptr();
|
||||
| ^^^^^^^^ help: use a `c""` literal: `c"电脑"`
|
||||
|
||||
error: manually constructing a nul-terminated string
|
||||
--> $DIR/manual_c_str_literals.rs:54:13
|
||||
|
|
||||
LL | let _ = "电脑\x00".as_ptr();
|
||||
| ^^^^^^^^^^ help: use a `c""` literal: `c"电脑"`
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
|
|
@ -14,6 +14,9 @@ fn main() {
|
|||
_msrv_153();
|
||||
_msrv_126();
|
||||
_msrv_118();
|
||||
|
||||
issue_10393();
|
||||
issue_12081();
|
||||
}
|
||||
|
||||
fn binary_heap_retain() {
|
||||
|
@ -23,6 +26,11 @@ fn binary_heap_retain() {
|
|||
binary_heap.retain(|x| x % 2 == 0);
|
||||
binary_heap.retain(|x| x % 2 == 0);
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = BinaryHeap::from([(0, 1), (1, 2), (2, 3)]);
|
||||
tuples.retain(|(ref x, ref y)| *x == 0);
|
||||
tuples.retain(|(x, y)| *x == 0);
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
binary_heap = binary_heap
|
||||
.into_iter()
|
||||
|
@ -55,6 +63,9 @@ fn btree_map_retain() {
|
|||
btree_map.retain(|_, &mut v| v % 2 == 0);
|
||||
btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0));
|
||||
|
||||
// Do not lint, because the parameters are not matched in tuple pattern
|
||||
btree_map = btree_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
|
||||
|
||||
// Do not lint.
|
||||
btree_map = btree_map
|
||||
.into_iter()
|
||||
|
@ -76,6 +87,11 @@ fn btree_set_retain() {
|
|||
btree_set.retain(|x| x % 2 == 0);
|
||||
btree_set.retain(|x| x % 2 == 0);
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = BTreeSet::from([(0, 1), (1, 2), (2, 3)]);
|
||||
tuples.retain(|(ref x, ref y)| *x == 0);
|
||||
tuples.retain(|(x, y)| *x == 0);
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
btree_set = btree_set
|
||||
.iter()
|
||||
|
@ -108,6 +124,9 @@ fn hash_map_retain() {
|
|||
hash_map.retain(|_, &mut v| v % 2 == 0);
|
||||
hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0));
|
||||
|
||||
// Do not lint, because the parameters are not matched in tuple pattern
|
||||
hash_map = hash_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
|
||||
|
||||
// Do not lint.
|
||||
hash_map = hash_map
|
||||
.into_iter()
|
||||
|
@ -128,6 +147,11 @@ fn hash_set_retain() {
|
|||
hash_set.retain(|x| x % 2 == 0);
|
||||
hash_set.retain(|x| x % 2 == 0);
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = HashSet::from([(0, 1), (1, 2), (2, 3)]);
|
||||
tuples.retain(|(ref x, ref y)| *x == 0);
|
||||
tuples.retain(|(x, y)| *x == 0);
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>();
|
||||
hash_set = hash_set
|
||||
|
@ -171,6 +195,11 @@ fn vec_retain() {
|
|||
vec.retain(|x| x % 2 == 0);
|
||||
vec.retain(|x| x % 2 == 0);
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = vec![(0, 1), (1, 2), (2, 3)];
|
||||
tuples.retain(|(ref x, ref y)| *x == 0);
|
||||
tuples.retain(|(x, y)| *x == 0);
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>();
|
||||
vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>();
|
||||
|
@ -246,3 +275,37 @@ fn _msrv_118() {
|
|||
let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect();
|
||||
hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
|
||||
}
|
||||
|
||||
fn issue_10393() {
|
||||
// Do lint
|
||||
let mut vec = vec![(0, 1), (1, 2), (2, 3)];
|
||||
vec.retain(|(x, y)| *x == 0);
|
||||
|
||||
// Do lint
|
||||
let mut tuples = vec![(true, -2), (false, 3)];
|
||||
tuples.retain(|(_, n)| *n > 0);
|
||||
}
|
||||
|
||||
fn issue_11457() {
|
||||
// Do not lint, as we need to modify the closure
|
||||
let mut vals = vec![1, 2, 3, 4];
|
||||
vals = vals.iter().filter(|v| **v != 1).cloned().collect();
|
||||
|
||||
// Do not lint, as we need to modify the closure
|
||||
let mut s = String::from("foobar");
|
||||
s = s.chars().filter(|c| *c != 'o').to_owned().collect();
|
||||
}
|
||||
|
||||
fn issue_12081() {
|
||||
let mut vec = vec![0, 1, 2];
|
||||
|
||||
// Do lint
|
||||
vec.retain(|&x| x == 0);
|
||||
vec.retain(|&x| x == 0);
|
||||
vec.retain(|&x| x == 0);
|
||||
|
||||
// Do lint
|
||||
vec.retain(|x| *x == 0);
|
||||
vec.retain(|x| *x == 0);
|
||||
vec.retain(|x| *x == 0);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ fn main() {
|
|||
_msrv_153();
|
||||
_msrv_126();
|
||||
_msrv_118();
|
||||
|
||||
issue_10393();
|
||||
issue_12081();
|
||||
}
|
||||
|
||||
fn binary_heap_retain() {
|
||||
|
@ -23,6 +26,11 @@ fn binary_heap_retain() {
|
|||
binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = BinaryHeap::from([(0, 1), (1, 2), (2, 3)]);
|
||||
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
binary_heap = binary_heap
|
||||
.into_iter()
|
||||
|
@ -58,6 +66,9 @@ fn btree_map_retain() {
|
|||
.filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0))
|
||||
.collect();
|
||||
|
||||
// Do not lint, because the parameters are not matched in tuple pattern
|
||||
btree_map = btree_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
|
||||
|
||||
// Do not lint.
|
||||
btree_map = btree_map
|
||||
.into_iter()
|
||||
|
@ -79,6 +90,11 @@ fn btree_set_retain() {
|
|||
btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = BTreeSet::from([(0, 1), (1, 2), (2, 3)]);
|
||||
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
btree_set = btree_set
|
||||
.iter()
|
||||
|
@ -114,6 +130,9 @@ fn hash_map_retain() {
|
|||
.filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0))
|
||||
.collect();
|
||||
|
||||
// Do not lint, because the parameters are not matched in tuple pattern
|
||||
hash_map = hash_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
|
||||
|
||||
// Do not lint.
|
||||
hash_map = hash_map
|
||||
.into_iter()
|
||||
|
@ -134,6 +153,11 @@ fn hash_set_retain() {
|
|||
hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = HashSet::from([(0, 1), (1, 2), (2, 3)]);
|
||||
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>();
|
||||
hash_set = hash_set
|
||||
|
@ -177,6 +201,11 @@ fn vec_retain() {
|
|||
vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
vec = vec.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
|
||||
// Do lint, because we use pattern matching
|
||||
let mut tuples = vec![(0, 1), (1, 2), (2, 3)];
|
||||
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
|
||||
// Do not lint, because type conversion is performed
|
||||
vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>();
|
||||
vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>();
|
||||
|
@ -252,3 +281,37 @@ fn _msrv_118() {
|
|||
let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect();
|
||||
hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
|
||||
}
|
||||
|
||||
fn issue_10393() {
|
||||
// Do lint
|
||||
let mut vec = vec![(0, 1), (1, 2), (2, 3)];
|
||||
vec = vec.into_iter().filter(|(x, y)| *x == 0).collect();
|
||||
|
||||
// Do lint
|
||||
let mut tuples = vec![(true, -2), (false, 3)];
|
||||
tuples = tuples.into_iter().filter(|(_, n)| *n > 0).collect();
|
||||
}
|
||||
|
||||
fn issue_11457() {
|
||||
// Do not lint, as we need to modify the closure
|
||||
let mut vals = vec![1, 2, 3, 4];
|
||||
vals = vals.iter().filter(|v| **v != 1).cloned().collect();
|
||||
|
||||
// Do not lint, as we need to modify the closure
|
||||
let mut s = String::from("foobar");
|
||||
s = s.chars().filter(|c| *c != 'o').to_owned().collect();
|
||||
}
|
||||
|
||||
fn issue_12081() {
|
||||
let mut vec = vec![0, 1, 2];
|
||||
|
||||
// Do lint
|
||||
vec = vec.iter().filter(|&&x| x == 0).copied().collect();
|
||||
vec = vec.iter().filter(|&&x| x == 0).cloned().collect();
|
||||
vec = vec.into_iter().filter(|&x| x == 0).collect();
|
||||
|
||||
// Do lint
|
||||
vec = vec.iter().filter(|&x| *x == 0).copied().collect();
|
||||
vec = vec.iter().filter(|&x| *x == 0).cloned().collect();
|
||||
vec = vec.into_iter().filter(|x| *x == 0).collect();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:22:5
|
||||
--> $DIR/manual_retain.rs:25:5
|
||||
|
|
||||
LL | binary_heap = binary_heap.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)`
|
||||
|
@ -8,31 +8,43 @@ LL | binary_heap = binary_heap.into_iter().filter(|x| x % 2 == 0).collect();
|
|||
= help: to override `-D warnings` add `#[allow(clippy::manual_retain)]`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:23:5
|
||||
--> $DIR/manual_retain.rs:26:5
|
||||
|
|
||||
LL | binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:24:5
|
||||
--> $DIR/manual_retain.rs:27:5
|
||||
|
|
||||
LL | binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:54:5
|
||||
--> $DIR/manual_retain.rs:31:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:32:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:62:5
|
||||
|
|
||||
LL | btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|k, _| k % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:55:5
|
||||
--> $DIR/manual_retain.rs:63:5
|
||||
|
|
||||
LL | btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|_, &mut v| v % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:56:5
|
||||
--> $DIR/manual_retain.rs:64:5
|
||||
|
|
||||
LL | / btree_map = btree_map
|
||||
LL | | .into_iter()
|
||||
|
@ -41,37 +53,49 @@ LL | | .collect();
|
|||
| |__________________^ help: consider calling `.retain()` instead: `btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:78:5
|
||||
--> $DIR/manual_retain.rs:89:5
|
||||
|
|
||||
LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:79:5
|
||||
--> $DIR/manual_retain.rs:90:5
|
||||
|
|
||||
LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:80:5
|
||||
--> $DIR/manual_retain.rs:91:5
|
||||
|
|
||||
LL | btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:110:5
|
||||
--> $DIR/manual_retain.rs:95:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:96:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:126:5
|
||||
|
|
||||
LL | hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|k, _| k % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:111:5
|
||||
--> $DIR/manual_retain.rs:127:5
|
||||
|
|
||||
LL | hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|_, &mut v| v % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:112:5
|
||||
--> $DIR/manual_retain.rs:128:5
|
||||
|
|
||||
LL | / hash_map = hash_map
|
||||
LL | | .into_iter()
|
||||
|
@ -80,64 +104,136 @@ LL | | .collect();
|
|||
| |__________________^ help: consider calling `.retain()` instead: `hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:133:5
|
||||
--> $DIR/manual_retain.rs:152:5
|
||||
|
|
||||
LL | hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:134:5
|
||||
--> $DIR/manual_retain.rs:153:5
|
||||
|
|
||||
LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:135:5
|
||||
--> $DIR/manual_retain.rs:154:5
|
||||
|
|
||||
LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:164:5
|
||||
--> $DIR/manual_retain.rs:158:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:159:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:188:5
|
||||
|
|
||||
LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `s.retain(|c| c != 'o')`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:176:5
|
||||
--> $DIR/manual_retain.rs:200:5
|
||||
|
|
||||
LL | vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:177:5
|
||||
--> $DIR/manual_retain.rs:201:5
|
||||
|
|
||||
LL | vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:178:5
|
||||
--> $DIR/manual_retain.rs:202:5
|
||||
|
|
||||
LL | vec = vec.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:200:5
|
||||
--> $DIR/manual_retain.rs:206:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:207:5
|
||||
|
|
||||
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:229:5
|
||||
|
|
||||
LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:201:5
|
||||
--> $DIR/manual_retain.rs:230:5
|
||||
|
|
||||
LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:202:5
|
||||
--> $DIR/manual_retain.rs:231:5
|
||||
|
|
||||
LL | vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)`
|
||||
|
||||
error: aborting due to 22 previous errors
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:288:5
|
||||
|
|
||||
LL | vec = vec.into_iter().filter(|(x, y)| *x == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|(x, y)| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:292:5
|
||||
|
|
||||
LL | tuples = tuples.into_iter().filter(|(_, n)| *n > 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(_, n)| *n > 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:309:5
|
||||
|
|
||||
LL | vec = vec.iter().filter(|&&x| x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|&x| x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:310:5
|
||||
|
|
||||
LL | vec = vec.iter().filter(|&&x| x == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|&x| x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:311:5
|
||||
|
|
||||
LL | vec = vec.into_iter().filter(|&x| x == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|&x| x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:314:5
|
||||
|
|
||||
LL | vec = vec.iter().filter(|&x| *x == 0).copied().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:315:5
|
||||
|
|
||||
LL | vec = vec.iter().filter(|&x| *x == 0).cloned().collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| *x == 0)`
|
||||
|
||||
error: this expression can be written more simply using `.retain()`
|
||||
--> $DIR/manual_retain.rs:316:5
|
||||
|
|
||||
LL | vec = vec.into_iter().filter(|x| *x == 0).collect();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| *x == 0)`
|
||||
|
||||
error: aborting due to 38 previous errors
|
||||
|
||||
|
|
|
@ -114,4 +114,12 @@ fn main() {
|
|||
a_usize % b_usize;
|
||||
let mut a_usize: usize = 1;
|
||||
a_usize %= 2;
|
||||
|
||||
// No lint when comparing to zero
|
||||
let a = -1;
|
||||
let mut b = 2;
|
||||
let c = a % b == 0;
|
||||
let c = 0 == a % b;
|
||||
let c = a % b != 0;
|
||||
let c = 0 != a % b;
|
||||
}
|
||||
|
|
|
@ -131,6 +131,9 @@ fn main() {
|
|||
0
|
||||
}
|
||||
}
|
||||
|
||||
// issue #11786
|
||||
let x: (&str,) = ("",);
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_borrowed_reference)]
|
||||
|
|
|
@ -131,6 +131,9 @@ fn main() {
|
|||
0
|
||||
}
|
||||
}
|
||||
|
||||
// issue #11786
|
||||
let x: (&str,) = (&"",);
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_borrowed_reference)]
|
||||
|
|
|
@ -121,41 +121,47 @@ error: this expression creates a reference which is immediately dereferenced by
|
|||
LL | (&&5).foo();
|
||||
| ^^^^^ help: change this to: `(&5)`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:136:23
|
||||
|
|
||||
LL | let x: (&str,) = (&"",);
|
||||
| ^^^ help: change this to: `""`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:175:13
|
||||
--> $DIR/needless_borrow.rs:178:13
|
||||
|
|
||||
LL | (&self.f)()
|
||||
| ^^^^^^^^^ help: change this to: `(self.f)`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:184:13
|
||||
--> $DIR/needless_borrow.rs:187:13
|
||||
|
|
||||
LL | (&mut self.f)()
|
||||
| ^^^^^^^^^^^^^ help: change this to: `(self.f)`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:221:22
|
||||
--> $DIR/needless_borrow.rs:224:22
|
||||
|
|
||||
LL | let _ = &mut (&mut { x.u }).x;
|
||||
| ^^^^^^^^^^^^^^ help: change this to: `{ x.u }`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:228:22
|
||||
--> $DIR/needless_borrow.rs:231:22
|
||||
|
|
||||
LL | let _ = &mut (&mut { x.u }).x;
|
||||
| ^^^^^^^^^^^^^^ help: change this to: `{ x.u }`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:232:22
|
||||
--> $DIR/needless_borrow.rs:235:22
|
||||
|
|
||||
LL | let _ = &mut (&mut x.u).x;
|
||||
| ^^^^^^^^^^ help: change this to: `x.u`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:233:22
|
||||
--> $DIR/needless_borrow.rs:236:22
|
||||
|
|
||||
LL | let _ = &mut (&mut { x.u }).x;
|
||||
| ^^^^^^^^^^^^^^ help: change this to: `{ x.u }`
|
||||
|
||||
error: aborting due to 26 previous errors
|
||||
error: aborting due to 27 previous errors
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// With an explicit return type it should lint too
|
||||
/// ```edition2015
|
||||
/// fn main() -> () {
|
||||
|
@ -18,7 +18,7 @@
|
|||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// This should, too.
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
|
@ -26,7 +26,7 @@
|
|||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// This one too.
|
||||
/// ```no_run
|
||||
/// // the fn is not always the first line
|
||||
|
|
|
@ -77,3 +77,53 @@ fn issue11616() -> Result<(), ()> {
|
|||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn issue11982() {
|
||||
mod bar {
|
||||
pub struct Error;
|
||||
pub fn foo(_: bool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Error;
|
||||
|
||||
impl From<bar::Error> for Error {
|
||||
fn from(_: bar::Error) -> Self {
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
||||
fn foo(ok: bool) -> Result<(), Error> {
|
||||
if !ok {
|
||||
return bar::foo(ok).map(|_| Ok::<(), Error>(()))?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn issue11982_no_conversion() {
|
||||
mod bar {
|
||||
pub struct Error;
|
||||
pub fn foo(_: bool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn foo(ok: bool) -> Result<(), bar::Error> {
|
||||
if !ok {
|
||||
return bar::foo(ok).map(|_| Ok::<(), bar::Error>(()))?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn general_return() {
|
||||
fn foo(ok: bool) -> Result<(), ()> {
|
||||
let bar = Result::Ok(Result::<(), ()>::Ok(()));
|
||||
if !ok {
|
||||
return bar?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,3 +77,53 @@ fn issue11616() -> Result<(), ()> {
|
|||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn issue11982() {
|
||||
mod bar {
|
||||
pub struct Error;
|
||||
pub fn foo(_: bool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Error;
|
||||
|
||||
impl From<bar::Error> for Error {
|
||||
fn from(_: bar::Error) -> Self {
|
||||
Error
|
||||
}
|
||||
}
|
||||
|
||||
fn foo(ok: bool) -> Result<(), Error> {
|
||||
if !ok {
|
||||
return bar::foo(ok).map(|_| Ok::<(), Error>(()))?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn issue11982_no_conversion() {
|
||||
mod bar {
|
||||
pub struct Error;
|
||||
pub fn foo(_: bool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn foo(ok: bool) -> Result<(), bar::Error> {
|
||||
if !ok {
|
||||
return bar::foo(ok).map(|_| Ok::<(), bar::Error>(()))?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn general_return() {
|
||||
fn foo(ok: bool) -> Result<(), ()> {
|
||||
let bar = Result::Ok(Result::<(), ()>::Ok(()));
|
||||
if !ok {
|
||||
return bar?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(inline_const)]
|
||||
#![feature(inline_const, try_blocks)]
|
||||
#![allow(
|
||||
clippy::eq_op,
|
||||
clippy::single_match,
|
||||
|
@ -400,6 +400,15 @@ pub fn test32() {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn issue12205() -> Option<()> {
|
||||
loop {
|
||||
let _: Option<_> = try {
|
||||
None?;
|
||||
return Some(());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test1();
|
||||
test2();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue