1
Fork 0

Merge commit 'cb806113e0' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-08-08 19:13:50 +02:00
parent 4c6a3f4b6e
commit 1ac76a2062
297 changed files with 5624 additions and 4064 deletions

View file

@ -13,6 +13,13 @@ target-dir = "target"
[unstable] [unstable]
binary-dep-depinfo = true binary-dep-depinfo = true
profile-rustflags = true
[profile.dev] [profile.dev]
split-debuginfo = "unpacked" split-debuginfo = "unpacked"
# Add back the containing directory of the packages we have to refer to using --manifest-path
[profile.dev.package.clippy_dev]
rustflags = ["--remap-path-prefix", "=clippy_dev"]
[profile.dev.package.lintcheck]
rustflags = ["--remap-path-prefix", "=lintcheck"]

View file

@ -25,6 +25,7 @@ env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
NO_FMT_TEST: 1 NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings
concurrency: concurrency:
# For a given workflow, if we push to the same PR, cancel all previous builds on that PR. # For a given workflow, if we push to the same PR, cancel all previous builds on that PR.
@ -47,25 +48,25 @@ jobs:
# Run # Run
- name: Build - name: Build
run: cargo build --tests --features deny-warnings,internal run: cargo build --tests --features internal
- name: Test - name: Test
run: cargo test --features deny-warnings,internal run: cargo test --features internal
- name: Test clippy_lints - name: Test clippy_lints
run: cargo test --features deny-warnings,internal run: cargo test --features internal
working-directory: clippy_lints working-directory: clippy_lints
- name: Test clippy_utils - name: Test clippy_utils
run: cargo test --features deny-warnings run: cargo test
working-directory: clippy_utils working-directory: clippy_utils
- name: Test rustc_tools_util - name: Test rustc_tools_util
run: cargo test --features deny-warnings run: cargo test
working-directory: rustc_tools_util working-directory: rustc_tools_util
- name: Test clippy_dev - name: Test clippy_dev
run: cargo test --features deny-warnings run: cargo test
working-directory: clippy_dev working-directory: clippy_dev
- name: Test clippy-driver - name: Test clippy-driver

View file

@ -11,6 +11,7 @@ env:
CARGO_TARGET_DIR: '${{ github.workspace }}/target' CARGO_TARGET_DIR: '${{ github.workspace }}/target'
NO_FMT_TEST: 1 NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings
concurrency: concurrency:
# For a given workflow, if we push to the same branch, cancel all previous builds on that branch. # For a given workflow, if we push to the same branch, cancel all previous builds on that branch.
@ -85,34 +86,34 @@ jobs:
# Run # Run
- name: Build - name: Build
run: cargo build --tests --features deny-warnings,internal run: cargo build --tests --features internal
- name: Test - name: Test
if: matrix.host == 'x86_64-unknown-linux-gnu' if: matrix.host == 'x86_64-unknown-linux-gnu'
run: cargo test --features deny-warnings,internal run: cargo test --features internal
- name: Test - name: Test
if: matrix.host != 'x86_64-unknown-linux-gnu' if: matrix.host != 'x86_64-unknown-linux-gnu'
run: cargo test --features deny-warnings,internal -- --skip dogfood run: cargo test --features internal -- --skip dogfood
- name: Test clippy_lints - name: Test clippy_lints
run: cargo test --features deny-warnings,internal run: cargo test --features internal
working-directory: clippy_lints working-directory: clippy_lints
- name: Test clippy_utils - name: Test clippy_utils
run: cargo test --features deny-warnings run: cargo test
working-directory: clippy_utils working-directory: clippy_utils
- name: Test clippy_config - name: Test clippy_config
run: cargo test --features deny-warnings run: cargo test
working-directory: clippy_config working-directory: clippy_config
- name: Test rustc_tools_util - name: Test rustc_tools_util
run: cargo test --features deny-warnings run: cargo test
working-directory: rustc_tools_util working-directory: rustc_tools_util
- name: Test clippy_dev - name: Test clippy_dev
run: cargo test --features deny-warnings run: cargo test
working-directory: clippy_dev working-directory: clippy_dev
- name: Test clippy-driver - name: Test clippy-driver

View file

@ -16,6 +16,7 @@ on:
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings
jobs: jobs:
clippy_dev: clippy_dev:
@ -28,7 +29,7 @@ jobs:
# Run # Run
- name: Build - name: Build
run: cargo build --features deny-warnings run: cargo build
working-directory: clippy_dev working-directory: clippy_dev
- name: Test update_lints - name: Test update_lints
@ -38,6 +39,8 @@ jobs:
run: cargo dev fmt --check run: cargo dev fmt --check
- name: Test cargo dev new lint - name: Test cargo dev new lint
env:
RUSTFLAGS: -A unused-imports
run: | run: |
cargo dev new_lint --name new_early_pass --pass early cargo dev new_lint --name new_early_pass --pass early
cargo dev new_lint --name new_late_pass --pass late cargo dev new_lint --name new_late_pass --pass late

View file

@ -58,7 +58,7 @@ jobs:
- name: Run lintcheck - name: Run lintcheck
if: steps.cache-json.outputs.cache-hit != 'true' if: steps.cache-json.outputs.cache-hit != 'true'
run: ./target/debug/lintcheck --format json --warn-all --crates-toml ./lintcheck/ci_crates.toml run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
- name: Upload base JSON - name: Upload base JSON
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -86,7 +86,7 @@ jobs:
run: cargo build --manifest-path=lintcheck/Cargo.toml run: cargo build --manifest-path=lintcheck/Cargo.toml
- name: Run lintcheck - name: Run lintcheck
run: ./target/debug/lintcheck --format json --warn-all --crates-toml ./lintcheck/ci_crates.toml run: ./target/debug/lintcheck --format json --all-lints --crates-toml ./lintcheck/ci_crates.toml
- name: Upload head JSON - name: Upload head JSON
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View file

@ -5830,6 +5830,7 @@ Released 2018-09-13
[`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err [`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err
[`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used
[`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use [`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use
[`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop
[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push [`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
@ -5998,6 +5999,7 @@ Released 2018-09-13
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
[`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable [`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable
[`unused_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_result_ok
[`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding [`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding
[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit

View file

@ -30,7 +30,7 @@ color-print = "0.3.4"
anstream = "0.6.0" anstream = "0.6.0"
[dev-dependencies] [dev-dependencies]
ui_test = "0.24" ui_test = "0.25"
regex = "1.5.5" regex = "1.5.5"
toml = "0.7.3" toml = "0.7.3"
walkdir = "2.3" walkdir = "2.3"
@ -51,7 +51,6 @@ tokio = { version = "1", features = ["io-util"] }
rustc_tools_util = "0.3.0" rustc_tools_util = "0.3.0"
[features] [features]
deny-warnings = ["clippy_lints/deny-warnings"]
integration = ["tempfile"] integration = ["tempfile"]
internal = ["clippy_lints/internal", "tempfile"] internal = ["clippy_lints/internal", "tempfile"]

View file

@ -364,7 +364,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
## `await-holding-invalid-types` ## `await-holding-invalid-types`
The list of types which may not be held across an await point.
**Default Value:** `[]` **Default Value:** `[]`
@ -668,6 +668,8 @@ crate. For example, `pub(crate)` items.
## `msrv` ## `msrv`
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
**Default Value:** `current version`
--- ---
**Affected lints:** **Affected lints:**
* [`allow_attributes`](https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes) * [`allow_attributes`](https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes)
@ -862,6 +864,8 @@ The maximum number of lines a function or method can have
The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
reference. By default there is no limit reference. By default there is no limit
**Default Value:** `target_pointer_width * 2`
--- ---
**Affected lints:** **Affected lints:**
* [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref) * [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref)

View file

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
itertools = "0.12"
rustc-semver = "1.1" rustc-semver = "1.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = "0.7.3" toml = "0.7.3"
@ -13,9 +14,6 @@ toml = "0.7.3"
[dev-dependencies] [dev-dependencies]
walkdir = "2.3" walkdir = "2.3"
[features]
deny-warnings = []
[package.metadata.rust-analyzer] [package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)] # This crate uses #[feature(rustc_private)]
rustc_private = true rustc_private = true

View file

@ -123,7 +123,8 @@ macro_rules! define_Conf {
$(#[doc = $doc:literal])+ $(#[doc = $doc:literal])+
$(#[conf_deprecated($dep:literal, $new_conf:ident)])? $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
$(#[default_text = $default_text:expr])? $(#[default_text = $default_text:expr])?
($name:ident: $ty:ty = $default:expr), $(#[lints($($for_lints:ident),* $(,)?)])?
$name:ident: $ty:ty = $default:expr,
)*) => { )*) => {
/// Clippy lint configuration /// Clippy lint configuration
pub struct Conf { pub struct Conf {
@ -201,411 +202,91 @@ macro_rules! define_Conf {
} }
pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> { pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
let mut sorted = vec![ vec![$(
$( ClippyConfiguration {
{ name: stringify!($name).replace('_', "-"),
let deprecation_reason = wrap_option!($($dep)?); default: default_text!(defaults::$name() $(, $default_text)?),
lints: &[$($(stringify!($for_lints)),*)?],
ClippyConfiguration::new( doc: concat!($($doc, '\n',)*),
stringify!($name), deprecation_reason: wrap_option!($($dep)?)
default_text!(defaults::$name() $(, $default_text)?), },
concat!($($doc, '\n',)*), )*]
deprecation_reason,
)
},
)+
];
sorted.sort_by(|a, b| a.name.cmp(&b.name));
sorted
} }
}; };
} }
define_Conf! { define_Conf! {
/// Lint: ARITHMETIC_SIDE_EFFECTS. /// Which crates to allow absolute paths from
/// #[lints(absolute_paths)]
/// Suppress checking of the passed type names in all types of operations. absolute_paths_allowed_crates: FxHashSet<String> = FxHashSet::default(),
/// /// The maximum number of segments a path can have before being linted, anything above this will
/// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead. /// be linted.
#[lints(absolute_paths)]
absolute_paths_max_segments: u64 = 2,
/// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
#[lints(undocumented_unsafe_blocks)]
accept_comment_above_attributes: bool = true,
/// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
#[lints(undocumented_unsafe_blocks)]
accept_comment_above_statement: bool = true,
/// Don't lint when comparing the result of a modulo operation to zero.
#[lints(modulo_arithmetic)]
allow_comparison_to_zero: bool = true,
/// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
#[lints(dbg_macro)]
allow_dbg_in_tests: bool = false,
/// Whether `expect` should be allowed in test functions or `#[cfg(test)]`
#[lints(expect_used)]
allow_expect_in_tests: bool = false,
/// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
#[lints(uninlined_format_args)]
allow_mixed_uninlined_format_args: bool = true,
/// Whether to allow `r#""#` when `r""` can be used
#[lints(unnecessary_raw_string_hashes)]
allow_one_hash_in_raw_strings: bool = false,
/// Whether `panic` should be allowed in test functions or `#[cfg(test)]`
#[lints(panic)]
allow_panic_in_tests: bool = false,
/// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]`
#[lints(print_stderr, print_stdout)]
allow_print_in_tests: bool = false,
/// Whether to allow module inception if it's not public.
#[lints(module_inception)]
allow_private_module_inception: bool = false,
/// List of trait paths to ignore when checking renamed function parameters.
/// ///
/// #### Example /// #### Example
/// ///
/// ```toml /// ```toml
/// arithmetic-side-effects-allowed = ["SomeType", "AnotherType"] /// allow-renamed-params-for = [ "std::convert::From" ]
/// ``` /// ```
/// ///
/// #### Noteworthy /// #### Noteworthy
/// ///
/// A type, say `SomeType`, listed in this configuration has the same behavior of /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr`
/// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the
(arithmetic_side_effects_allowed: Vec<String> = <_>::default()),
/// Lint: ARITHMETIC_SIDE_EFFECTS.
///
/// Suppress checking of the passed type pair names in binary operations like addition or
/// multiplication.
///
/// Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless
/// of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`.
///
/// Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as
/// `["AnotherType", "SomeType"]`.
///
/// #### Example
///
/// ```toml
/// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]]
/// ```
(arithmetic_side_effects_allowed_binary: Vec<[String; 2]> = <_>::default()),
/// Lint: ARITHMETIC_SIDE_EFFECTS.
///
/// Suppress checking of the passed type names in unary operations like "negation" (`-`).
///
/// #### Example
///
/// ```toml
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
/// ```
(arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default()),
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN, NEEDLESS_PASS_BY_REF_MUT.
///
/// 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, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS, MANUAL_PATTERN_CHAR_COMPARISON, ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON, COLLAPSIBLE_MATCH.
///
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = ""]
(msrv: Msrv = Msrv::empty()),
/// DEPRECATED LINT: BLACKLISTED_NAME.
///
/// Use the Disallowed Names lint instead
#[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
(blacklisted_names: Vec<String> = Vec::new()),
/// Lint: COGNITIVE_COMPLEXITY.
///
/// The maximum cognitive complexity a function can have
(cognitive_complexity_threshold: u64 = 25),
/// Lint: EXCESSIVE_NESTING.
///
/// The maximum amount of nesting a block can reside in
(excessive_nesting_threshold: u64 = 0),
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
///
/// Use the Cognitive Complexity lint instead.
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
(cyclomatic_complexity_threshold: u64 = 25),
/// Lint: DISALLOWED_NAMES.
///
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
/// `".."` can be used as part of the list to indicate that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value. /// default configuration of Clippy. By default, any configuration will replace the default value.
(disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()), #[lints(renamed_function_params)]
/// Lint: SEMICOLON_INSIDE_BLOCK. allow_renamed_params_for: Vec<String> =
/// DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect(),
/// Whether to lint only if it's multiline.
(semicolon_inside_block_ignore_singleline: bool = false),
/// Lint: SEMICOLON_OUTSIDE_BLOCK.
///
/// Whether to lint only if it's singleline.
(semicolon_outside_block_ignore_multiline: bool = false),
/// Lint: DOC_MARKDOWN.
///
/// The list of words this lint should not consider as identifiers needing ticks. The value
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value. For example:
/// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
/// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
(doc_valid_idents: FxHashSet<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect()),
/// Lint: TOO_MANY_ARGUMENTS.
///
/// The maximum number of argument a function or method can have
(too_many_arguments_threshold: u64 = 7),
/// Lint: TYPE_COMPLEXITY.
///
/// The maximum complexity a type can have
(type_complexity_threshold: u64 = 250),
/// Lint: MANY_SINGLE_CHAR_NAMES.
///
/// The maximum number of single char bindings a scope may have
(single_char_binding_names_threshold: u64 = 4),
/// Lint: BOXED_LOCAL, USELESS_VEC.
///
/// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
(too_large_for_stack: u64 = 200),
/// Lint: ENUM_VARIANT_NAMES.
///
/// The minimum number of enum variants for the lints about variant names to trigger
(enum_variant_name_threshold: u64 = 3),
/// Lint: STRUCT_FIELD_NAMES.
///
/// The minimum number of struct fields for the lints about field names to trigger
(struct_field_name_threshold: u64 = 3),
/// Lint: LARGE_ENUM_VARIANT.
///
/// The maximum size of an enum's variant to avoid box suggestion
(enum_variant_size_threshold: u64 = 200),
/// Lint: VERBOSE_BIT_MASK.
///
/// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
(verbose_bit_mask_threshold: u64 = 1),
/// Lint: DECIMAL_LITERAL_REPRESENTATION.
///
/// The lower bound for linting decimal literals
(literal_representation_threshold: u64 = 16384),
/// Lint: TRIVIALLY_COPY_PASS_BY_REF.
///
/// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
/// reference. By default there is no limit
#[default_text = ""]
(trivial_copy_size_limit: Option<u64> = None),
/// Lint: LARGE_TYPES_PASSED_BY_VALUE.
///
/// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
(pass_by_value_size_limit: u64 = 256),
/// Lint: TOO_MANY_LINES.
///
/// The maximum number of lines a function or method can have
(too_many_lines_threshold: u64 = 100),
/// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS.
///
/// The maximum allowed size for arrays on the stack
(array_size_threshold: u64 = 512_000),
/// Lint: LARGE_STACK_FRAMES.
///
/// The maximum allowed stack size for functions in bytes
(stack_size_threshold: u64 = 512_000),
/// Lint: VEC_BOX.
///
/// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
(vec_box_size_threshold: u64 = 4096),
/// Lint: TYPE_REPETITION_IN_BOUNDS.
///
/// The maximum number of bounds a trait can have to be linted
(max_trait_bounds: u64 = 3),
/// Lint: STRUCT_EXCESSIVE_BOOLS.
///
/// The maximum number of bool fields a struct can have
(max_struct_bools: u64 = 3),
/// Lint: FN_PARAMS_EXCESSIVE_BOOLS.
///
/// The maximum number of bool parameters a function can have
(max_fn_params_bools: u64 = 3),
/// Lint: WILDCARD_IMPORTS.
///
/// Whether to allow certain wildcard imports (prelude, super in tests).
(warn_on_all_wildcard_imports: bool = false),
/// Lint: DISALLOWED_MACROS.
///
/// The list of disallowed macros, written as fully qualified paths.
(disallowed_macros: Vec<DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_METHODS.
///
/// The list of disallowed methods, written as fully qualified paths.
(disallowed_methods: Vec<DisallowedPath> = Vec::new()),
/// Lint: DISALLOWED_TYPES.
///
/// The list of disallowed types, written as fully qualified paths.
(disallowed_types: Vec<DisallowedPath> = Vec::new()),
/// Lint: UNREADABLE_LITERAL.
///
/// Should the fraction of a decimal be linted to include separators.
(unreadable_literal_lint_fractions: bool = true),
/// Lint: UPPER_CASE_ACRONYMS.
///
/// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
(upper_case_acronyms_aggressive: bool = false),
/// Lint: MANUAL_LET_ELSE.
///
/// Whether the matches should be considered by the lint, and whether there should
/// be filtering for common types.
(matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes),
/// Lint: CARGO_COMMON_METADATA.
///
/// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
(cargo_ignore_publish: bool = false),
/// Lint: NONSTANDARD_MACRO_BRACES.
///
/// Enforce the named macros always use the braces specified.
///
/// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
/// could be used with a full path two `MacroMatcher`s have to be added one with the full path
/// `crate_name::macro_name` and one with just the macro name.
(standard_macro_braces: Vec<MacroMatcher> = Vec::new()),
/// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
///
/// The list of imports to always rename, a fully qualified path followed by the rename.
(enforced_import_renames: Vec<Rename> = Vec::new()),
/// Lint: DISALLOWED_SCRIPT_IDENTS.
///
/// The list of unicode scripts allowed to be used in the scope.
(allowed_scripts: Vec<String> = vec!["Latin".to_string()]),
/// Lint: NON_SEND_FIELDS_IN_SEND_TY.
///
/// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
(enable_raw_pointer_heuristic_for_send: bool = true),
/// Lint: INDEX_REFUTABLE_SLICE.
///
/// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
/// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
/// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
(max_suggested_slice_pattern_length: u64 = 3),
/// Lint: AWAIT_HOLDING_INVALID_TYPE.
(await_holding_invalid_types: Vec<DisallowedPath> = Vec::new()),
/// Lint: LARGE_INCLUDE_FILE.
///
/// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
(max_include_file_size: u64 = 1_000_000),
/// Lint: EXPECT_USED.
///
/// Whether `expect` should be allowed in test functions or `#[cfg(test)]`
(allow_expect_in_tests: bool = false),
/// Lint: UNWRAP_USED.
///
/// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]`
(allow_unwrap_in_tests: bool = false), #[lints(unwrap_used)]
/// Lint: PANIC. allow_unwrap_in_tests: bool = false,
///
/// Whether `panic` should be allowed in test functions or `#[cfg(test)]`
(allow_panic_in_tests: bool = false),
/// Lint: DBG_MACRO.
///
/// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]`
(allow_dbg_in_tests: bool = false),
/// Lint: PRINT_STDOUT, PRINT_STDERR.
///
/// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]`
(allow_print_in_tests: bool = false),
/// Lint: USELESS_VEC.
///
/// Whether `useless_vec` should ignore test functions or `#[cfg(test)]` /// Whether `useless_vec` should ignore test functions or `#[cfg(test)]`
(allow_useless_vec_in_tests: bool = false), #[lints(useless_vec)]
/// Lint: RESULT_LARGE_ERR. allow_useless_vec_in_tests: bool = false,
/// /// Additional dotfiles (files or directories starting with a dot) to allow
/// The maximum size of the `Err`-variant in a `Result` returned from a function #[lints(path_ends_with_ext)]
(large_error_threshold: u64 = 128), allowed_dotfiles: Vec<String> = Vec::default(),
/// Lint: MUTABLE_KEY_TYPE, IFS_SAME_COND, BORROW_INTERIOR_MUTABLE_CONST, DECLARE_INTERIOR_MUTABLE_CONST. /// A list of crate names to allow duplicates of
/// #[lints(multiple_crate_versions)]
/// A list of paths to types that should be treated as if they do not contain interior mutability allowed_duplicate_crates: FxHashSet<String> = FxHashSet::default(),
(ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()])),
/// Lint: UNINLINED_FORMAT_ARGS.
///
/// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
(allow_mixed_uninlined_format_args: bool = true),
/// Lint: INDEXING_SLICING.
///
/// Whether to suppress a restriction lint in constant code. In same
/// cases the restructured operation might not be unavoidable, as the
/// suggested counterparts are unavailable in constant code. This
/// configuration will cause restriction lints to trigger even
/// if no suggestion can be made.
(suppress_restriction_lint_in_const: bool = false),
/// Lint: MISSING_DOCS_IN_PRIVATE_ITEMS.
///
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
(missing_docs_in_crate_items: bool = false),
/// Lint: LARGE_FUTURES.
///
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
(future_size_threshold: u64 = 16 * 1024),
/// Lint: UNNECESSARY_BOX_RETURNS.
///
/// The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
(unnecessary_box_size: u64 = 128),
/// Lint: MODULE_INCEPTION.
///
/// Whether to allow module inception if it's not public.
(allow_private_module_inception: bool = false),
/// Lint: MIN_IDENT_CHARS.
///
/// Allowed names below the minimum allowed characters. The value `".."` can be used as part of /// Allowed names below the minimum allowed characters. The value `".."` can be used as part of
/// the list to indicate, that the configured values should be appended to the default /// the list to indicate, that the configured values should be appended to the default
/// configuration of Clippy. By default, any configuration will replace the default value. /// configuration of Clippy. By default, any configuration will replace the default value.
(allowed_idents_below_min_chars: FxHashSet<String> = #[lints(min_ident_chars)]
DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect()), allowed_idents_below_min_chars: FxHashSet<String> =
/// Lint: MIN_IDENT_CHARS. DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string).collect(),
///
/// Minimum chars an ident can have, anything below or equal to this will be linted.
(min_ident_chars_threshold: u64 = 1),
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
///
/// Whether to accept a safety comment to be placed above the statement containing the `unsafe` block
(accept_comment_above_statement: bool = true),
/// Lint: UNDOCUMENTED_UNSAFE_BLOCKS.
///
/// Whether to accept a safety comment to be placed above the attributes for the `unsafe` block
(accept_comment_above_attributes: bool = true),
/// Lint: UNNECESSARY_RAW_STRING_HASHES.
///
/// Whether to allow `r#""#` when `r""` can be used
(allow_one_hash_in_raw_strings: bool = false),
/// Lint: ABSOLUTE_PATHS.
///
/// The maximum number of segments a path can have before being linted, anything above this will
/// be linted.
(absolute_paths_max_segments: u64 = 2),
/// Lint: ABSOLUTE_PATHS.
///
/// Which crates to allow absolute paths from
(absolute_paths_allowed_crates: FxHashSet<String> = FxHashSet::default()),
/// Lint: PATH_ENDS_WITH_EXT.
///
/// Additional dotfiles (files or directories starting with a dot) to allow
(allowed_dotfiles: Vec<String> = Vec::default()),
/// Lint: MULTIPLE_CRATE_VERSIONS.
///
/// A list of crate names to allow duplicates of
(allowed_duplicate_crates: FxHashSet<String> = FxHashSet::default()),
/// Lint: EXPLICIT_ITER_LOOP.
///
/// Whether to recommend using implicit into iter for reborrowed values.
///
/// #### Example
/// ```no_run
/// let mut vec = vec![1, 2, 3];
/// let rmvec = &mut vec;
/// for _ in rmvec.iter() {}
/// for _ in rmvec.iter_mut() {}
/// ```
///
/// Use instead:
/// ```no_run
/// let mut vec = vec![1, 2, 3];
/// let rmvec = &mut vec;
/// for _ in &*rmvec {}
/// for _ in &mut *rmvec {}
/// ```
(enforce_iter_loop_reborrow: bool = false),
/// Lint: MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC, MISSING_PANICS_DOC, MISSING_ERRORS_DOC.
///
/// Whether to also run the listed lints on private items.
(check_private_items: bool = false),
/// Lint: PUB_UNDERSCORE_FIELDS.
///
/// 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()),
/// Lint: MODULE_NAME_REPETITIONS.
///
/// List of prefixes to allow when determining whether an item's name ends with the module's name. /// List of prefixes to allow when determining whether an item's name ends with the module's name.
/// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`), /// If the rest of an item's name is an allowed prefix (e.g. item `ToFoo` or `to_foo` in module `foo`),
/// then don't emit a warning. /// then don't emit a warning.
@ -623,28 +304,343 @@ define_Conf! {
/// `TryInto` will also be included) /// `TryInto` will also be included)
/// - Use `".."` as part of the list to indicate that the configured values should be appended to the /// - Use `".."` as part of the list to indicate that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value /// default configuration of Clippy. By default, any configuration will replace the default value
(allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect()), #[lints(module_name_repetitions)]
/// Lint: RENAMED_FUNCTION_PARAMS. allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect(),
/// /// The list of unicode scripts allowed to be used in the scope.
/// List of trait paths to ignore when checking renamed function parameters. #[lints(disallowed_script_idents)]
allowed_scripts: Vec<String> = vec!["Latin".to_string()],
/// List of path segments allowed to have wildcard imports.
/// ///
/// #### Example /// #### Example
/// ///
/// ```toml /// ```toml
/// allow-renamed-params-for = [ "std::convert::From" ] /// allowed-wildcard-imports = [ "utils", "common" ]
/// ``` /// ```
/// ///
/// #### Noteworthy /// #### Noteworthy
/// ///
/// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` /// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
/// - `".."` can be used as part of the list to indicate that the configured values should be appended to the /// 2. Paths with any segment that containing the word 'prelude'
/// default configuration of Clippy. By default, any configuration will replace the default value. /// are already allowed by default.
(allow_renamed_params_for: Vec<String> = #[lints(wildcard_imports)]
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect()), allowed_wildcard_imports: FxHashSet<String> = FxHashSet::default(),
/// Lint: MACRO_METAVARS_IN_UNSAFE. /// Suppress checking of the passed type names in all types of operations.
/// ///
/// If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead.
///
/// #### Example
///
/// ```toml
/// arithmetic-side-effects-allowed = ["SomeType", "AnotherType"]
/// ```
///
/// #### Noteworthy
///
/// A type, say `SomeType`, listed in this configuration has the same behavior of
/// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`.
#[lints(arithmetic_side_effects)]
arithmetic_side_effects_allowed: Vec<String> = <_>::default(),
/// Suppress checking of the passed type pair names in binary operations like addition or
/// multiplication.
///
/// Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless
/// of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`.
///
/// Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as
/// `["AnotherType", "SomeType"]`.
///
/// #### Example
///
/// ```toml
/// arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]]
/// ```
#[lints(arithmetic_side_effects)]
arithmetic_side_effects_allowed_binary: Vec<[String; 2]> = <_>::default(),
/// Suppress checking of the passed type names in unary operations like "negation" (`-`).
///
/// #### Example
///
/// ```toml
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
/// ```
#[lints(arithmetic_side_effects)]
arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
/// The maximum allowed size for arrays on the stack
#[lints(large_const_arrays, large_stack_arrays)]
array_size_threshold: u64 = 512_000,
/// Suppress lints whenever the suggested change would cause breakage for other crates.
#[lints(
box_collection,
enum_variant_names,
large_types_passed_by_value,
linkedlist,
needless_pass_by_ref_mut,
option_option,
rc_buffer,
rc_mutex,
redundant_allocation,
single_call_fn,
trivially_copy_pass_by_ref,
unnecessary_box_returns,
unnecessary_wraps,
unused_self,
upper_case_acronyms,
vec_box,
wrong_self_convention,
)]
avoid_breaking_exported_api: bool = true,
/// The list of types which may not be held across an await point.
#[lints(await_holding_invalid_type)]
await_holding_invalid_types: Vec<DisallowedPath> = Vec::new(),
/// DEPRECATED LINT: BLACKLISTED_NAME.
///
/// Use the Disallowed Names lint instead
#[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
blacklisted_names: Vec<String> = Vec::new(),
/// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
#[lints(cargo_common_metadata)]
cargo_ignore_publish: bool = false,
/// Whether to also run the listed lints on private items.
#[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
check_private_items: bool = false,
/// The maximum cognitive complexity a function can have
#[lints(cognitive_complexity)]
cognitive_complexity_threshold: u64 = 25,
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
///
/// Use the Cognitive Complexity lint instead.
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
cyclomatic_complexity_threshold: u64 = 25,
/// The list of disallowed macros, written as fully qualified paths.
#[lints(disallowed_macros)]
disallowed_macros: Vec<DisallowedPath> = Vec::new(),
/// The list of disallowed methods, written as fully qualified paths.
#[lints(disallowed_methods)]
disallowed_methods: Vec<DisallowedPath> = Vec::new(),
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
/// `".."` can be used as part of the list to indicate that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value.
#[lints(disallowed_names)]
disallowed_names: Vec<String> = DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect(),
/// The list of disallowed types, written as fully qualified paths.
#[lints(disallowed_types)]
disallowed_types: Vec<DisallowedPath> = Vec::new(),
/// The list of words this lint should not consider as identifiers needing ticks. The value
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value. For example:
/// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
/// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
#[lints(doc_markdown)]
doc_valid_idents: FxHashSet<String> = DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect(),
/// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
#[lints(non_send_fields_in_send_ty)]
enable_raw_pointer_heuristic_for_send: bool = true,
/// Whether to recommend using implicit into iter for reborrowed values.
///
/// #### Example
/// ```no_run
/// let mut vec = vec![1, 2, 3];
/// let rmvec = &mut vec;
/// for _ in rmvec.iter() {}
/// for _ in rmvec.iter_mut() {}
/// ```
///
/// Use instead:
/// ```no_run
/// let mut vec = vec![1, 2, 3];
/// let rmvec = &mut vec;
/// for _ in &*rmvec {}
/// for _ in &mut *rmvec {}
/// ```
#[lints(explicit_iter_loop)]
enforce_iter_loop_reborrow: bool = false,
/// The list of imports to always rename, a fully qualified path followed by the rename.
#[lints(missing_enforced_import_renames)]
enforced_import_renames: Vec<Rename> = Vec::new(),
/// The minimum number of enum variants for the lints about variant names to trigger
#[lints(enum_variant_names)]
enum_variant_name_threshold: u64 = 3,
/// The maximum size of an enum's variant to avoid box suggestion
#[lints(large_enum_variant)]
enum_variant_size_threshold: u64 = 200,
/// The maximum amount of nesting a block can reside in
#[lints(excessive_nesting)]
excessive_nesting_threshold: u64 = 0,
/// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
#[lints(large_futures)]
future_size_threshold: u64 = 16 * 1024,
/// A list of paths to types that should be treated as if they do not contain interior mutability
#[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
/// The maximum size of the `Err`-variant in a `Result` returned from a function
#[lints(result_large_err)]
large_error_threshold: u64 = 128,
/// The lower bound for linting decimal literals
#[lints(decimal_literal_representation)]
literal_representation_threshold: u64 = 16384,
/// Whether the matches should be considered by the lint, and whether there should
/// be filtering for common types.
#[lints(manual_let_else)]
matches_for_let_else: MatchLintBehaviour = MatchLintBehaviour::WellKnownTypes,
/// The maximum number of bool parameters a function can have
#[lints(fn_params_excessive_bools)]
max_fn_params_bools: u64 = 3,
/// The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes
#[lints(large_include_file)]
max_include_file_size: u64 = 1_000_000,
/// The maximum number of bool fields a struct can have
#[lints(struct_excessive_bools)]
max_struct_bools: u64 = 3,
/// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
/// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
/// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
#[lints(index_refutable_slice)]
max_suggested_slice_pattern_length: u64 = 3,
/// The maximum number of bounds a trait can have to be linted
#[lints(type_repetition_in_bounds)]
max_trait_bounds: u64 = 3,
/// Minimum chars an ident can have, anything below or equal to this will be linted.
#[lints(min_ident_chars)]
min_ident_chars_threshold: u64 = 1,
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
#[lints(missing_docs_in_private_items)]
missing_docs_in_crate_items: bool = false,
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = "current version"]
#[lints(
allow_attributes,
allow_attributes_without_reason,
almost_complete_range,
approx_constant,
assigning_clones,
borrow_as_ptr,
cast_abs_to_unsigned,
checked_conversions,
cloned_instead_of_copied,
collapsible_match,
collapsible_str_replace,
deprecated_cfg_attr,
derivable_impls,
err_expect,
filter_map_next,
from_over_into,
if_then_some_else_none,
index_refutable_slice,
iter_kv_map,
legacy_numeric_constants,
manual_bits,
manual_c_str_literals,
manual_clamp,
manual_hash_one,
manual_is_ascii_check,
manual_let_else,
manual_non_exhaustive,
manual_pattern_char_comparison,
manual_range_contains,
manual_rem_euclid,
manual_retain,
manual_split_once,
manual_str_repeat,
manual_strip,
manual_try_fold,
map_clone,
map_unwrap_or,
match_like_matches_macro,
mem_replace_with_default,
missing_const_for_fn,
needless_borrow,
option_as_ref_deref,
option_map_unwrap_or,
ptr_as_ptr,
redundant_field_names,
redundant_static_lifetimes,
seek_from_current,
seek_rewind,
transmute_ptr_to_ref,
tuple_array_conversions,
type_repetition_in_bounds,
unchecked_duration_subtraction,
uninlined_format_args,
unnecessary_lazy_evaluations,
unnested_or_patterns,
use_self,
)]
msrv: Msrv = Msrv::empty(),
/// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
#[lints(large_types_passed_by_value)]
pass_by_value_size_limit: u64 = 256,
/// Lint "public" fields in a struct that are prefixed with an underscore based on their
/// exported visibility, or whether they are marked as "pub".
#[lints(pub_underscore_fields)]
pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported,
/// Whether to lint only if it's multiline.
#[lints(semicolon_inside_block)]
semicolon_inside_block_ignore_singleline: bool = false,
/// Whether to lint only if it's singleline.
#[lints(semicolon_outside_block)]
semicolon_outside_block_ignore_multiline: bool = false,
/// The maximum number of single char bindings a scope may have
#[lints(many_single_char_names)]
single_char_binding_names_threshold: u64 = 4,
/// The maximum allowed stack size for functions in bytes
#[lints(large_stack_frames)]
stack_size_threshold: u64 = 512_000,
/// Enforce the named macros always use the braces specified.
///
/// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
/// could be used with a full path two `MacroMatcher`s have to be added one with the full path
/// `crate_name::macro_name` and one with just the macro name.
#[lints(nonstandard_macro_braces)]
standard_macro_braces: Vec<MacroMatcher> = Vec::new(),
/// The minimum number of struct fields for the lints about field names to trigger
#[lints(struct_field_names)]
struct_field_name_threshold: u64 = 3,
/// Whether to suppress a restriction lint in constant code. In same
/// cases the restructured operation might not be unavoidable, as the
/// suggested counterparts are unavailable in constant code. This
/// configuration will cause restriction lints to trigger even
/// if no suggestion can be made.
#[lints(indexing_slicing)]
suppress_restriction_lint_in_const: bool = false,
/// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
#[lints(boxed_local, useless_vec)]
too_large_for_stack: u64 = 200,
/// The maximum number of argument a function or method can have
#[lints(too_many_arguments)]
too_many_arguments_threshold: u64 = 7,
/// The maximum number of lines a function or method can have
#[lints(too_many_lines)]
too_many_lines_threshold: u64 = 100,
/// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by
/// reference. By default there is no limit
#[default_text = "target_pointer_width * 2"]
#[lints(trivially_copy_pass_by_ref)]
trivial_copy_size_limit: Option<u64> = None,
/// The maximum complexity a type can have
#[lints(type_complexity)]
type_complexity_threshold: u64 = 250,
/// The byte size a `T` in `Box<T>` can have, below which it triggers the `clippy::unnecessary_box` lint
#[lints(unnecessary_box_returns)]
unnecessary_box_size: u64 = 128,
/// Should the fraction of a decimal be linted to include separators.
#[lints(unreadable_literal)]
unreadable_literal_lint_fractions: bool = true,
/// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
#[lints(upper_case_acronyms)]
upper_case_acronyms_aggressive: bool = false,
/// The size of the boxed type in bytes, where boxing in a `Vec` is allowed
#[lints(vec_box)]
vec_box_size_threshold: u64 = 4096,
/// The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
#[lints(verbose_bit_mask)]
verbose_bit_mask_threshold: u64 = 1,
/// Whether to allow certain wildcard imports (prelude, super in tests).
#[lints(wildcard_imports)]
warn_on_all_wildcard_imports: bool = false,
/// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros. /// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
(warn_unsafe_macro_metavars_in_private_macros: bool = false), #[lints(macro_metavars_in_unsafe)]
warn_unsafe_macro_metavars_in_private_macros: bool = false,
} }
/// Search for the configuration file. /// Search for the configuration file.

View file

@ -1,5 +1,4 @@
#![feature(rustc_private, let_chains)] #![feature(rustc_private, array_windows, let_chains)]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![warn( #![warn(
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,

View file

@ -1,11 +1,12 @@
use std::fmt::{self, Write}; use itertools::Itertools;
use std::fmt;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ClippyConfiguration { pub struct ClippyConfiguration {
pub name: String, pub name: String,
pub default: String, pub default: String,
pub lints: Vec<String>, pub lints: &'static [&'static str],
pub doc: String, pub doc: &'static str,
pub deprecation_reason: Option<&'static str>, pub deprecation_reason: Option<&'static str>,
} }
@ -13,61 +14,23 @@ impl fmt::Display for ClippyConfiguration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "- `{}`: {}", self.name, self.doc)?; write!(f, "- `{}`: {}", self.name, self.doc)?;
if !self.default.is_empty() { if !self.default.is_empty() {
write!(f, " (default: `{}`)", self.default)?; write!(f, "\n\n (default: `{}`)", self.default)?;
} }
Ok(()) Ok(())
} }
} }
impl ClippyConfiguration { impl ClippyConfiguration {
pub fn new(
name: &'static str,
default: String,
doc_comment: &'static str,
deprecation_reason: Option<&'static str>,
) -> Self {
let (mut lints, doc) = parse_config_field_doc(doc_comment)
.unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
lints.sort();
Self {
name: to_kebab(name),
lints,
doc,
default,
deprecation_reason,
}
}
pub fn to_markdown_paragraph(&self) -> String { pub fn to_markdown_paragraph(&self) -> String {
let mut out = format!( format!(
"## `{}`\n{}\n\n", "## `{}`\n{}\n\n**Default Value:** `{}`\n\n---\n**Affected lints:**\n{}\n\n",
self.name, self.name,
self.doc self.doc.lines().map(|x| x.strip_prefix(' ').unwrap_or(x)).join("\n"),
.lines() self.default,
.map(|line| line.strip_prefix(" ").unwrap_or(line)) self.lints.iter().format_with("\n", |name, f| f(&format_args!(
.collect::<Vec<_>>() "* [`{name}`](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"
.join("\n"), ))),
);
if !self.default.is_empty() {
write!(out, "**Default Value:** `{}`\n\n", self.default).unwrap();
}
write!(
out,
"---\n**Affected lints:**\n{}\n\n",
self.lints
.iter()
.map(|name| name.to_string().split_whitespace().next().unwrap().to_string())
.map(|name| format!("* [`{name}`](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"))
.collect::<Vec<_>>()
.join("\n"),
) )
.unwrap();
out
} }
pub fn to_markdown_link(&self) -> String { pub fn to_markdown_link(&self) -> String {
@ -75,47 +38,3 @@ impl ClippyConfiguration {
format!("[`{}`]: {BOOK_CONFIGS_PATH}#{}", self.name, self.name) format!("[`{}`]: {BOOK_CONFIGS_PATH}#{}", self.name, self.name)
} }
} }
/// This parses the field documentation of the config struct.
///
/// ```rust, ignore
/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
/// ```
///
/// Would yield:
/// ```rust, ignore
/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
/// ```
fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
const DOC_START: &str = " Lint: ";
if doc_comment.starts_with(DOC_START)
&& let Some(split_pos) = doc_comment.find('.')
{
let mut doc_comment = doc_comment.to_string();
let mut documentation = doc_comment.split_off(split_pos);
// Extract lints
doc_comment.make_ascii_lowercase();
let lints: Vec<String> = doc_comment
.split_off(DOC_START.len())
.lines()
.next()
.unwrap()
.split(", ")
.map(str::to_string)
.collect();
// Format documentation correctly
// split off leading `.` from lint name list and indent for correct formatting
documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");
Some((lints, documentation))
} else {
None
}
}
/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
fn to_kebab(config_name: &str) -> String {
config_name.replace('_', "-")
}

View file

@ -13,9 +13,6 @@ opener = "0.6"
shell-escape = "0.1" shell-escape = "0.1"
walkdir = "2.3" walkdir = "2.3"
[features]
deny-warnings = []
[package.metadata.rust-analyzer] [package.metadata.rust-analyzer]
# This package uses #[feature(rustc_private)] # This package uses #[feature(rustc_private)]
rustc_private = true rustc_private = true

View file

@ -1,30 +1,65 @@
use crate::clippy_project_root; use crate::clippy_project_root;
use itertools::Itertools; use itertools::Itertools;
use rustc_lexer::{tokenize, TokenKind};
use shell_escape::escape; use shell_escape::escape;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::path::Path; use std::ops::ControlFlow;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio}; use std::process::{self, Command, Stdio};
use std::{fs, io}; use std::{fs, io};
use walkdir::WalkDir; use walkdir::WalkDir;
#[derive(Debug)] pub enum Error {
pub enum CliError {
CommandFailed(String, String), CommandFailed(String, String),
IoError(io::Error), Io(io::Error),
RustfmtNotInstalled, RustfmtNotInstalled,
WalkDirError(walkdir::Error), WalkDir(walkdir::Error),
IntellijSetupActive, IntellijSetupActive,
Parse(PathBuf, usize, String),
CheckFailed,
} }
impl From<io::Error> for CliError { impl From<io::Error> for Error {
fn from(error: io::Error) -> Self { fn from(error: io::Error) -> Self {
Self::IoError(error) Self::Io(error)
} }
} }
impl From<walkdir::Error> for CliError { impl From<walkdir::Error> for Error {
fn from(error: walkdir::Error) -> Self { fn from(error: walkdir::Error) -> Self {
Self::WalkDirError(error) Self::WalkDir(error)
}
}
impl Error {
fn display(&self) {
match self {
Self::CheckFailed => {
eprintln!("Formatting check failed!\nRun `cargo dev fmt` to update.");
},
Self::CommandFailed(command, stderr) => {
eprintln!("error: command `{command}` failed!\nstderr: {stderr}");
},
Self::Io(err) => {
eprintln!("error: {err}");
},
Self::RustfmtNotInstalled => {
eprintln!("error: rustfmt nightly is not installed.");
},
Self::WalkDir(err) => {
eprintln!("error: {err}");
},
Self::IntellijSetupActive => {
eprintln!(
"error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.\n\
Not formatting because that would format the local repo as well!\n\
Please revert the changes to `Cargo.toml`s with `cargo dev remove intellij`."
);
},
Self::Parse(path, line, msg) => {
eprintln!("error parsing `{}:{line}`: {msg}", path.display());
},
}
} }
} }
@ -34,75 +69,244 @@ struct FmtContext {
rustfmt_path: String, rustfmt_path: String,
} }
struct ClippyConf<'a> {
name: &'a str,
attrs: &'a str,
lints: Vec<&'a str>,
field: &'a str,
}
fn offset_to_line(text: &str, offset: usize) -> usize {
match text.split('\n').try_fold((1usize, 0usize), |(line, pos), s| {
let pos = pos + s.len() + 1;
if pos > offset {
ControlFlow::Break(line)
} else {
ControlFlow::Continue((line + 1, pos))
}
}) {
ControlFlow::Break(x) | ControlFlow::Continue((x, _)) => x,
}
}
/// Formats the configuration list in `clippy_config/src/conf.rs`
#[expect(clippy::too_many_lines)]
fn fmt_conf(check: bool) -> Result<(), Error> {
#[derive(Clone, Copy)]
enum State {
Start,
Docs,
Pound,
OpenBracket,
Attr(u32),
Lints,
EndLints,
Field,
}
let path: PathBuf = [
clippy_project_root().as_path(),
"clippy_config".as_ref(),
"src".as_ref(),
"conf.rs".as_ref(),
]
.into_iter()
.collect();
let text = fs::read_to_string(&path)?;
let (pre, conf) = text
.split_once("define_Conf! {\n")
.expect("can't find config definition");
let (conf, post) = conf.split_once("\n}\n").expect("can't find config definition");
let conf_offset = pre.len() + 15;
let mut pos = 0u32;
let mut attrs_start = 0;
let mut attrs_end = 0;
let mut field_start = 0;
let mut lints = Vec::new();
let mut name = "";
let mut fields = Vec::new();
let mut state = State::Start;
for (i, t) in tokenize(conf)
.map(|x| {
let start = pos;
pos += x.len;
(start as usize, x)
})
.filter(|(_, t)| !matches!(t.kind, TokenKind::Whitespace))
{
match (state, t.kind) {
(State::Start, TokenKind::LineComment { doc_style: Some(_) }) => {
attrs_start = i;
attrs_end = i + t.len as usize;
state = State::Docs;
},
(State::Start, TokenKind::Pound) => {
attrs_start = i;
attrs_end = i;
state = State::Pound;
},
(State::Docs, TokenKind::LineComment { doc_style: Some(_) }) => attrs_end = i + t.len as usize,
(State::Docs, TokenKind::Pound) => state = State::Pound,
(State::Pound, TokenKind::OpenBracket) => state = State::OpenBracket,
(State::OpenBracket, TokenKind::Ident) => {
state = if conf[i..i + t.len as usize] == *"lints" {
State::Lints
} else {
State::Attr(0)
};
},
(State::Attr(0), TokenKind::CloseBracket) => {
attrs_end = i + 1;
state = State::Docs;
},
(State::Attr(x), TokenKind::OpenParen | TokenKind::OpenBracket | TokenKind::OpenBrace) => {
state = State::Attr(x + 1);
},
(State::Attr(x), TokenKind::CloseParen | TokenKind::CloseBracket | TokenKind::CloseBrace) => {
state = State::Attr(x - 1);
},
(State::Lints, TokenKind::Ident) => lints.push(&conf[i..i + t.len as usize]),
(State::Lints, TokenKind::CloseBracket) => state = State::EndLints,
(State::EndLints | State::Docs, TokenKind::Ident) => {
field_start = i;
name = &conf[i..i + t.len as usize];
state = State::Field;
},
(State::Field, TokenKind::LineComment { doc_style: Some(_) }) => {
#[expect(clippy::drain_collect)]
fields.push(ClippyConf {
name,
lints: lints.drain(..).collect(),
attrs: &conf[attrs_start..attrs_end],
field: conf[field_start..i].trim_end(),
});
attrs_start = i;
attrs_end = i + t.len as usize;
state = State::Docs;
},
(State::Field, TokenKind::Pound) => {
#[expect(clippy::drain_collect)]
fields.push(ClippyConf {
name,
lints: lints.drain(..).collect(),
attrs: &conf[attrs_start..attrs_end],
field: conf[field_start..i].trim_end(),
});
attrs_start = i;
attrs_end = i;
state = State::Pound;
},
(State::Field | State::Attr(_), _)
| (State::Lints, TokenKind::Comma | TokenKind::OpenParen | TokenKind::CloseParen) => {},
_ => {
return Err(Error::Parse(
path,
offset_to_line(&text, conf_offset + i),
format!("unexpected token `{}`", &conf[i..i + t.len as usize]),
));
},
}
}
if !matches!(state, State::Field) {
return Err(Error::Parse(
path,
offset_to_line(&text, conf_offset + conf.len()),
"incomplete field".into(),
));
}
fields.push(ClippyConf {
name,
lints,
attrs: &conf[attrs_start..attrs_end],
field: conf[field_start..].trim_end(),
});
for field in &mut fields {
field.lints.sort_unstable();
}
fields.sort_by_key(|x| x.name);
let new_text = format!(
"{pre}define_Conf! {{\n{}}}\n{post}",
fields.iter().format_with("", |field, f| {
if field.lints.is_empty() {
f(&format_args!(" {}\n {}\n", field.attrs, field.field))
} else if field.lints.iter().map(|x| x.len() + 2).sum::<usize>() < 120 - 14 {
f(&format_args!(
" {}\n #[lints({})]\n {}\n",
field.attrs,
field.lints.iter().join(", "),
field.field,
))
} else {
f(&format_args!(
" {}\n #[lints({}\n )]\n {}\n",
field.attrs,
field
.lints
.iter()
.format_with("", |x, f| f(&format_args!("\n {x},"))),
field.field,
))
}
})
);
if text != new_text {
if check {
return Err(Error::CheckFailed);
}
fs::write(&path, new_text.as_bytes())?;
}
Ok(())
}
fn run_rustfmt(context: &FmtContext) -> Result<(), Error> {
let project_root = clippy_project_root();
// if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
// format because rustfmt would also format the entire rustc repo as it is a local
// dependency
if fs::read_to_string(project_root.join("Cargo.toml"))
.expect("Failed to read clippy Cargo.toml")
.contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
{
return Err(Error::IntellijSetupActive);
}
check_for_rustfmt(context)?;
cargo_fmt(context, project_root.as_path())?;
cargo_fmt(context, &project_root.join("clippy_dev"))?;
cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
cargo_fmt(context, &project_root.join("lintcheck"))?;
let chunks = WalkDir::new(project_root.join("tests"))
.into_iter()
.filter_map(|entry| {
let entry = entry.expect("failed to find tests");
let path = entry.path();
if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" {
None
} else {
Some(entry.into_path().into_os_string())
}
})
.chunks(250);
for chunk in &chunks {
rustfmt(context, chunk)?;
}
Ok(())
}
// the "main" function of cargo dev fmt // the "main" function of cargo dev fmt
pub fn run(check: bool, verbose: bool) { pub fn run(check: bool, verbose: bool) {
fn try_run(context: &FmtContext) -> Result<bool, CliError> {
let mut success = true;
let project_root = clippy_project_root();
// if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
// format because rustfmt would also format the entire rustc repo as it is a local
// dependency
if fs::read_to_string(project_root.join("Cargo.toml"))
.expect("Failed to read clippy Cargo.toml")
.contains("[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
{
return Err(CliError::IntellijSetupActive);
}
rustfmt_test(context)?;
success &= cargo_fmt(context, project_root.as_path())?;
success &= cargo_fmt(context, &project_root.join("clippy_dev"))?;
success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
success &= cargo_fmt(context, &project_root.join("lintcheck"))?;
let chunks = WalkDir::new(project_root.join("tests"))
.into_iter()
.filter_map(|entry| {
let entry = entry.expect("failed to find tests");
let path = entry.path();
if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" {
None
} else {
Some(entry.into_path().into_os_string())
}
})
.chunks(250);
for chunk in &chunks {
success &= rustfmt(context, chunk)?;
}
Ok(success)
}
fn output_err(err: CliError) {
match err {
CliError::CommandFailed(command, stderr) => {
eprintln!("error: A command failed! `{command}`\nstderr: {stderr}");
},
CliError::IoError(err) => {
eprintln!("error: {err}");
},
CliError::RustfmtNotInstalled => {
eprintln!("error: rustfmt nightly is not installed.");
},
CliError::WalkDirError(err) => {
eprintln!("error: {err}");
},
CliError::IntellijSetupActive => {
eprintln!(
"error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.
Not formatting because that would format the local repo as well!
Please revert the changes to Cargo.tomls with `cargo dev remove intellij`."
);
},
}
}
let output = Command::new("rustup") let output = Command::new("rustup")
.args(["which", "rustfmt"]) .args(["which", "rustfmt"])
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
@ -120,21 +324,10 @@ Please revert the changes to Cargo.tomls with `cargo dev remove intellij`."
verbose, verbose,
rustfmt_path, rustfmt_path,
}; };
let result = try_run(&context); if let Err(e) = run_rustfmt(&context).and_then(|()| fmt_conf(check)) {
let code = match result { e.display();
Ok(true) => 0, process::exit(1);
Ok(false) => { }
eprintln!();
eprintln!("Formatting check failed.");
eprintln!("Run `cargo dev fmt` to update formatting.");
1
},
Err(err) => {
output_err(err);
1
},
};
process::exit(code);
} }
fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String { fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
@ -148,12 +341,12 @@ fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[imp
) )
} }
fn exec( fn exec_fmt_command(
context: &FmtContext, context: &FmtContext,
program: impl AsRef<OsStr>, program: impl AsRef<OsStr>,
dir: impl AsRef<Path>, dir: impl AsRef<Path>,
args: &[impl AsRef<OsStr>], args: &[impl AsRef<OsStr>],
) -> Result<bool, CliError> { ) -> Result<(), Error> {
if context.verbose { if context.verbose {
println!("{}", format_command(&program, &dir, args)); println!("{}", format_command(&program, &dir, args));
} }
@ -166,28 +359,28 @@ fn exec(
.unwrap(); .unwrap();
let success = output.status.success(); let success = output.status.success();
if !context.check && !success { match (context.check, success) {
let stderr = std::str::from_utf8(&output.stderr).unwrap_or(""); (_, true) => Ok(()),
return Err(CliError::CommandFailed( (true, false) => Err(Error::CheckFailed),
format_command(&program, &dir, args), (false, false) => {
String::from(stderr), let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
)); Err(Error::CommandFailed(
format_command(&program, &dir, args),
String::from(stderr),
))
},
} }
Ok(success)
} }
fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> { fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<(), Error> {
let mut args = vec!["fmt", "--all"]; let mut args = vec!["fmt", "--all"];
if context.check { if context.check {
args.push("--check"); args.push("--check");
} }
let success = exec(context, "cargo", path, &args)?; exec_fmt_command(context, "cargo", path, &args)
Ok(success)
} }
fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> { fn check_for_rustfmt(context: &FmtContext) -> Result<(), Error> {
let program = "rustfmt"; let program = "rustfmt";
let dir = std::env::current_dir()?; let dir = std::env::current_dir()?;
let args = &["--version"]; let args = &["--version"];
@ -204,23 +397,20 @@ fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> {
.unwrap_or("") .unwrap_or("")
.starts_with("error: 'rustfmt' is not installed") .starts_with("error: 'rustfmt' is not installed")
{ {
Err(CliError::RustfmtNotInstalled) Err(Error::RustfmtNotInstalled)
} else { } else {
Err(CliError::CommandFailed( Err(Error::CommandFailed(
format_command(program, &dir, args), format_command(program, &dir, args),
std::str::from_utf8(&output.stderr).unwrap_or("").to_string(), std::str::from_utf8(&output.stderr).unwrap_or("").to_string(),
)) ))
} }
} }
fn rustfmt(context: &FmtContext, paths: impl Iterator<Item = OsString>) -> Result<bool, CliError> { fn rustfmt(context: &FmtContext, paths: impl Iterator<Item = OsString>) -> Result<(), Error> {
let mut args = Vec::new(); let mut args = Vec::new();
if context.check { if context.check {
args.push(OsString::from("--check")); args.push(OsString::from("--check"));
} }
args.extend(paths); args.extend(paths);
exec_fmt_command(context, &context.rustfmt_path, std::env::current_dir()?, &args)
let success = exec(context, &context.rustfmt_path, std::env::current_dir()?, &args)?;
Ok(success)
} }

View file

@ -1,6 +1,5 @@
#![feature(let_chains)] #![feature(let_chains)]
#![feature(rustc_private)] #![feature(rustc_private)]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![warn( #![warn(
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,

View file

@ -1,4 +1,3 @@
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
// warn on lints, that are included in `rust-lang/rust`s bootstrap // warn on lints, that are included in `rust-lang/rust`s bootstrap
#![warn(rust_2018_idioms, unused_lifetimes)] #![warn(rust_2018_idioms, unused_lifetimes)]
@ -74,7 +73,7 @@ fn main() {
new_name, new_name,
uplift, uplift,
} => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift), } => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift),
DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, reason.as_deref()), DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, &reason),
} }
} }
@ -223,7 +222,7 @@ enum DevCommand {
name: String, name: String,
#[arg(long, short)] #[arg(long, short)]
/// The reason for deprecation /// The reason for deprecation
reason: Option<String>, reason: String,
}, },
} }

View file

@ -1,10 +1,14 @@
use std::ffi::OsStr;
use std::num::ParseIntError;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use std::{env, thread}; use std::{env, thread};
#[cfg(windows)]
const PYTHON: &str = "python";
#[cfg(not(windows))]
const PYTHON: &str = "python3";
/// # Panics /// # Panics
/// ///
/// Panics if the python commands could not be spawned /// Panics if the python commands could not be spawned
@ -25,7 +29,7 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
} }
if let Some(url) = url.take() { if let Some(url) = url.take() {
thread::spawn(move || { thread::spawn(move || {
Command::new("python3") Command::new(PYTHON)
.arg("-m") .arg("-m")
.arg("http.server") .arg("http.server")
.arg(port.to_string()) .arg(port.to_string())
@ -58,8 +62,3 @@ fn mtime(path: impl AsRef<Path>) -> SystemTime {
.unwrap_or(SystemTime::UNIX_EPOCH) .unwrap_or(SystemTime::UNIX_EPOCH)
} }
} }
#[allow(clippy::missing_errors_doc)]
pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> {
arg.to_string_lossy().parse::<u16>().map(|_| ())
}

View file

@ -1,13 +1,12 @@
use crate::clippy_project_root; use crate::clippy_project_root;
use aho_corasick::AhoCorasickBuilder; use aho_corasick::AhoCorasickBuilder;
use indoc::writedoc;
use itertools::Itertools; use itertools::Itertools;
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::fs::{self, OpenOptions}; use std::fs::{self, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write as _}; use std::io::{self, Read, Seek, Write as _};
use std::ops::Range; use std::ops::Range;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
@ -77,12 +76,8 @@ fn generate_lint_files(
for lint in usable_lints for lint in usable_lints
.iter() .iter()
.map(|l| &*l.name) .map(|l| &*l.name)
.chain(deprecated_lints.iter().map(|l| &*l.name)) .chain(deprecated_lints.iter().filter_map(|l| l.name.strip_prefix("clippy::")))
.chain( .chain(renamed_lints.iter().filter_map(|l| l.old_name.strip_prefix("clippy::")))
renamed_lints
.iter()
.map(|l| l.old_name.strip_prefix("clippy::").unwrap_or(&l.old_name)),
)
.sorted() .sorted()
{ {
writeln!(res, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap(); writeln!(res, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap();
@ -108,11 +103,6 @@ fn generate_lint_files(
update_mode, update_mode,
&gen_declared_lints(internal_lints.iter(), usable_lints.iter()), &gen_declared_lints(internal_lints.iter(), usable_lints.iter()),
); );
process_file(
"clippy_lints/src/lib.deprecated.rs",
update_mode,
&gen_deprecated(deprecated_lints),
);
let content = gen_deprecated_lints_test(deprecated_lints); let content = gen_deprecated_lints_test(deprecated_lints);
process_file("tests/ui/deprecated.rs", update_mode, &content); process_file("tests/ui/deprecated.rs", update_mode, &content);
@ -205,7 +195,7 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
let ext = f.path().extension(); let ext = f.path().extension();
(ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed"))) (ext == Some(OsStr::new("rs")) || ext == Some(OsStr::new("fixed")))
&& name != Some(OsStr::new("rename.rs")) && name != Some(OsStr::new("rename.rs"))
&& name != Some(OsStr::new("renamed_lints.rs")) && name != Some(OsStr::new("deprecated_lints.rs"))
}) })
{ {
rewrite_file(file.path(), |s| { rewrite_file(file.path(), |s| {
@ -213,6 +203,19 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
}); });
} }
let version = crate::new_lint::get_stabilization_version();
rewrite_file(Path::new("clippy_lints/src/deprecated_lints.rs"), |s| {
insert_at_marker(
s,
"// end renamed lints. used by `cargo dev rename_lint`",
&format!(
"#[clippy::version = \"{version}\"]\n \
(\"{}\", \"{}\"),\n ",
lint.old_name, lint.new_name,
),
)
});
renamed_lints.push(lint); renamed_lints.push(lint);
renamed_lints.sort_by(|lhs, rhs| { renamed_lints.sort_by(|lhs, rhs| {
lhs.new_name lhs.new_name
@ -222,11 +225,6 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
.then_with(|| lhs.old_name.cmp(&rhs.old_name)) .then_with(|| lhs.old_name.cmp(&rhs.old_name))
}); });
write_file(
Path::new("clippy_lints/src/renamed_lints.rs"),
&gen_renamed_lints_list(&renamed_lints),
);
if uplift { if uplift {
write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints)); write_file(Path::new("tests/ui/rename.rs"), &gen_renamed_lints_test(&renamed_lints));
println!( println!(
@ -293,7 +291,8 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
// Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being // Don't change `clippy_utils/src/renamed_lints.rs` here as it would try to edit the lint being
// renamed. // renamed.
for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("renamed_lints.rs")) { for (_, file) in clippy_lints_src_files().filter(|(rel_path, _)| rel_path != OsStr::new("deprecated_lints.rs"))
{
rewrite_file(file.path(), |s| replace_ident_like(s, replacements)); rewrite_file(file.path(), |s| replace_ident_like(s, replacements));
} }
@ -304,7 +303,6 @@ pub fn rename(old_name: &str, new_name: &str, uplift: bool) {
println!("note: `cargo uitest` still needs to be run to update the test results"); println!("note: `cargo uitest` still needs to be run to update the test results");
} }
const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note";
/// Runs the `deprecate` command /// Runs the `deprecate` command
/// ///
/// This does the following: /// This does the following:
@ -314,33 +312,16 @@ const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note";
/// # Panics /// # Panics
/// ///
/// If a file path could not read from or written to /// If a file path could not read from or written to
pub fn deprecate(name: &str, reason: Option<&str>) { pub fn deprecate(name: &str, reason: &str) {
fn finish( let prefixed_name = if name.starts_with("clippy::") {
(lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>), name.to_owned()
name: &str, } else {
reason: &str, format!("clippy::{name}")
) { };
deprecated_lints.push(DeprecatedLint { let stripped_name = &prefixed_name[8..];
name: name.to_string(),
reason: reason.to_string(),
declaration_range: Range::default(),
});
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints); let (mut lints, mut deprecated_lints, renamed_lints) = gather_all();
println!("info: `{name}` has successfully been deprecated"); let Some(lint) = lints.iter().find(|l| l.name == stripped_name) else {
if reason == DEFAULT_DEPRECATION_REASON {
println!("note: the deprecation reason must be updated in `clippy_lints/src/deprecated_lints.rs`");
}
println!("note: you must run `cargo uitest` to update the test results");
}
let reason = reason.unwrap_or(DEFAULT_DEPRECATION_REASON);
let name_lower = name.to_lowercase();
let name_upper = name.to_uppercase();
let (mut lints, deprecated_lints, renamed_lints) = gather_all();
let Some(lint) = lints.iter().find(|l| l.name == name_lower) else {
eprintln!("error: failed to find lint `{name}`"); eprintln!("error: failed to find lint `{name}`");
return; return;
}; };
@ -357,13 +338,27 @@ pub fn deprecate(name: &str, reason: Option<&str>) {
let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs"); let deprecated_lints_path = &*clippy_project_root().join("clippy_lints/src/deprecated_lints.rs");
if remove_lint_declaration(&name_lower, &mod_path, &mut lints).unwrap_or(false) { if remove_lint_declaration(stripped_name, &mod_path, &mut lints).unwrap_or(false) {
declare_deprecated(&name_upper, deprecated_lints_path, reason).unwrap(); let version = crate::new_lint::get_stabilization_version();
finish((lints, deprecated_lints, renamed_lints), name, reason); rewrite_file(deprecated_lints_path, |s| {
return; insert_at_marker(
} s,
"// end deprecated lints. used by `cargo dev deprecate_lint`",
&format!("#[clippy::version = \"{version}\"]\n (\"{prefixed_name}\", \"{reason}\"),\n ",),
)
});
eprintln!("error: lint not found"); deprecated_lints.push(DeprecatedLint {
name: prefixed_name,
reason: reason.into(),
});
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{name}` has successfully been deprecated");
println!("note: you must run `cargo uitest` to update the test results");
} else {
eprintln!("error: lint not found");
}
} }
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> { fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
@ -377,14 +372,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
// Some lints have their own directories, delete them // Some lints have their own directories, delete them
if path.is_dir() { if path.is_dir() {
fs::remove_dir_all(path).ok(); let _ = fs::remove_dir_all(path);
return; return;
} }
// Remove all related test files // Remove all related test files
fs::remove_file(path.with_extension("rs")).ok(); let _ = fs::remove_file(path.with_extension("rs"));
fs::remove_file(path.with_extension("stderr")).ok(); let _ = fs::remove_file(path.with_extension("stderr"));
fs::remove_file(path.with_extension("fixed")).ok(); let _ = fs::remove_file(path.with_extension("fixed"));
} }
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) { fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
@ -427,7 +422,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
lint_mod_path.set_file_name(name); lint_mod_path.set_file_name(name);
lint_mod_path.set_extension("rs"); lint_mod_path.set_extension("rs");
fs::remove_file(lint_mod_path).ok(); let _ = fs::remove_file(lint_mod_path);
} }
let mut content = let mut content =
@ -465,37 +460,6 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
Ok(false) Ok(false)
} }
fn declare_deprecated(name: &str, path: &Path, reason: &str) -> io::Result<()> {
let mut file = OpenOptions::new().write(true).open(path)?;
file.seek(SeekFrom::End(0))?;
let version = crate::new_lint::get_stabilization_version();
let deprecation_reason = if reason == DEFAULT_DEPRECATION_REASON {
"TODO"
} else {
reason
};
writedoc!(
file,
"
declare_deprecated_lint! {{
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// {deprecation_reason}
#[clippy::version = \"{version}\"]
pub {name},
\"{reason}\"
}}
"
)
}
/// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there /// Replace substrings if they aren't bordered by identifier characters. Returns `None` if there
/// were no replacements. /// were no replacements.
fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> { fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option<String> {
@ -604,14 +568,12 @@ impl Lint {
struct DeprecatedLint { struct DeprecatedLint {
name: String, name: String,
reason: String, reason: String,
declaration_range: Range<usize>,
} }
impl DeprecatedLint { impl DeprecatedLint {
fn new(name: &str, reason: &str, declaration_range: Range<usize>) -> Self { fn new(name: &str, reason: &str) -> Self {
Self { Self {
name: name.to_lowercase(), name: remove_line_splices(name),
reason: remove_line_splices(reason), reason: remove_line_splices(reason),
declaration_range,
} }
} }
} }
@ -629,28 +591,6 @@ impl RenamedLint {
} }
} }
/// Generates the `register_removed` code
#[must_use]
fn gen_deprecated(lints: &[DeprecatedLint]) -> String {
let mut output = GENERATED_FILE_COMMENT.to_string();
output.push_str("{\n");
for lint in lints {
let _: fmt::Result = write!(
output,
concat!(
" store.register_removed(\n",
" \"clippy::{}\",\n",
" \"{}\",\n",
" );\n"
),
lint.name, lint.reason,
);
}
output.push_str("}\n");
output
}
/// Generates the code for registering lints /// Generates the code for registering lints
#[must_use] #[must_use]
fn gen_declared_lints<'a>( fn gen_declared_lints<'a>(
@ -680,7 +620,7 @@ fn gen_declared_lints<'a>(
fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String { fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
let mut res: String = GENERATED_FILE_COMMENT.into(); let mut res: String = GENERATED_FILE_COMMENT.into();
for lint in lints { for lint in lints {
writeln!(res, "#![warn(clippy::{})]", lint.name).unwrap(); writeln!(res, "#![warn({})] //~ ERROR: lint `{}`", lint.name, lint.name).unwrap();
} }
res.push_str("\nfn main() {}\n"); res.push_str("\nfn main() {}\n");
res res
@ -699,27 +639,13 @@ fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
seen_lints.clear(); seen_lints.clear();
for lint in lints { for lint in lints {
if seen_lints.insert(&lint.old_name) { if seen_lints.insert(&lint.old_name) {
writeln!(res, "#![warn({})]", lint.old_name).unwrap(); writeln!(res, "#![warn({})] //~ ERROR: lint `{}`", lint.old_name, lint.old_name).unwrap();
} }
} }
res.push_str("\nfn main() {}\n"); res.push_str("\nfn main() {}\n");
res res
} }
fn gen_renamed_lints_list(lints: &[RenamedLint]) -> String {
const HEADER: &str = "\
// This file is managed by `cargo dev rename_lint`. Prefer using that when possible.\n\n\
#[rustfmt::skip]\n\
pub static RENAMED_LINTS: &[(&str, &str)] = &[\n";
let mut res = String::from(HEADER);
for lint in lints {
writeln!(res, " (\"{}\", \"{}\"),", lint.old_name, lint.new_name).unwrap();
}
res.push_str("];\n");
res
}
/// Gathers all lints defined in `clippy_lints/src` /// Gathers all lints defined in `clippy_lints/src`
fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) { fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
let mut lints = Vec::with_capacity(1000); let mut lints = Vec::with_capacity(1000);
@ -744,10 +670,10 @@ fn gather_all() -> (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>) {
module.strip_suffix(".rs").unwrap_or(&module) module.strip_suffix(".rs").unwrap_or(&module)
}; };
match module { if module == "deprecated_lints" {
"deprecated_lints" => parse_deprecated_contents(&contents, &mut deprecated_lints), parse_deprecated_contents(&contents, &mut deprecated_lints, &mut renamed_lints);
"renamed_lints" => parse_renamed_contents(&contents, &mut renamed_lints), } else {
_ => parse_contents(&contents, module, &mut lints), parse_contents(&contents, module, &mut lints);
} }
} }
(lints, deprecated_lints, renamed_lints) (lints, deprecated_lints, renamed_lints)
@ -848,54 +774,37 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
} }
/// Parse a source file looking for `declare_deprecated_lint` macro invocations. /// Parse a source file looking for `declare_deprecated_lint` macro invocations.
fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) { fn parse_deprecated_contents(contents: &str, deprecated: &mut Vec<DeprecatedLint>, renamed: &mut Vec<RenamedLint>) {
let mut offset = 0usize; let Some((_, contents)) = contents.split_once("\ndeclare_with_version! { DEPRECATED") else {
let mut iter = tokenize(contents).map(|t| { return;
let range = offset..offset + t.len as usize; };
offset = range.end; let Some((deprecated_src, renamed_src)) = contents.split_once("\ndeclare_with_version! { RENAMED") else {
return;
};
LintDeclSearchResult { for line in deprecated_src.lines() {
token_kind: t.kind, let mut offset = 0usize;
content: &contents[range.clone()], let mut iter = tokenize(line).map(|t| {
range, let range = offset..offset + t.len as usize;
} offset = range.end;
});
while let Some(LintDeclSearchResult { range, .. }) = iter.find( LintDeclSearchResult {
|LintDeclSearchResult { token_kind: t.kind,
token_kind, content, .. content: &line[range.clone()],
}| token_kind == &TokenKind::Ident && *content == "declare_deprecated_lint", range,
) { }
let start = range.start;
let mut iter = iter.by_ref().filter(|LintDeclSearchResult { ref token_kind, .. }| {
!matches!(token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })
}); });
let (name, reason) = match_tokens!( let (name, reason) = match_tokens!(
iter, iter,
// !{ // ("old_name",
Bang OpenBrace Whitespace OpenParen Literal{kind: LiteralKind::Str{..},..}(name) Comma
// #[clippy::version = "version"] // "new_name"),
Pound OpenBracket Ident Colon Colon Ident Eq Literal{..} CloseBracket Whitespace Literal{kind: LiteralKind::Str{..},..}(reason) CloseParen Comma
// pub LINT_NAME,
Ident Ident(name) Comma
// "description"
Literal{kind: LiteralKind::Str{..},..}(reason)
); );
deprecated.push(DeprecatedLint::new(name, reason));
if let Some(LintDeclSearchResult {
token_kind: TokenKind::CloseBrace,
range,
..
}) = iter.next()
{
lints.push(DeprecatedLint::new(name, reason, start..range.end));
}
} }
} for line in renamed_src.lines() {
fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
for line in contents.lines() {
let mut offset = 0usize; let mut offset = 0usize;
let mut iter = tokenize(line).map(|t| { let mut iter = tokenize(line).map(|t| {
let range = offset..offset + t.len as usize; let range = offset..offset + t.len as usize;
@ -915,7 +824,7 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
// "new_name"), // "new_name"),
Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma Whitespace Literal{kind: LiteralKind::Str{..},..}(new_name) CloseParen Comma
); );
lints.push(RenamedLint::new(old_name, new_name)); renamed.push(RenamedLint::new(old_name, new_name));
} }
} }
@ -1015,6 +924,12 @@ fn panic_file(error: io::Error, name: &Path, action: &str) -> ! {
panic!("failed to {action} file `{}`: {error}", name.display()) panic!("failed to {action} file `{}`: {error}", name.display())
} }
fn insert_at_marker(text: &str, marker: &str, new_text: &str) -> Option<String> {
let i = text.find(marker)?;
let (pre, post) = text.split_at(i);
Some([pre, new_text, post].into_iter().collect())
}
fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) { fn rewrite_file(path: &Path, f: impl FnOnce(&str) -> Option<String>) {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.write(true) .write(true)
@ -1084,31 +999,6 @@ mod tests {
assert_eq!(expected, result); assert_eq!(expected, result);
} }
#[test]
fn test_parse_deprecated_contents() {
static DEPRECATED_CONTENTS: &str = r#"
/// some doc comment
declare_deprecated_lint! {
#[clippy::version = "I'm a version"]
pub SHOULD_ASSERT_EQ,
"`assert!()` will be more flexible with RFC 2011"
}
"#;
let mut result = Vec::new();
parse_deprecated_contents(DEPRECATED_CONTENTS, &mut result);
for r in &mut result {
r.declaration_range = Range::default();
}
let expected = vec![DeprecatedLint::new(
"should_assert_eq",
"\"`assert!()` will be more flexible with RFC 2011\"",
Range::default(),
)];
assert_eq!(expected, result);
}
#[test] #[test]
fn test_usable_lints() { fn test_usable_lints() {
let lints = vec![ let lints = vec![
@ -1177,34 +1067,4 @@ mod tests {
); );
assert_eq!(expected, Lint::by_lint_group(lints.into_iter())); assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
} }
#[test]
fn test_gen_deprecated() {
let lints = vec![
DeprecatedLint::new(
"should_assert_eq",
"\"has been superseded by should_assert_eq2\"",
Range::default(),
),
DeprecatedLint::new("another_deprecated", "\"will be removed\"", Range::default()),
];
let expected = GENERATED_FILE_COMMENT.to_string()
+ &[
"{",
" store.register_removed(",
" \"clippy::should_assert_eq\",",
" \"has been superseded by should_assert_eq2\",",
" );",
" store.register_removed(",
" \"clippy::another_deprecated\",",
" \"will be removed\",",
" );",
"}",
]
.join("\n")
+ "\n";
assert_eq!(expected, gen_deprecated(&lints));
}
} }

View file

@ -32,7 +32,6 @@ url = "2.2"
walkdir = "2.3" walkdir = "2.3"
[features] [features]
deny-warnings = ["clippy_config/deny-warnings", "clippy_utils/deny-warnings"]
# build clippy with internal lints enabled, off by default # build clippy with internal lints enabled, off by default
internal = ["serde_json", "tempfile", "regex"] internal = ["serde_json", "tempfile", "regex"]

View file

@ -97,7 +97,7 @@ impl ApproxConstant {
cx, cx,
APPROX_CONSTANT, APPROX_CONSTANT,
e.span, e.span,
format!("approximate value of `{module}::consts::{}` found", &name), format!("approximate value of `{module}::consts::{name}` found"),
None, None,
"consider using the constant directly", "consider using the constant directly",
); );

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro; use clippy_utils::is_from_proc_macro;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -52,13 +52,15 @@ impl<'tcx> LateLintPass<'tcx> for AsConversions {
&& !in_external_macro(cx.sess(), expr.span) && !in_external_macro(cx.sess(), expr.span)
&& !is_from_proc_macro(cx, expr) && !is_from_proc_macro(cx, expr)
{ {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
AS_CONVERSIONS, AS_CONVERSIONS,
expr.span, expr.span,
"using a potentially dangerous silent `as` conversion", "using a potentially dangerous silent `as` conversion",
None, |diag| {
"consider using a safe wrapper for this conversion", diag.help("consider using a safe wrapper for this conversion");
},
); );
} }
} }

View file

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions}; use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions};
use rustc_ast::{InlineAsm, Item, ItemKind}; use rustc_ast::{InlineAsm, Item, ItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext}; use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
@ -49,14 +49,10 @@ fn check_asm_syntax(
}; };
if style == check_for { if style == check_for {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
cx, span_lint_and_then(cx, lint, span, format!("{style} x86 assembly syntax used"), |diag| {
lint, diag.help(format!("use {} x86 assembly syntax", !style));
span, });
format!("{style} x86 assembly syntax used"),
None,
format!("use {} x86 assembly syntax", !style),
);
} }
} }
} }
@ -98,13 +94,13 @@ declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]);
impl EarlyLintPass for InlineAsmX86IntelSyntax { impl EarlyLintPass for InlineAsmX86IntelSyntax {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::InlineAsm(inline_asm) = &expr.kind { if let ExprKind::InlineAsm(inline_asm) = &expr.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Intel); check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Intel);
} }
} }
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if let ItemKind::GlobalAsm(inline_asm) = &item.kind { if let ItemKind::GlobalAsm(inline_asm) = &item.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Intel); check_asm_syntax(INLINE_ASM_X86_INTEL_SYNTAX, cx, inline_asm, item.span, AsmStyle::Intel);
} }
} }
} }
@ -146,13 +142,13 @@ declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]);
impl EarlyLintPass for InlineAsmX86AttSyntax { impl EarlyLintPass for InlineAsmX86AttSyntax {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::InlineAsm(inline_asm) = &expr.kind { if let ExprKind::InlineAsm(inline_asm) = &expr.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, expr.span, AsmStyle::Att); check_asm_syntax(INLINE_ASM_X86_ATT_SYNTAX, cx, inline_asm, expr.span, AsmStyle::Att);
} }
} }
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if let ItemKind::GlobalAsm(inline_asm) = &item.kind { if let ItemKind::GlobalAsm(inline_asm) = &item.kind {
check_asm_syntax(Self::get_lints()[0], cx, inline_asm, item.span, AsmStyle::Att); check_asm_syntax(INLINE_ASM_X86_ATT_SYNTAX, cx, inline_asm, item.span, AsmStyle::Att);
} }
} }
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_inside_always_const_context; use clippy_utils::is_inside_always_const_context;
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else {
return; return;
}; };
let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { let Some(Constant::Bool(val)) = ConstEvalCtxt::new(cx).eval(condition) else {
return; return;
}; };

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item};
@ -68,39 +68,28 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
return; return;
} }
} }
let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; let (message, replacement) = match method_segment.ident.as_str() {
let mut app = Applicability::MachineApplicable;
match method_segment.ident.as_str() {
"is_ok" if type_suitable_to_unwrap(cx, args.type_at(1)) => { "is_ok" if type_suitable_to_unwrap(cx, args.type_at(1)) => {
span_lint_and_sugg( ("called `assert!` with `Result::is_ok`", "unwrap")
cx,
ASSERTIONS_ON_RESULT_STATES,
macro_call.span,
"called `assert!` with `Result::is_ok`",
"replace with",
format!(
"{}.unwrap(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
}, },
"is_err" if type_suitable_to_unwrap(cx, args.type_at(0)) => { "is_err" if type_suitable_to_unwrap(cx, args.type_at(0)) => {
span_lint_and_sugg( ("called `assert!` with `Result::is_err`", "unwrap_err")
cx,
ASSERTIONS_ON_RESULT_STATES,
macro_call.span,
"called `assert!` with `Result::is_err`",
"replace with",
format!(
"{}.unwrap_err(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
}, },
_ => (), _ => return,
}; };
span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| {
let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" };
let mut app = Applicability::MachineApplicable;
diag.span_suggestion(
macro_call.span,
"replace with",
format!(
"{}.{replacement}(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
});
} }
} }
} }

View file

@ -227,9 +227,22 @@ fn build_sugg<'tcx>(
match call_kind { match call_kind {
CallKind::Method => { CallKind::Method => {
let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` // If `ref_expr` is a reference, we can remove the dereference operator (`*`) to make
Sugg::hir_with_applicability(cx, ref_expr, "_", app) // the generated code a bit simpler. In other cases, we don't do this special case, to avoid
// having to deal with Deref (https://github.com/rust-lang/rust-clippy/issues/12437).
let ty = cx.typeck_results().expr_ty(ref_expr);
if ty.is_ref() {
// Apply special case, remove `*`
// `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, ref_expr, "_", app)
} else {
// Keep the original lhs
// `*lhs = self_expr.clone();` -> `(*lhs).clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app)
}
} else { } else {
// Keep the original lhs
// `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app) Sugg::hir_with_applicability(cx, lhs, "_", app)
} }
@ -249,8 +262,16 @@ fn build_sugg<'tcx>(
}, },
CallKind::Ufcs => { CallKind::Ufcs => {
let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` // See special case of removing `*` in method handling above
Sugg::hir_with_applicability(cx, ref_expr, "_", app) let ty = cx.typeck_results().expr_ty(ref_expr);
if ty.is_ref() {
// `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)`
Sugg::hir_with_applicability(cx, ref_expr, "_", app)
} else {
// `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut *lhs, self_expr)`
// mut_addr_deref is used to avoid unnecessary parentheses around `*lhs`
Sugg::hir_with_applicability(cx, ref_expr, "_", app).mut_addr_deref()
}
} else { } else {
// `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)` // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr() Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr()

View file

@ -1,5 +1,5 @@
use super::ALLOW_ATTRIBUTES; use super::ALLOW_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro; use clippy_utils::is_from_proc_macro;
use rustc_ast::{AttrStyle, Attribute}; use rustc_ast::{AttrStyle, Attribute};
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -13,14 +13,14 @@ pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) {
&& let Some(ident) = attr.ident() && let Some(ident) = attr.ident()
&& !is_from_proc_macro(cx, attr) && !is_from_proc_macro(cx, attr)
{ {
span_lint_and_sugg( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
cx, span_lint_and_then(cx, ALLOW_ATTRIBUTES, ident.span, "#[allow] attribute found", |diag| {
ALLOW_ATTRIBUTES, diag.span_suggestion(
ident.span, ident.span,
"#[allow] attribute found", "replace it with",
"replace it with", "expect",
"expect".into(), Applicability::MachineApplicable,
Applicability::MachineApplicable, );
); });
} }
} }

View file

@ -1,5 +1,5 @@
use super::{Attribute, ALLOW_ATTRIBUTES_WITHOUT_REASON}; use super::{Attribute, ALLOW_ATTRIBUTES_WITHOUT_REASON};
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro; use clippy_utils::is_from_proc_macro;
use rustc_ast::{MetaItemKind, NestedMetaItem}; use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_lint::{LateContext, LintContext}; use rustc_lint::{LateContext, LintContext};
@ -21,12 +21,14 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet
return; return;
} }
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
ALLOW_ATTRIBUTES_WITHOUT_REASON, ALLOW_ATTRIBUTES_WITHOUT_REASON,
attr.span, attr.span,
format!("`{}` attribute without specifying a reason", name.as_str()), format!("`{}` attribute without specifying a reason", name.as_str()),
None, |diag| {
"try adding a reason at the end with `, reason = \"..\"`", diag.help("try adding a reason at the end with `, reason = \"..\"`");
},
); );
} }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{in_constant, is_else_clause}; use clippy_utils::{is_else_clause, is_in_const_context};
use rustc_ast::LitKind; use rustc_ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
&& let Some(else_lit) = as_int_bool_lit(else_) && let Some(else_lit) = as_int_bool_lit(else_)
&& then_lit != else_lit && then_lit != else_lit
&& !expr.span.from_expansion() && !expr.span.from_expansion()
&& !in_constant(cx, expr.hir_id) && !is_in_const_context(cx)
{ {
let ty = cx.typeck_results().expr_ty(then); let ty = cx.typeck_results().expr_ty(then);
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;

View file

@ -477,14 +477,12 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
cx: self.cx, cx: self.cx,
}; };
if let Ok(expr) = h2q.run(e) { if let Ok(expr) = h2q.run(e) {
if h2q.terminals.len() > 8 { let stats = terminal_stats(&expr);
// QMC has exponentially slow behavior as the number of terminals increases if stats.ops > 7 {
// 8 is reasonable, it takes approximately 0.2 seconds. // QMC has exponentially slow behavior as the number of ops increases.
// See #825 // See #825, #13206
return; return;
} }
let stats = terminal_stats(&expr);
let mut simplified = expr.simplify(); let mut simplified = expr.simplify();
for simple in Bool::Not(Box::new(expr)).simplify() { for simple in Bool::Not(Box::new(expr)).simplify() {
match simple { match simple {

View file

@ -1,6 +1,6 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::in_constant; use clippy_utils::is_in_const_context;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_isize_or_usize; use clippy_utils::ty::is_isize_or_usize;
@ -21,7 +21,7 @@ pub(super) fn check(
cast_to_hir: &rustc_hir::Ty<'_>, cast_to_hir: &rustc_hir::Ty<'_>,
msrv: &Msrv, msrv: &Msrv,
) { ) {
if !should_lint(cx, expr, cast_from, cast_to, msrv) { if !should_lint(cx, cast_from, cast_to, msrv) {
return; return;
} }
@ -70,9 +70,9 @@ pub(super) fn check(
); );
} }
fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool { fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool {
// Do not suggest using From in consts/statics until it is valid to do so (see #2267). // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
if in_constant(cx, expr.hir_id) { if is_in_const_context(cx) {
return false; return false;
} }

View file

@ -1,6 +1,6 @@
use super::CAST_NAN_TO_INT; use super::CAST_NAN_TO_INT;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_note;
use rustc_hir::Expr; use rustc_hir::Expr;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
} }
fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match constant(cx, cx.typeck_results(), e) { match ConstEvalCtxt::new(cx).eval(e) {
// FIXME(f16_f128): add these types when nan checks are available on all platforms // FIXME(f16_f128): add these types when nan checks are available on all platforms
Some(Constant::F64(n)) => n.is_nan(), Some(Constant::F64(n)) => n.is_nan(),
Some(Constant::F32(n)) => n.is_nan(), Some(Constant::F32(n)) => n.is_nan(),

View file

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::expr_or_init; use clippy_utils::expr_or_init;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
@ -15,7 +15,7 @@ use rustc_target::abi::IntegerType;
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> { fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
if let Some(Constant::Int(c)) = constant(cx, cx.typeck_results(), expr) { if let Some(Constant::Int(c)) = ConstEvalCtxt::new(cx).eval(expr) {
Some(c) Some(c)
} else { } else {
None None

View file

@ -1,7 +1,7 @@
use std::convert::Infallible; use std::convert::Infallible;
use std::ops::ControlFlow; use std::ops::ControlFlow;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::visitors::{for_each_expr_without_closures, Descend}; use clippy_utils::visitors::{for_each_expr_without_closures, Descend};
use clippy_utils::{method_chain_args, sext}; use clippy_utils::{method_chain_args, sext};
@ -88,7 +88,7 @@ fn get_const_signed_int_eval<'cx>(
) -> Option<i128> { ) -> Option<i128> {
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)?
&& let ty::Int(ity) = *ty.kind() && let ty::Int(ity) = *ty.kind()
{ {
return Some(sext(cx.tcx, n, ity)); return Some(sext(cx.tcx, n, ity));
@ -103,7 +103,7 @@ fn get_const_unsigned_int_eval<'cx>(
) -> Option<u128> { ) -> Option<u128> {
let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr)); let ty = ty.into().unwrap_or_else(|| cx.typeck_results().expr_ty(expr));
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)? if let Constant::Int(n) = ConstEvalCtxt::new(cx).eval(expr)?
&& let ty::Uint(_ity) = *ty.kind() && let ty::Uint(_ity) = *ty.kind()
{ {
return Some(n); return Some(n);

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability; use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::Expr; use rustc_hir::Expr;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
@ -14,21 +14,24 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
_ => { /* continue to checks */ }, _ => { /* continue to checks */ },
} }
match cast_from.kind() { if let ty::FnDef(..) | ty::FnPtr(_) = cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(_) => { let mut applicability = Applicability::MaybeIncorrect;
let mut applicability = Applicability::MaybeIncorrect; let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
span_lint_and_sugg( span_lint_and_then(
cx, cx,
FN_TO_NUMERIC_CAST_ANY, FN_TO_NUMERIC_CAST_ANY,
expr.span, expr.span,
format!("casting function pointer `{from_snippet}` to `{cast_to}`"), format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
"did you mean to invoke the function?", |diag| {
format!("{from_snippet}() as {cast_to}"), diag.span_suggestion_with_style(
applicability, expr.span,
); "did you mean to invoke the function?",
}, format!("{from_snippet}() as {cast_to}"),
_ => {}, applicability,
SuggestionStyle::ShowAlways,
);
},
);
} }
} }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::{in_constant, is_integer_literal, std_or_core}; use clippy_utils::{is_in_const_context, is_integer_literal, std_or_core};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability, Ty, TyKind}; use rustc_hir::{Expr, Mutability, Ty, TyKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -10,7 +10,7 @@ use super::ZERO_PTR;
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) { pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
if let TyKind::Ptr(ref mut_ty) = to.kind if let TyKind::Ptr(ref mut_ty) = to.kind
&& is_integer_literal(from, 0) && is_integer_literal(from, 0)
&& !in_constant(cx, from.hir_id) && !is_in_const_context(cx)
&& let Some(std_or_core) = std_or_core(cx) && let Some(std_or_core) = std_or_core(cx)
{ {
let (msg, sugg_fn) = match mut_ty.mutbl { let (msg, sugg_fn) = match mut_ty.mutbl {

View file

@ -4,7 +4,7 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq}; use clippy_utils::{is_in_const_context, is_integer_literal, SpanlessEq};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
_ => return, _ => return,
} }
&& !in_external_macro(cx.sess(), item.span) && !in_external_macro(cx.sess(), item.span)
&& !in_constant(cx, item.hir_id) && !is_in_const_context(cx)
&& self.msrv.meets(msrvs::TRY_FROM) && self.msrv.meets(msrvs::TRY_FROM)
&& let Some(cv) = match op2 { && let Some(cv) = match op2 {
// todo: check for case signed -> larger unsigned == only x >= 0 // todo: check for case signed -> larger unsigned == only x >= 0

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::{if_sequence, in_constant, is_else_clause, SpanlessEq}; use clippy_utils::{if_sequence, is_else_clause, is_in_const_context, SpanlessEq};
use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
return; return;
} }
if in_constant(cx, expr.hir_id) { if is_in_const_context(cx) {
return; return;
} }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability; use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
@ -39,14 +39,24 @@ impl LateLintPass<'_> for CreateDir {
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id) && cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id)
{ {
span_lint_and_sugg( span_lint_and_then(
cx, cx,
CREATE_DIR, CREATE_DIR,
expr.span, expr.span,
"calling `std::fs::create_dir` where there may be a better way", "calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead", |diag| {
format!("create_dir_all({})", snippet(cx, arg.span, "..")), let mut app = Applicability::MaybeIncorrect;
Applicability::MaybeIncorrect, diag.span_suggestion_with_style(
expr.span,
"consider calling `std::fs::create_dir_all` instead",
format!(
"create_dir_all({})",
snippet_with_applicability(cx, arg.span, "..", &mut app)
),
app,
SuggestionStyle::ShowAlways,
);
},
); );
} }
} }

View file

@ -1,5 +1,5 @@
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_in_test; use clippy_utils::is_in_test;
use clippy_utils::macros::{macro_backtrace, MacroCall}; use clippy_utils::macros::{macro_backtrace, MacroCall};
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
@ -65,61 +65,67 @@ impl LateLintPass<'_> for DbgMacro {
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml // allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
!(self.allow_dbg_in_tests && is_in_test(cx.tcx, expr.hir_id)) !(self.allow_dbg_in_tests && is_in_test(cx.tcx, expr.hir_id))
{ {
let mut applicability = Applicability::MachineApplicable;
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
// dbg!()
ExprKind::Block(..) => {
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
// remove the whole statement.
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
{
(macro_call.span.to(semi_span), String::new())
} else {
(macro_call.span, String::from("()"))
}
},
// dbg!(1)
ExprKind::Match(val, ..) => (
macro_call.span,
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string(),
),
// dbg!(2, 3)
ExprKind::Tup(
[
Expr {
kind: ExprKind::Match(first, ..),
..
},
..,
Expr {
kind: ExprKind::Match(last, ..),
..
},
],
) => {
let snippet = snippet_with_applicability(
cx,
first.span.source_callsite().to(last.span.source_callsite()),
"..",
&mut applicability,
);
(macro_call.span, format!("({snippet})"))
},
_ => return,
};
self.prev_ctxt = cur_syntax_ctxt; self.prev_ctxt = cur_syntax_ctxt;
span_lint_and_sugg( span_lint_and_then(
cx, cx,
DBG_MACRO, DBG_MACRO,
sugg_span, macro_call.span,
"the `dbg!` macro is intended as a debugging tool", "the `dbg!` macro is intended as a debugging tool",
"remove the invocation before committing it to a version control system", |diag| {
suggestion, let mut applicability = Applicability::MachineApplicable;
applicability,
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
// dbg!()
ExprKind::Block(..) => {
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
// remove the whole statement.
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
{
(macro_call.span.to(semi_span), String::new())
} else {
(macro_call.span, String::from("()"))
}
},
// dbg!(1)
ExprKind::Match(val, ..) => (
macro_call.span,
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
.to_string(),
),
// dbg!(2, 3)
ExprKind::Tup(
[
Expr {
kind: ExprKind::Match(first, ..),
..
},
..,
Expr {
kind: ExprKind::Match(last, ..),
..
},
],
) => {
let snippet = snippet_with_applicability(
cx,
first.span.source_callsite().to(last.span.source_callsite()),
"..",
&mut applicability,
);
(macro_call.span, format!("({snippet})"))
},
_ => unreachable!(),
};
diag.span_suggestion(
sugg_span,
"remove the invocation before committing it to a version control system",
suggestion,
applicability,
);
},
); );
} }
} }

View file

@ -14,8 +14,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO, crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO,
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_DEPRECATION_REASON_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO, crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO,
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO, crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO,
@ -741,6 +739,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::unused_async::UNUSED_ASYNC_INFO, crate::unused_async::UNUSED_ASYNC_INFO,
crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO, crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO,
crate::unused_peekable::UNUSED_PEEKABLE_INFO, crate::unused_peekable::UNUSED_PEEKABLE_INFO,
crate::unused_result_ok::UNUSED_RESULT_OK_INFO,
crate::unused_rounding::UNUSED_ROUNDING_INFO, crate::unused_rounding::UNUSED_ROUNDING_INFO,
crate::unused_self::UNUSED_SELF_INFO, crate::unused_self::UNUSED_SELF_INFO,
crate::unused_unit::UNUSED_UNIT_INFO, crate::unused_unit::UNUSED_UNIT_INFO,

View file

@ -221,7 +221,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
.map(ToString::to_string) .map(ToString::to_string)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
format!("{adt_def_ty_name}::<{}>", &tys_str) format!("{adt_def_ty_name}::<{tys_str}>")
} else { } else {
binding_type.to_string() binding_type.to_string()
}; };

View file

@ -92,20 +92,8 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
let (suffix, is_float) = match lit_ty.kind() { let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false), ty::Int(IntTy::I32) => ("i32", false),
ty::Float(FloatTy::F64) => ("f64", true), ty::Float(FloatTy::F64) => ("f64", true),
// Default numeric fallback never results in other types.
_ => return, _ => return,
}; };
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => return,
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
span_lint_hir_and_then( span_lint_hir_and_then(
self.cx, self.cx,
DEFAULT_NUMERIC_FALLBACK, DEFAULT_NUMERIC_FALLBACK,
@ -113,6 +101,17 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
lit.span, lit.span,
"default numeric fallback might occur", "default numeric fallback might occur",
|diag| { |diag| {
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => unreachable!("Default numeric fallback never results in other types"),
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect); diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
}, },
); );

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_hir::{HirId, Item, ItemKind}; use rustc_hir::{HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::layout::LayoutOf;
@ -56,16 +56,18 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
&& is_union_with_two_non_zst_fields(cx, item) && is_union_with_two_non_zst_fields(cx, item)
&& !has_c_repr_attr(cx, item.hir_id()) && !has_c_repr_attr(cx, item.hir_id())
{ {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
DEFAULT_UNION_REPRESENTATION, DEFAULT_UNION_REPRESENTATION,
item.span, item.span,
"this union has the default representation", "this union has the default representation",
None, |diag| {
format!( diag.help(format!(
"consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout", "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout",
cx.tcx.def_path_str(item.owner_id) cx.tcx.def_path_str(item.owner_id)
), ));
},
); );
} }
} }

View file

@ -1,243 +1,181 @@
// NOTE: Entries should be created with `cargo dev deprecate` // This file is managed by `cargo dev rename_lint` and `cargo dev deprecate_lint`.
// Prefer to use those when possible.
/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This macro_rules! declare_with_version {
/// enables the simple extraction of the metadata without changing the current deprecation ($name:ident($name_version:ident): &[$ty:ty] = &[$(
/// declaration. #[clippy::version = $version:literal]
pub struct ClippyDeprecatedLint { $e:expr,
#[allow(dead_code)] )*]) => {
pub desc: &'static str, pub static $name: &[$ty] = &[$($e),*];
#[allow(unused)]
pub static $name_version: &[&str] = &[$($version),*];
};
} }
#[macro_export] #[rustfmt::skip]
macro_rules! declare_deprecated_lint { declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
{ $(#[$attr:meta])* pub $name: ident, $reason: literal} => {
$(#[$attr])*
#[allow(dead_code)]
pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {
desc: $reason
};
}
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This used to check for `assert!(a == b)` and recommend
/// replacement with `assert_eq!(a, b)`, but this is no longer needed after RFC 2011.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub SHOULD_ASSERT_EQ, ("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
"`assert!()` will be more flexible with RFC 2011"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This used to check for `Vec::extend`, which was slower than
/// `Vec::extend_from_slice`. Thanks to specialization, this is no longer true.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub EXTEND_FROM_SLICE, ("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"),
"`.extend_from_slice(_)` is a faster way to extend a Vec by a slice"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// `Range::step_by(0)` used to be linted since it's
/// an infinite iterator, which is better expressed by `iter::repeat`,
/// but the method has been removed for `Iterator::step_by` which panics
/// if given a zero
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub RANGE_STEP_BY_ZERO, ("clippy::range_step_by_zero", "`Iterator::step_by(0)` now panics and is no longer an infinite iterator"),
"`iterator.step_by(0)` panics nowadays"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This used to check for `Vec::as_slice`, which was unstable with good
/// stable alternatives. `Vec::as_slice` has now been stabilized.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub UNSTABLE_AS_SLICE, ("clippy::unstable_as_slice", "`Vec::as_slice` is now stable"),
"`Vec::as_slice` has been stabilized in 1.7"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This used to check for `Vec::as_mut_slice`, which was unstable with good
/// stable alternatives. `Vec::as_mut_slice` has now been stabilized.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub UNSTABLE_AS_MUT_SLICE, ("clippy::unstable_as_mut_slice", "`Vec::as_mut_slice` is now stable"),
"`Vec::as_mut_slice` has been stabilized in 1.7"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint should never have applied to non-pointer types, as transmuting
/// between non-pointer types of differing alignment is well-defined behavior (it's semantically
/// equivalent to a memcpy). This lint has thus been refactored into two separate lints:
/// cast_ptr_alignment and transmute_ptr_to_ptr.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub MISALIGNED_TRANSMUTE, ("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"),
"this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint is too subjective, not having a good reason for being in clippy.
/// Additionally, compound assignment operators may be overloaded separately from their non-assigning
/// counterparts, so this lint may suggest a change in behavior or the code may not compile.
#[clippy::version = "1.30.0"] #[clippy::version = "1.30.0"]
pub ASSIGN_OPS, ("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"),
"using compound assignment operators (e.g., `+=`) is harmless"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// The original rule will only lint for `if let`. After
/// making it support to lint `match`, naming as `if let` is not suitable for it.
/// So, this lint is deprecated.
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub IF_LET_REDUNDANT_PATTERN_MATCHING, ("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"),
"this lint has been changed to redundant_pattern_matching"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint used to suggest replacing `let mut vec =
/// Vec::with_capacity(n); vec.set_len(n);` with `let vec = vec![0; n];`. The
/// replacement has very different performance characteristics so the lint is
/// deprecated.
#[clippy::version = "pre 1.29.0"]
pub UNSAFE_VECTOR_INITIALIZATION,
"the replacement suggested by this lint had substantially different behavior"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint has been superseded by #[must_use] in rustc.
#[clippy::version = "1.39.0"] #[clippy::version = "1.39.0"]
pub UNUSED_COLLECT, ("clippy::unused_collect", "`Iterator::collect` is now marked as `#[must_use]`"),
"`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// Associated-constants are now preferred.
#[clippy::version = "1.44.0"] #[clippy::version = "1.44.0"]
pub REPLACE_CONSTS, ("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"),
"associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// The regex! macro does not exist anymore.
#[clippy::version = "1.47.0"] #[clippy::version = "1.47.0"]
pub REGEX_MACRO, ("clippy::regex_macro", "the `regex!` macro was removed from the regex crate in 2018"),
"the regex! macro has been removed from the regex crate in 2018" #[clippy::version = "1.54.0"]
} ("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"),
#[clippy::version = "1.54.0"]
("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"),
// end deprecated lints. used by `cargo dev deprecate_lint`
]}
declare_deprecated_lint! { #[rustfmt::skip]
/// ### What it does declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
/// Nothing. This lint has been deprecated. #[clippy::version = ""]
/// ("clippy::almost_complete_letter_range", "clippy::almost_complete_range"),
/// ### Deprecation reason #[clippy::version = ""]
/// This lint has been replaced by `manual_find_map`, a ("clippy::blacklisted_name", "clippy::disallowed_names"),
/// more specific lint. #[clippy::version = ""]
("clippy::block_in_if_condition_expr", "clippy::blocks_in_conditions"),
#[clippy::version = ""]
("clippy::block_in_if_condition_stmt", "clippy::blocks_in_conditions"),
#[clippy::version = ""]
("clippy::blocks_in_if_conditions", "clippy::blocks_in_conditions"),
#[clippy::version = ""]
("clippy::box_vec", "clippy::box_collection"),
#[clippy::version = ""]
("clippy::const_static_lifetime", "clippy::redundant_static_lifetimes"),
#[clippy::version = ""]
("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"),
#[clippy::version = ""]
("clippy::derive_hash_xor_eq", "clippy::derived_hash_with_manual_eq"),
#[clippy::version = ""]
("clippy::disallowed_method", "clippy::disallowed_methods"),
#[clippy::version = ""]
("clippy::disallowed_type", "clippy::disallowed_types"),
#[clippy::version = ""]
("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
#[clippy::version = "1.51.0"] #[clippy::version = "1.51.0"]
pub FIND_MAP, ("clippy::find_map", "clippy::manual_find_map"),
"this lint has been replaced by `manual_find_map`, a more specific lint"
}
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint has been replaced by `manual_filter_map`, a
/// more specific lint.
#[clippy::version = "1.53.0"] #[clippy::version = "1.53.0"]
pub FILTER_MAP, ("clippy::filter_map", "clippy::manual_filter_map"),
"this lint has been replaced by `manual_filter_map`, a more specific lint" #[clippy::version = ""]
} ("clippy::identity_conversion", "clippy::useless_conversion"),
#[clippy::version = "pre 1.29.0"]
declare_deprecated_lint! { ("clippy::if_let_redundant_pattern_matching", "clippy::redundant_pattern_matching"),
/// ### What it does #[clippy::version = ""]
/// Nothing. This lint has been deprecated. ("clippy::if_let_some_result", "clippy::match_result_ok"),
/// #[clippy::version = ""]
/// ### Deprecation reason ("clippy::incorrect_clone_impl_on_copy_type", "clippy::non_canonical_clone_impl"),
/// The `avoid_breaking_exported_api` config option was added, which #[clippy::version = ""]
/// enables the `enum_variant_names` lint for public items. ("clippy::incorrect_partial_ord_impl_on_ord_type", "clippy::non_canonical_partial_ord_impl"),
#[clippy::version = "1.54.0"] #[clippy::version = ""]
pub PUB_ENUM_VARIANT_NAMES, ("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"),
"set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items" #[clippy::version = ""]
} ("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
#[clippy::version = ""]
declare_deprecated_lint! { ("clippy::new_without_default_derive", "clippy::new_without_default"),
/// ### What it does #[clippy::version = ""]
/// Nothing. This lint has been deprecated. ("clippy::option_and_then_some", "clippy::bind_instead_of_map"),
/// #[clippy::version = ""]
/// ### Deprecation reason ("clippy::option_expect_used", "clippy::expect_used"),
/// The `avoid_breaking_exported_api` config option was added, which #[clippy::version = ""]
/// enables the `wrong_self_conversion` lint for public items. ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"),
#[clippy::version = "1.54.0"] #[clippy::version = ""]
pub WRONG_PUB_SELF_CONVENTION, ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"),
"set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items" #[clippy::version = ""]
} ("clippy::option_unwrap_used", "clippy::unwrap_used"),
#[clippy::version = ""]
declare_deprecated_lint! { ("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"),
/// ### What it does #[clippy::version = ""]
/// Nothing. This lint has been deprecated. ("clippy::ref_in_deref", "clippy::needless_borrow"),
/// #[clippy::version = ""]
/// ### Deprecation reason ("clippy::result_expect_used", "clippy::expect_used"),
/// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect the `#[cfg(features)]` and `#[cfg(tests)]` typos. #[clippy::version = ""]
/// ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"),
/// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs #[clippy::version = ""]
("clippy::result_unwrap_used", "clippy::unwrap_used"),
#[clippy::version = ""]
("clippy::single_char_push_str", "clippy::single_char_add_str"),
#[clippy::version = ""]
("clippy::stutter", "clippy::module_name_repetitions"),
#[clippy::version = ""]
("clippy::thread_local_initializer_can_be_made_const", "clippy::missing_const_for_thread_local"),
#[clippy::version = ""]
("clippy::to_string_in_display", "clippy::recursive_format_impl"),
#[clippy::version = ""]
("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"),
#[clippy::version = ""]
("clippy::zero_width_space", "clippy::invisible_characters"),
#[clippy::version = ""]
("clippy::cast_ref_to_mut", "invalid_reference_casting"),
#[clippy::version = ""]
("clippy::clone_double_ref", "suspicious_double_ref_op"),
#[clippy::version = ""]
("clippy::cmp_nan", "invalid_nan_comparisons"),
#[clippy::version = ""]
("clippy::drop_bounds", "drop_bounds"),
#[clippy::version = ""]
("clippy::drop_copy", "dropping_copy_types"),
#[clippy::version = ""]
("clippy::drop_ref", "dropping_references"),
#[clippy::version = ""]
("clippy::fn_null_check", "useless_ptr_null_checks"),
#[clippy::version = ""]
("clippy::for_loop_over_option", "for_loops_over_fallibles"),
#[clippy::version = ""]
("clippy::for_loop_over_result", "for_loops_over_fallibles"),
#[clippy::version = ""]
("clippy::for_loops_over_fallibles", "for_loops_over_fallibles"),
#[clippy::version = ""]
("clippy::forget_copy", "forgetting_copy_types"),
#[clippy::version = ""]
("clippy::forget_ref", "forgetting_references"),
#[clippy::version = ""]
("clippy::into_iter_on_array", "array_into_iter"),
#[clippy::version = ""]
("clippy::invalid_atomic_ordering", "invalid_atomic_ordering"),
#[clippy::version = ""]
("clippy::invalid_ref", "invalid_value"),
#[clippy::version = ""]
("clippy::invalid_utf8_in_unchecked", "invalid_from_utf8_unchecked"),
#[clippy::version = ""]
("clippy::let_underscore_drop", "let_underscore_drop"),
#[clippy::version = "1.80.0"] #[clippy::version = "1.80.0"]
pub MAYBE_MISUSED_CFG, ("clippy::maybe_misused_cfg", "unexpected_cfgs"),
"this lint has been replaced by `unexpected_cfgs`" #[clippy::version = ""]
} ("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums"),
declare_deprecated_lint! {
/// ### What it does
/// Nothing. This lint has been deprecated.
///
/// ### Deprecation reason
/// This lint has been superseded by rustc's own [`unexpected_cfgs`] lint that is able to detect invalid `#[cfg(linux)]` attributes.
///
/// [`unexpected_cfgs`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#unexpected-cfgs
#[clippy::version = "1.80.0"] #[clippy::version = "1.80.0"]
pub MISMATCHED_TARGET_OS, ("clippy::mismatched_target_os", "unexpected_cfgs"),
"this lint has been replaced by `unexpected_cfgs`" #[clippy::version = ""]
} ("clippy::panic_params", "non_fmt_panics"),
#[clippy::version = ""]
("clippy::positional_named_format_parameters", "named_arguments_used_positionally"),
#[clippy::version = ""]
("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr"),
#[clippy::version = ""]
("clippy::undropped_manually_drops", "undropped_manually_drops"),
#[clippy::version = ""]
("clippy::unknown_clippy_lints", "unknown_lints"),
#[clippy::version = ""]
("clippy::unused_label", "unused_labels"),
#[clippy::version = ""]
("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"),
#[clippy::version = ""]
("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"),
// end renamed lints. used by `cargo dev rename_lint`
]}

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; use clippy_utils::ty::{implements_trait, is_manually_drop};
use clippy_utils::{ use clippy_utils::{
expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, DefinedTy, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, DefinedTy,
ExprUseNode, ExprUseNode,
@ -947,7 +947,7 @@ fn report<'tcx>(
let (expr_str, _expr_is_macro_call) = let (expr_str, _expr_is_macro_call) =
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app); snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
let ty = typeck.expr_ty(expr); let ty = typeck.expr_ty(expr);
let (_, ref_count) = peel_mid_ty_refs(ty); let (_, ref_count) = peel_middle_ty_refs(ty);
let deref_str = if ty_changed_count >= ref_count && ref_count != 0 { let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
// a deref call changing &T -> &U requires two deref operators the first time // a deref call changing &T -> &U requires two deref operators the first time
// this occurs. One to remove the reference, a second to call the deref impl. // this occurs. One to remove the reference, a second to call the deref impl.

View file

@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable; use clippy_utils::visitors::Visitable;
use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args}; use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
use pulldown_cmark::Event::{ use pulldown_cmark::Event::{
Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
TaskListMarker, Text, TaskListMarker, Text,
@ -768,7 +768,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
); );
} }
}, },
FootnoteReference(text) | Text(text) => { Text(text) => {
paragraph_range.end = range.end; paragraph_range.end = range.end;
let range_ = range.clone(); let range_ = range.clone();
ticks_unbalanced |= text.contains('`') ticks_unbalanced |= text.contains('`')
@ -812,7 +812,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
} }
text_to_check.push((text, range, code_level)); text_to_check.push((text, range, code_level));
} }
}, }
FootnoteReference(_) => {}
} }
} }
headers headers
@ -857,7 +858,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
"assert" | "assert_eq" | "assert_ne" "assert" | "assert_eq" | "assert_ne"
) )
{ {
self.is_const = in_constant(self.cx, expr.hir_id); self.is_const = self.cx.tcx.hir().is_inside_const_context(expr.hir_id);
self.panic_span = Some(macro_call.span); self.panic_span = Some(macro_call.span);
} }
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_must_use_func_call; use clippy_utils::is_must_use_func_call;
use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item}; use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node}; use rustc_hir::{Arm, Expr, ExprKind, LangItem, Node};
@ -126,14 +126,14 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
}, },
_ => return, _ => return,
}; };
span_lint_and_note( span_lint_and_then(cx, lint, expr.span, msg, |diag| {
cx, let note = format!("argument has type `{arg_ty}`");
lint, if let Some(span) = note_span {
expr.span, diag.span_note(span, note);
msg, } else {
note_span, diag.note(note);
format!("argument has type `{arg_ty}`"), }
); });
} }
} }
} }

View file

@ -1,6 +1,6 @@
//! Lint on if expressions with an else if, but without a final else branch. //! Lint on if expressions with an else if, but without a final else branch.
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::{Expr, ExprKind}; use rustc_ast::ast::{Expr, ExprKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -54,13 +54,15 @@ impl EarlyLintPass for ElseIfWithoutElse {
&& let ExprKind::If(_, _, None) = els.kind && let ExprKind::If(_, _, None) = els.kind
&& !in_external_macro(cx.sess(), item.span) && !in_external_macro(cx.sess(), item.span)
{ {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
ELSE_IF_WITHOUT_ELSE, ELSE_IF_WITHOUT_ELSE,
els.span, els.span,
"`if` expression with an `else if`, but without a final `else`", "`if` expression with an `else if`, but without a final `else`",
None, |diag| {
"add an `else` block here", diag.help("add an `else` block here");
},
); );
} }
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::peel_blocks; use clippy_utils::peel_blocks;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node}; use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
@ -50,15 +50,14 @@ impl LateLintPass<'_> for EmptyDrop {
&& block.stmts.is_empty() && block.stmts.is_empty()
&& block.expr.is_none() && block.expr.is_none()
{ {
span_lint_and_sugg( span_lint_and_then(cx, EMPTY_DROP, item.span, "empty drop implementation", |diag| {
cx, diag.span_suggestion_hidden(
EMPTY_DROP, item.span,
item.span, "try removing this impl",
"empty drop implementation", String::new(),
"try removing this impl", Applicability::MaybeIncorrect,
String::new(), );
Applicability::MaybeIncorrect, });
);
} }
} }
} }

View file

@ -7,7 +7,6 @@ use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::Ty; use rustc_middle::ty::Ty;
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::Symbol; use rustc_span::Symbol;
use std::borrow::Cow;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -141,52 +140,6 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix
_ => return, _ => return,
}; };
let mut help = None;
'build_help: {
// all lints disallowed, don't give help here
if [&[lint], other_lints.as_slice()]
.concat()
.iter()
.all(|lint| !lint.allowed(cx, expr))
{
break 'build_help;
}
// ne_bytes and all other lints allowed
if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) {
help = Some(Cow::Borrowed("specify the desired endianness explicitly"));
break 'build_help;
}
// le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but
// le_bytes is not
if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) {
help = Some(Cow::Borrowed("use the native endianness instead"));
break 'build_help;
}
let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr));
let len = allowed_lints.clone().count();
let mut help_str = "use ".to_owned();
for (i, lint) in allowed_lints.enumerate() {
let only_one = len == 1;
if !only_one {
help_str.push_str("either of ");
}
help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix)));
if i != len && !only_one {
help_str.push_str("or ");
}
}
help = Some(Cow::Owned(help_str + "instead"));
}
span_lint_and_then( span_lint_and_then(
cx, cx,
lint.as_lint(), lint.as_lint(),
@ -198,9 +151,47 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix
if prefix == Prefix::To { " method" } else { "" }, if prefix == Prefix::To { " method" } else { "" },
), ),
move |diag| { move |diag| {
if let Some(help) = help { // all lints disallowed, don't give help here
diag.help(help); if [&[lint], other_lints.as_slice()]
.concat()
.iter()
.all(|lint| !lint.allowed(cx, expr))
{
return;
} }
// ne_bytes and all other lints allowed
if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) {
diag.help("specify the desired endianness explicitly");
return;
}
// le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but
// le_bytes is not
if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) {
diag.help("use the native endianness instead");
return;
}
let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr));
let len = allowed_lints.clone().count();
let mut help_str = "use ".to_owned();
for (i, lint) in allowed_lints.enumerate() {
let only_one = len == 1;
if !only_one {
help_str.push_str("either of ");
}
help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix)));
if i != len && !only_one {
help_str.push_str("or ");
}
}
help_str.push_str("instead");
diag.help(help_str);
}, },
); );
} }

View file

@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
.const_eval_poly(def_id.to_def_id()) .const_eval_poly(def_id.to_def_id())
.ok() .ok()
.map(|val| rustc_middle::mir::Const::from_value(val, ty)); .map(|val| rustc_middle::mir::Const::from_value(val, ty));
if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx, c)) { if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) {
if let ty::Adt(adt, _) = ty.kind() { if let ty::Adt(adt, _) = ty.kind() {
if adt.is_enum() { if adt.is_enum() {
ty = adt.repr().discr_type().to_ty(cx.tcx); ty = adt.repr().discr_type().to_ty(cx.tcx);

View file

@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{ use rustc_middle::ty::{
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty, TyCtxt, self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults,
TypeVisitableExt, TypeckResults,
}; };
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
@ -74,159 +73,184 @@ declare_clippy_lint! {
declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]);
impl<'tcx> LateLintPass<'tcx> for EtaReduction { impl<'tcx> LateLintPass<'tcx> for EtaReduction {
#[allow(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::MethodCall(_method, receiver, args, _) = expr.kind {
let body = if let ExprKind::Closure(c) = expr.kind for arg in args {
&& c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) check_clousure(cx, Some(receiver), arg);
&& matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_))
&& !expr.span.from_expansion()
{
cx.tcx.hir().body(c.body)
} else {
return;
};
if body.value.span.from_expansion() {
if body.params.is_empty() {
if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) {
// replace `|| vec![]` with `Vec::new`
span_lint_and_sugg(
cx,
REDUNDANT_CLOSURE,
expr.span,
"redundant closure",
"replace the closure with `Vec::new`",
"std::vec::Vec::new".into(),
Applicability::MachineApplicable,
);
}
} }
// skip `foo(|| macro!())`
return;
} }
if let ExprKind::Call(func, args) = expr.kind {
let typeck = cx.typeck_results(); check_clousure(cx, None, func);
let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { for arg in args {
closure_subs.as_closure() check_clousure(cx, None, arg);
} else { }
return;
};
if is_adjusted(cx, body.value) {
return;
} }
}
}
match body.value.kind { #[allow(clippy::too_many_lines)]
ExprKind::Call(callee, args) fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx>>, expr: &Expr<'tcx>) {
if matches!( let body = if let ExprKind::Closure(c) = expr.kind
callee.kind, && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer))
ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..)) && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_))
) => && !expr.span.from_expansion()
{
cx.tcx.hir().body(c.body)
} else {
return;
};
if body.value.span.from_expansion() {
if body.params.is_empty() {
if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) {
// replace `|| vec![]` with `Vec::new`
span_lint_and_sugg(
cx,
REDUNDANT_CLOSURE,
expr.span,
"redundant closure",
"replace the closure with `Vec::new`",
"std::vec::Vec::new".into(),
Applicability::MachineApplicable,
);
}
}
// skip `foo(|| macro!())`
return;
}
if is_adjusted(cx, body.value) {
return;
}
let typeck = cx.typeck_results();
let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() {
closure_subs.as_closure()
} else {
return;
};
let closure_sig = cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder();
match body.value.kind {
ExprKind::Call(callee, args)
if matches!(
callee.kind,
ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..))
) =>
{
let callee_ty_raw = typeck.expr_ty(callee);
let callee_ty = callee_ty_raw.peel_refs();
if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc))
|| !check_inputs(typeck, body.params, None, args)
{ {
let callee_ty_raw = typeck.expr_ty(callee); return;
let callee_ty = callee_ty_raw.peel_refs(); }
if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) let callee_ty_adjusted = typeck
|| !check_inputs(typeck, body.params, None, args) .expr_adjustments(callee)
{ .last()
return; .map_or(callee_ty, |a| a.target.peel_refs());
}
let callee_ty_adjusted = typeck
.expr_adjustments(callee)
.last()
.map_or(callee_ty, |a| a.target.peel_refs());
let sig = match callee_ty_adjusted.kind() { let sig = match callee_ty_adjusted.kind() {
ty::FnDef(def, _) => { ty::FnDef(def, _) => {
// Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
if cx.tcx.has_attr(*def, sym::track_caller) { if cx.tcx.has_attr(*def, sym::track_caller) {
return; return;
} }
cx.tcx.fn_sig(def).skip_binder().skip_binder() cx.tcx.fn_sig(def).skip_binder().skip_binder()
}, },
ty::FnPtr(sig) => sig.skip_binder(), ty::FnPtr(sig) => sig.skip_binder(),
ty::Closure(_, subs) => cx ty::Closure(_, subs) => cx
.tcx .tcx
.signature_unclosure(subs.as_closure().sig(), Safety::Safe) .signature_unclosure(subs.as_closure().sig(), Safety::Safe)
.skip_binder(), .skip_binder(),
_ => { _ => {
if typeck.type_dependent_def_id(body.value.hir_id).is_some() if typeck.type_dependent_def_id(body.value.hir_id).is_some()
&& let subs = typeck.node_args(body.value.hir_id) && let subs = typeck.node_args(body.value.hir_id)
&& let output = typeck.expr_ty(body.value) && let output = typeck.expr_ty(body.value)
&& let ty::Tuple(tys) = *subs.type_at(1).kind() && let ty::Tuple(tys) = *subs.type_at(1).kind()
{ {
cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust)
} else { } else {
return; return;
} }
}, },
}; };
if check_sig(cx, closure, sig) if let Some(outer) = outer_receiver
&& let generic_args = typeck.node_args(callee.hir_id) && ty_has_static(sig.output())
// Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not && let generic_args = typeck.node_args(outer.hir_id)
// `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result // HACK: Given a closure in `T.method(|| f())`, where `fn f() -> U where U: 'static`, `T.method(f)`
// in a type which is `'static`. // will succeed iff `T: 'static`. But the region of `T` is always erased by `typeck.expr_ty()` when
// For now ignore all callee types which reference a type parameter. // T is a generic type. For example, return type of `Option<String>::as_deref()` is a generic.
&& !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_))) // So we have a hack like this.
{ && generic_args.len() > 0
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { {
if let Some(mut snippet) = snippet_opt(cx, callee.span) { return;
if path_to_local(callee).map_or(false, |l| { }
// FIXME: Do we really need this `local_used_in` check? if check_sig(closure_sig, sig)
// Isn't it checking something like... `callee(callee)`? && let generic_args = typeck.node_args(callee.hir_id)
// If somehow this check is needed, add some test for it, // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not
// 'cuz currently nothing changes after deleting this check. // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) // in a type which is `'static`.
}) { // For now ignore all callee types which reference a type parameter.
match cx.tcx.infer_ctxt().build().err_ctxt().type_implements_fn_trait( && !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_)))
cx.param_env, {
Binder::bind_with_vars(callee_ty_adjusted, List::empty()), span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
ty::PredicatePolarity::Positive, if let Some(mut snippet) = snippet_opt(cx, callee.span) {
) { if path_to_local(callee).map_or(false, |l| {
// Mutable closure is used after current expr; we cannot consume it. // FIXME: Do we really need this `local_used_in` check?
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), // Isn't it checking something like... `callee(callee)`?
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { // If somehow this check is needed, add some test for it,
snippet = format!("&{snippet}"); // 'cuz currently nothing changes after deleting this check.
}, local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
_ => (), }) {
} match cx.tcx.infer_ctxt().build().err_ctxt().type_implements_fn_trait(
cx.param_env,
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
ty::PredicatePolarity::Positive,
) {
// Mutable closure is used after current expr; we cannot consume it.
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
snippet = format!("&{snippet}");
},
_ => (),
} }
diag.span_suggestion(
expr.span,
"replace the closure with the function itself",
snippet,
Applicability::MachineApplicable,
);
} }
}); diag.span_suggestion(
} expr.span,
}, "replace the closure with the function itself",
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { snippet,
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) Applicability::MachineApplicable,
&& !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( }
cx, },
REDUNDANT_CLOSURE_FOR_METHOD_CALLS, ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
expr.span, if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
"redundant closure", && !cx.tcx.has_attr(method_def_id, sym::track_caller)
|diag| { && check_sig(closure_sig, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder())
let args = typeck.node_args(body.value.hir_id); {
let caller = self_.hir_id.owner.def_id; span_lint_and_then(
let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); cx,
diag.span_suggestion( REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
expr.span, expr.span,
"replace the closure with the method itself", "redundant closure",
format!("{}::{}", type_name, path.ident.name), |diag| {
Applicability::MachineApplicable, let args = typeck.node_args(body.value.hir_id);
); 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!("{}::{}", type_name, path.ident.name),
} Applicability::MachineApplicable,
);
},
);
}
},
_ => (),
} }
} }
@ -251,12 +275,8 @@ fn check_inputs(
}) })
} }
fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<TyCtxt<'tcx>>, call_sig: FnSig<'_>) -> bool { fn check_sig<'tcx>(closure_sig: FnSig<'tcx>, call_sig: FnSig<'tcx>) -> bool {
call_sig.safety == Safety::Safe call_sig.safety == Safety::Safe && !has_late_bound_to_non_late_bound_regions(closure_sig, call_sig)
&& !has_late_bound_to_non_late_bound_regions(
cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(),
call_sig,
)
} }
/// This walks through both signatures and checks for any time a late-bound region is expected by an /// This walks through both signatures and checks for any time a late-bound region is expected by an
@ -265,7 +285,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<TyCtxt<'tcx>>, c
/// This is needed because rustc is unable to late bind early-bound regions in a function signature. /// This is needed because rustc is unable to late bind early-bound regions in a function signature.
fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool { fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool {
fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool { fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool {
matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..)) from_region.is_bound() && !to_region.is_bound()
} }
fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool {
@ -318,3 +338,8 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
.zip(to_sig.inputs_and_output) .zip(to_sig.inputs_and_output)
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
} }
fn ty_has_static(ty: Ty<'_>) -> bool {
ty.walk()
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(re) if re.is_static()))
}

View file

@ -88,11 +88,11 @@ impl LateLintPass<'_> for ExhaustiveItems {
&& !attrs.iter().any(|a| a.has_name(sym::non_exhaustive)) && !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
&& fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public()) && fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public())
{ {
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
span_lint_and_then(cx, lint, item.span, msg, |diag| { span_lint_and_then(cx, lint, item.span, msg, |diag| {
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
let sugg = format!("#[non_exhaustive]\n{indent}"); let sugg = format!("#[non_exhaustive]\n{indent}");
diag.span_suggestion( diag.span_suggestion_verbose(
suggestion_span, suggestion_span,
"try adding #[non_exhaustive]", "try adding #[non_exhaustive]",
sugg, sugg,

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::{Item, ItemKind, VisibilityKind}; use rustc_ast::ast::{Item, ItemKind, VisibilityKind};
use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
@ -62,13 +62,15 @@ impl EarlyLintPass for FieldScopedVisibilityModifiers {
// pub(self) is equivalent to not using pub at all, so we ignore it // pub(self) is equivalent to not using pub at all, so we ignore it
continue; continue;
} }
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
FIELD_SCOPED_VISIBILITY_MODIFIERS, FIELD_SCOPED_VISIBILITY_MODIFIERS,
field.vis.span, field.vis.span,
"scoped visibility modifier on a field", "scoped visibility modifier on a field",
None, |diag| {
"consider making the field private and adding a scoped visibility method for it", diag.help("consider making the field private and adding a scoped visibility method for it");
},
); );
} }
} }

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::numeric_literal; use clippy_utils::numeric_literal;
use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_ast::ast::{self, LitFloatType, LitKind};
use rustc_errors::Applicability; use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FloatTy}; use rustc_middle::ty::{self, FloatTy};
@ -105,32 +105,43 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
if is_whole && !sym_str.contains(['e', 'E']) { if is_whole && !sym_str.contains(['e', 'E']) {
// Normalize the literal by stripping the fractional portion // Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str { if sym_str.split('.').next().unwrap() != float_str {
// If the type suffix is missing the suggestion would be span_lint_and_then(
// incorrectly interpreted as an integer so adding a `.0`
// suffix to prevent that.
if type_suffix.is_none() {
float_str.push_str(".0");
}
span_lint_and_sugg(
cx, cx,
LOSSY_FLOAT_LITERAL, LOSSY_FLOAT_LITERAL,
expr.span, expr.span,
"literal cannot be represented as the underlying type without loss of precision", "literal cannot be represented as the underlying type without loss of precision",
"consider changing the type or replacing it with", |diag| {
numeric_literal::format(&float_str, type_suffix, true), // If the type suffix is missing the suggestion would be
Applicability::MachineApplicable, // incorrectly interpreted as an integer so adding a `.0`
// suffix to prevent that.
if type_suffix.is_none() {
float_str.push_str(".0");
}
diag.span_suggestion_with_style(
expr.span,
"consider changing the type or replacing it with",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways,
);
},
); );
} }
} else if digits > max as usize && float_str.len() < sym_str.len() { } else if digits > max as usize && float_str.len() < sym_str.len() {
span_lint_and_sugg( span_lint_and_then(
cx, cx,
EXCESSIVE_PRECISION, EXCESSIVE_PRECISION,
expr.span, expr.span,
"float has excessive precision", "float has excessive precision",
"consider changing the type or truncating it to", |diag| {
numeric_literal::format(&float_str, type_suffix, true), diag.span_suggestion_with_style(
Applicability::MachineApplicable, expr.span,
"consider changing the type or truncating it to",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways,
);
},
); );
} }
} }

View file

@ -1,9 +1,9 @@
use clippy_utils::consts::Constant::{Int, F32, F64}; use clippy_utils::consts::Constant::{Int, F32, F64};
use clippy_utils::consts::{constant, constant_simple, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{ use clippy_utils::{
eq_expr_value, get_parent_expr, higher, in_constant, is_inherent_method_call, is_no_std_crate, numeric_literal, eq_expr_value, get_parent_expr, higher, is_in_const_context, is_inherent_method_call, is_no_std_crate,
peel_blocks, sugg, numeric_literal, peel_blocks, sugg,
}; };
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
@ -112,7 +112,7 @@ declare_lint_pass!(FloatingPointArithmetic => [
// Returns the specialized log method for a given base if base is constant // Returns the specialized log method for a given base if base is constant
// and is one of 2, 10 and e // and is one of 2, 10 and e
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> { fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
if let Some(value) = constant(cx, cx.typeck_results(), base) { if let Some(value) = ConstEvalCtxt::new(cx).eval(base) {
if F32(2.0) == value || F64(2.0) == value { if F32(2.0) == value || F64(2.0) == value {
return Some("log2"); return Some("log2");
} else if F32(10.0) == value || F64(10.0) == value { } else if F32(10.0) == value || F64(10.0) == value {
@ -182,10 +182,8 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
rhs, rhs,
) = receiver.kind ) = receiver.kind
{ {
let recv = match ( let ecx = ConstEvalCtxt::new(cx);
constant(cx, cx.typeck_results(), lhs), let recv = match (ecx.eval(lhs), ecx.eval(rhs)) {
constant(cx, cx.typeck_results(), rhs),
) {
(Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs, (Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs,
(_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs, (_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs,
_ => return, _ => return,
@ -230,7 +228,7 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> {
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
// Check receiver // Check receiver
if let Some(value) = constant(cx, cx.typeck_results(), receiver) { if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) {
if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
Some("exp") Some("exp")
} else if F32(2.0) == value || F64(2.0) == value { } else if F32(2.0) == value || F64(2.0) == value {
@ -251,7 +249,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
} }
// Check argument // Check argument
if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) { if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value { let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
( (
SUBOPTIMAL_FLOPS, SUBOPTIMAL_FLOPS,
@ -291,7 +289,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
} }
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) { if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
if value == Int(2) { if value == Int(2) {
if let Some(parent) = get_parent_expr(cx, expr) { if let Some(parent) = get_parent_expr(cx, expr) {
if let Some(grandparent) = get_parent_expr(cx, parent) { if let Some(grandparent) = get_parent_expr(cx, parent) {
@ -397,8 +395,9 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
) = &add_rhs.kind ) = &add_rhs.kind
&& lmethod_name.as_str() == "powi" && lmethod_name.as_str() == "powi"
&& rmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"
&& let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1) && let ecx = ConstEvalCtxt::new(cx)
&& let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1) && let Some(lvalue) = ecx.eval(largs_1)
&& let Some(rvalue) = ecx.eval(rargs_1)
&& Int(2) == lvalue && Int(2) == lvalue
&& Int(2) == rvalue && Int(2) == rvalue
{ {
@ -438,7 +437,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs, rhs,
) = expr.kind ) = expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point() && cx.typeck_results().expr_ty(lhs).is_floating_point()
&& let Some(value) = constant(cx, cx.typeck_results(), rhs) && let Some(value) = ConstEvalCtxt::new(cx).eval(rhs)
&& (F32(1.0) == value || F64(1.0) == value) && (F32(1.0) == value || F64(1.0) == value)
&& let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind && let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind
&& cx.typeck_results().expr_ty(self_arg).is_floating_point() && cx.typeck_results().expr_ty(self_arg).is_floating_point()
@ -552,7 +551,7 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
/// Returns true iff expr is some zero literal /// Returns true iff expr is some zero literal
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match constant_simple(cx, cx.typeck_results(), expr) { match ConstEvalCtxt::new(cx).eval_simple(expr) {
Some(Int(i)) => i == 0, Some(Int(i)) => i == 0,
Some(F32(f)) => f == 0.0, Some(F32(f)) => f == 0.0,
Some(F64(f)) => f == 0.0, Some(F64(f)) => f == 0.0,
@ -696,8 +695,9 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
mul_lhs, mul_lhs,
mul_rhs, mul_rhs,
) = &div_lhs.kind ) = &div_lhs.kind
&& let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs) && let ecx = ConstEvalCtxt::new(cx)
&& let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs) && let Some(rvalue) = ecx.eval(div_rhs)
&& let Some(lvalue) = ecx.eval(mul_rhs)
{ {
// TODO: also check for constant values near PI/180 or 180/PI // TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
@ -753,7 +753,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// All of these operations are currently not const and are in std. // All of these operations are currently not const and are in std.
if in_constant(cx, expr.hir_id) { if is_in_const_context(cx) {
return; return;
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_lang_item; use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{higher, match_def_path, paths}; use clippy_utils::{higher, match_def_path, paths};
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource}; use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource};
@ -81,13 +81,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString {
_ => return, _ => return,
}; };
if is_format(cx, arg) { if is_format(cx, arg) {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
FORMAT_PUSH_STRING, FORMAT_PUSH_STRING,
expr.span, expr.span,
"`format!(..)` appended to existing `String`", "`format!(..)` appended to existing `String`",
None, |diag| {
"consider using `write!` to avoid the extra allocation", diag.help("consider using `write!` to avoid the extra allocation");
},
); );
} }
} }

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{in_constant, is_integer_literal}; use clippy_utils::{is_in_const_context, is_integer_literal};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind}; use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -62,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
&& matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_)) && matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_))
// do not lint in constant context, because the suggestion won't work. // do not lint in constant context, because the suggestion won't work.
// NB: keep this check until a new `const_trait_impl` is available and stablized. // NB: keep this check until a new `const_trait_impl` is available and stabilized.
&& !in_constant(cx, exp.hir_id) && !is_in_const_context(cx)
{ {
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind { let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{higher, SpanlessEq}; use clippy_utils::{eq_expr_value, higher};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_errors::Diag; use rustc_errors::Diag;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
@ -51,53 +51,45 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
if_else: Some(if_else), if_else: Some(if_else),
.. ..
}) = higher::IfLet::hir(cx, expr) }) = higher::IfLet::hir(cx, expr)
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None))
&& let Some(arm_mutex) =
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex)))
{ {
let is_mutex_lock = |e: &'tcx Expr<'tcx>| { let diag = |diag: &mut Diag<'_, ()>| {
if let Some(mutex) = is_mutex_lock_call(cx, e) { diag.span_label(
ControlFlow::Break(mutex) op_mutex.span,
} else { "this Mutex will remain locked for the entire `if let`-block...",
ControlFlow::Continue(()) );
} diag.span_label(
arm_mutex.span,
"... and is tried to lock again here, which will always deadlock.",
);
diag.help("move the lock call outside of the `if let ...` expression");
}; };
span_lint_and_then(
let op_mutex = for_each_expr_without_closures(let_expr, is_mutex_lock); cx,
if let Some(op_mutex) = op_mutex { IF_LET_MUTEX,
let arm_mutex = for_each_expr_without_closures((if_then, if_else), is_mutex_lock); expr.span,
if let Some(arm_mutex) = arm_mutex "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
&& SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex) diag,
{ );
let diag = |diag: &mut Diag<'_, ()>| {
diag.span_label(
op_mutex.span,
"this Mutex will remain locked for the entire `if let`-block...",
);
diag.span_label(
arm_mutex.span,
"... and is tried to lock again here, which will always deadlock.",
);
diag.help("move the lock call outside of the `if let ...` expression");
};
span_lint_and_then(
cx,
IF_LET_MUTEX,
expr.span,
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
diag,
);
}
}
} }
} }
} }
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { fn mutex_lock_call<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op_mutex: Option<&'tcx Expr<'_>>,
) -> ControlFlow<&'tcx Expr<'tcx>> {
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
&& path.ident.as_str() == "lock" && path.ident.as_str() == "lock"
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& is_type_diagnostic_item(cx, ty, sym::Mutex) && is_type_diagnostic_item(cx, ty, sym::Mutex)
&& op_mutex.map_or(true, |op| eq_expr_value(cx, self_arg, op))
{ {
Some(self_arg) ControlFlow::Break(self_arg)
} else { } else {
None ControlFlow::Continue(())
} }
} }

View file

@ -1,7 +1,7 @@
//! lint on if branches that could be swapped so no `!` operation is necessary //! lint on if branches that could be swapped so no `!` operation is necessary
//! on the condition //! on the condition
use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_else_clause; use clippy_utils::is_else_clause;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
@ -49,7 +49,7 @@ declare_clippy_lint! {
declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]); declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]);
fn is_zero_const(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { fn is_zero_const(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
if let Some(value) = constant_simple(cx, cx.typeck_results(), expr) { if let Some(value) = ConstEvalCtxt::new(cx).eval_simple(expr) {
return Constant::Int(0) == value; return Constant::Int(0) == value;
} }
false false

View file

@ -1,10 +1,12 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{contains_return, higher, in_constant, is_else_clause, is_res_lang_ctor, path_res, peel_blocks}; use clippy_utils::{
contains_return, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, path_res, peel_blocks,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
@ -76,37 +78,44 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
&& is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome)
&& is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone)
&& !is_else_clause(cx.tcx, expr) && !is_else_clause(cx.tcx, expr)
&& !in_constant(cx, expr.hir_id) && !is_in_const_context(cx)
&& !in_external_macro(cx.sess(), expr.span) && !in_external_macro(cx.sess(), expr.span)
&& self.msrv.meets(msrvs::BOOL_THEN) && self.msrv.meets(msrvs::BOOL_THEN)
&& !contains_return(then_block.stmts) && !contains_return(then_block.stmts)
{ {
let mut app = Applicability::Unspecified;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
.maybe_par()
.to_string();
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let mut method_body = if then_block.stmts.is_empty() {
arg_snip.into_owned()
} else {
format!("{{ /* snippet */ {arg_snip} }}")
};
let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) {
"then_some" "then_some"
} else { } else {
method_body.insert_str(0, "|| ");
"then" "then"
}; };
let help = span_lint_and_then(
format!("consider using `bool::{method_name}` like: `{cond_snip}.{method_name}({method_body})`",);
span_lint_and_help(
cx, cx,
IF_THEN_SOME_ELSE_NONE, IF_THEN_SOME_ELSE_NONE,
expr.span, expr.span,
format!("this could be simplified with `bool::{method_name}`"), format!("this could be simplified with `bool::{method_name}`"),
None, |diag| {
help, let mut app = Applicability::Unspecified;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
.maybe_par()
.to_string();
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let method_body = if let Some(first_stmt) = then_block.stmts.first() {
let (block_snippet, _) =
snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app);
let closure = if method_name == "then" { "|| " } else { "" };
format!("{closure} {{ {block_snippet}; {arg_snip} }}")
} else {
arg_snip.into_owned()
};
diag.span_suggestion(
expr.span,
"try",
format!("{cond_snip}.{method_name}({method_body})"),
app,
);
},
); );
} }
} }

View file

@ -1,7 +1,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use rustc_errors::Diag; use rustc_errors::{Applicability, Diag};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor}; use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor};
use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind}; use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
@ -13,7 +13,7 @@ use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::Span; use rustc_span::Span;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet, IntoSpan, SpanRangeExt}; use clippy_utils::source::{snippet, IntoSpan, SpanRangeExt};
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
@ -77,33 +77,32 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
&generics_snip[1..generics_snip.len() - 1] &generics_snip[1..generics_snip.len() - 1]
}; };
multispan_sugg( let mut suggestions = vec![
diag, (
"consider adding a type parameter", generics_suggestion_span,
vec![ format!(
( "<{generics_snip}{}S: ::std::hash::BuildHasher{}>",
generics_suggestion_span, if generics_snip.is_empty() { "" } else { ", " },
format!( if vis.suggestions.is_empty() {
"<{generics_snip}{}S: ::std::hash::BuildHasher{}>", ""
if generics_snip.is_empty() { "" } else { ", " }, } else {
if vis.suggestions.is_empty() { // request users to add `Default` bound so that generic constructors can be used
"" " + Default"
} else { },
// request users to add `Default` bound so that generic constructors can be used
" + Default"
},
),
), ),
( ),
target.span(), (
format!("{}<{}, S>", target.type_name(), target.type_arguments(),), target.span(),
), format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
], ),
); ];
suggestions.extend(vis.suggestions);
if !vis.suggestions.is_empty() { diag.multipart_suggestion(
multispan_sugg(diag, "...and use generic constructor", vis.suggestions); "add a type parameter for `BuildHasher`",
} suggestions,
Applicability::MaybeIncorrect,
);
} }
if !cx.effective_visibilities.is_exported(item.owner_id.def_id) { if !cx.effective_visibilities.is_exported(item.owner_id.def_id) {

View file

@ -3,7 +3,7 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context, wal
use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro}; use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_errors::Applicability; use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -45,8 +45,6 @@ declare_clippy_lint! {
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) { fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app);
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
IMPLICIT_RETURN, IMPLICIT_RETURN,
@ -54,14 +52,20 @@ fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
span, span,
"missing `return` statement", "missing `return` statement",
|diag| { |diag| {
diag.span_suggestion(span, "add `return` as shown", format!("return {snip}"), app); let mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app);
diag.span_suggestion_with_style(
span,
"add `return` as shown",
format!("return {snip}"),
app,
SuggestionStyle::ShowAlways,
);
}, },
); );
} }
fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) { fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
IMPLICIT_RETURN, IMPLICIT_RETURN,
@ -69,11 +73,14 @@ fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, exp
break_span, break_span,
"missing `return` statement", "missing `return` statement",
|diag| { |diag| {
diag.span_suggestion( let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
diag.span_suggestion_with_style(
break_span, break_span,
"change `break` to `return` as shown", "change `break` to `return` as shown",
format!("return {snip}"), format!("return {snip}"),
app, app,
SuggestionStyle::ShowAlways,
); );
}, },
); );

View file

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr; use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
@ -117,11 +117,11 @@ fn get_int_max(ty: Ty<'_>) -> Option<u128> {
fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> { fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> {
if let ExprKind::Binary(op, l, r) = expr.kind { if let ExprKind::Binary(op, l, r) = expr.kind {
let tr = cx.typeck_results(); let ecx = ConstEvalCtxt::new(cx);
if let Some(Constant::Int(c)) = constant(cx, tr, r) { if let Some(Constant::Int(c)) = ecx.eval(r) {
return Some((c, op.node, l)); return Some((c, op.node, l));
}; };
if let Some(Constant::Int(c)) = constant(cx, tr, l) { if let Some(Constant::Int(c)) = ecx.eval(l) {
return Some((c, invert_op(op.node)?, r)); return Some((c, invert_op(op.node)?, r));
} }
} }

View file

@ -55,7 +55,6 @@ impl IncompatibleMsrv {
} }
} }
#[allow(clippy::cast_lossless)]
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion { fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
if let Some(version) = self.is_above_msrv.get(&def_id) { if let Some(version) = self.is_above_msrv.get(&def_id) {
return *version; return *version;
@ -67,9 +66,9 @@ impl IncompatibleMsrv {
since: StableSince::Version(version), since: StableSince::Version(version),
.. ..
} => Some(RustcVersion::new( } => Some(RustcVersion::new(
version.major as _, version.major.into(),
version.minor as _, version.minor.into(),
version.patch as _, version.patch.into(),
)), )),
_ => None, _ => None,
}) { }) {

View file

@ -1,6 +1,6 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLet; use clippy_utils::higher::IfLet;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
@ -246,7 +246,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
&& let parent_id = cx.tcx.parent_hir_id(expr.hir_id) && let parent_id = cx.tcx.parent_hir_id(expr.hir_id)
&& let hir::Node::Expr(parent_expr) = cx.tcx.hir_node(parent_id) && let hir::Node::Expr(parent_expr) = cx.tcx.hir_node(parent_id)
&& let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind && let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind
&& let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr) && let Some(Constant::Int(index_value)) = ConstEvalCtxt::new(cx).eval(index_expr)
&& let Ok(index_value) = index_value.try_into() && let Ok(index_value) = index_value.try_into()
&& index_value < max_suggested_slice && index_value < max_suggested_slice

View file

@ -1,7 +1,7 @@
//! lint on indexing and slicing operations //! lint on indexing and slicing operations
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::ty::{deref_chain, get_adt_inherent_method}; use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
use clippy_utils::{higher, is_from_proc_macro}; use clippy_utils::{higher, is_from_proc_macro};
@ -70,8 +70,6 @@ declare_clippy_lint! {
/// ///
/// Use instead: /// Use instead:
/// ```no_run /// ```no_run
/// # #![allow(unused)]
///
/// # let x = vec![0; 5]; /// # let x = vec![0; 5];
/// # let y = [0, 1, 2, 3]; /// # let y = [0, 1, 2, 3];
/// x.get(2); /// x.get(2);
@ -179,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
return; return;
} }
// Index is a constant uint. // Index is a constant uint.
if let Some(constant) = constant(cx, cx.typeck_results(), index) { if let Some(constant) = ConstEvalCtxt::new(cx).eval(index) {
// only `usize` index is legal in rust array index // only `usize` index is legal in rust array index
// leave other type to rustc // leave other type to rustc
if let Constant::Int(off) = constant if let Constant::Int(off) = constant
@ -217,14 +215,15 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
/// Returns a tuple of options with the start and end (exclusive) values of /// Returns a tuple of options with the start and end (exclusive) values of
/// the range. If the start or end is not constant, None is returned. /// the range. If the start or end is not constant, None is returned.
fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option<u128>, Option<u128>) { fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option<u128>, Option<u128>) {
let s = range.start.map(|expr| constant(cx, cx.typeck_results(), expr)); let ecx = ConstEvalCtxt::new(cx);
let s = range.start.map(|expr| ecx.eval(expr));
let start = match s { let start = match s {
Some(Some(Constant::Int(x))) => Some(x), Some(Some(Constant::Int(x))) => Some(x),
Some(_) => None, Some(_) => None,
None => Some(0), None => Some(0),
}; };
let e = range.end.map(|expr| constant(cx, cx.typeck_results(), expr)); let e = range.end.map(|expr| ecx.eval(expr));
let end = match e { let end = match e {
Some(Some(Constant::Int(x))) => { Some(Some(Constant::Int(x))) => {
if range.limits == RangeLimits::Closed { if range.limits == RangeLimits::Closed {

View file

@ -41,7 +41,6 @@ declare_clippy_lint! {
/// ### Example /// ### Example
/// ```no_run /// ```no_run
/// let infinite_iter = 0..; /// let infinite_iter = 0..;
/// # #[allow(unused)]
/// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5));
/// ``` /// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]

View file

@ -1,6 +1,6 @@
//! lint on inherent implementations //! lint on inherent implementations
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_lint_allowed; use clippy_utils::is_lint_allowed;
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
@ -105,13 +105,14 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
// `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first.
lint_spans.sort_by_key(|x| x.0.lo()); lint_spans.sort_by_key(|x| x.0.lo());
for (span, first_span) in lint_spans { for (span, first_span) in lint_spans {
span_lint_and_note( span_lint_and_then(
cx, cx,
MULTIPLE_INHERENT_IMPL, MULTIPLE_INHERENT_IMPL,
span, span,
"multiple implementations of this structure", "multiple implementations of this structure",
Some(first_span), |diag| {
"first implementation here", diag.span_note(first_span, "first implementation here");
},
); );
} }
} }

View file

@ -7,7 +7,7 @@ use rustc_span::Span;
use clippy_utils::comparisons; use clippy_utils::comparisons;
use clippy_utils::comparisons::Rel; use clippy_utils::comparisons::Rel;
use clippy_utils::consts::{constant_full_int, FullInt}; use clippy_utils::consts::{ConstEvalCtxt, FullInt};
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
@ -95,7 +95,7 @@ fn upcast_comparison_bounds_err<'tcx>(
invert: bool, invert: bool,
) { ) {
if let Some((lb, ub)) = lhs_bounds { if let Some((lb, ub)) = lhs_bounds {
if let Some(norm_rhs_val) = constant_full_int(cx, cx.typeck_results(), rhs) { if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) {
if rel == Rel::Eq || rel == Rel::Ne { if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub { if norm_rhs_val < lb || norm_rhs_val > ub {
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);

View file

@ -1,5 +1,5 @@
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::macros::root_macro_call_first_node;
use rustc_ast::LitKind; use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::{Expr, ExprKind};
@ -66,16 +66,18 @@ impl LateLintPass<'_> for LargeIncludeFile {
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
{ {
span_lint_and_note( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
LARGE_INCLUDE_FILE, LARGE_INCLUDE_FILE,
expr.span.source_callsite(), expr.span.source_callsite(),
"attempted to include a large file", "attempted to include a large file",
None, |diag| {
format!( diag.note(format!(
"the configuration allows a maximum size of {} bytes", "the configuration allows a maximum size of {} bytes",
self.max_file_size self.max_file_size
), ));
},
); );
} }
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type}; use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths}; use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths};
use rustc_hir::{LetStmt, LocalSource, PatKind}; use rustc_hir::{LetStmt, LocalSource, PatKind};
@ -149,43 +149,53 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
}); });
if contains_sync_guard { if contains_sync_guard {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
LET_UNDERSCORE_LOCK, LET_UNDERSCORE_LOCK,
local.span, local.span,
"non-binding `let` on a synchronization lock", "non-binding `let` on a synchronization lock",
None, |diag| {
"consider using an underscore-prefixed named \ diag.help(
binding or dropping explicitly with `std::mem::drop`", "consider using an underscore-prefixed named \
binding or dropping explicitly with `std::mem::drop`",
);
},
); );
} else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() } else if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
&& implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[]) && implements_trait(cx, cx.typeck_results().expr_ty(init), future_trait_def_id, &[])
{ {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_FUTURE,
local.span, local.span,
"non-binding `let` on a future", "non-binding `let` on a future",
None, |diag| {
"consider awaiting the future or dropping explicitly with `std::mem::drop`", diag.help("consider awaiting the future or dropping explicitly with `std::mem::drop`");
},
); );
} else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) { } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_MUST_USE,
local.span, local.span,
"non-binding `let` on an expression with `#[must_use]` type", "non-binding `let` on an expression with `#[must_use]` type",
None, |diag| {
"consider explicitly using expression value", diag.help("consider explicitly using expression value");
},
); );
} else if is_must_use_func_call(cx, init) { } else if is_must_use_func_call(cx, init) {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_MUST_USE,
local.span, local.span,
"non-binding `let` on a result of a `#[must_use]` function", "non-binding `let` on a result of a `#[must_use]` function",
None, |diag| {
"consider explicitly using function result", diag.help("consider explicitly using function result");
},
); );
} }
@ -204,18 +214,22 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
return; return;
} }
span_lint_and_help( span_lint_and_then(
cx, cx,
LET_UNDERSCORE_UNTYPED, LET_UNDERSCORE_UNTYPED,
local.span, local.span,
"non-binding `let` without a type annotation", "non-binding `let` without a type annotation",
Some(Span::new( |diag| {
local.pat.span.hi(), diag.span_help(
local.pat.span.hi() + BytePos(1), Span::new(
local.pat.span.ctxt(), local.pat.span.hi(),
local.pat.span.parent(), local.pat.span.hi() + BytePos(1),
)), local.pat.span.ctxt(),
"consider adding a type annotation", local.pat.span.parent(),
),
"consider adding a type annotation",
);
},
); );
} }
} }

View file

@ -1,78 +0,0 @@
// This file was generated by `cargo dev update_lints`.
// Use that command to update this file and do not edit by hand.
// Manual edits will be overwritten.
{
store.register_removed(
"clippy::should_assert_eq",
"`assert!()` will be more flexible with RFC 2011",
);
store.register_removed(
"clippy::extend_from_slice",
"`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
);
store.register_removed(
"clippy::range_step_by_zero",
"`iterator.step_by(0)` panics nowadays",
);
store.register_removed(
"clippy::unstable_as_slice",
"`Vec::as_slice` has been stabilized in 1.7",
);
store.register_removed(
"clippy::unstable_as_mut_slice",
"`Vec::as_mut_slice` has been stabilized in 1.7",
);
store.register_removed(
"clippy::misaligned_transmute",
"this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
);
store.register_removed(
"clippy::assign_ops",
"using compound assignment operators (e.g., `+=`) is harmless",
);
store.register_removed(
"clippy::if_let_redundant_pattern_matching",
"this lint has been changed to redundant_pattern_matching",
);
store.register_removed(
"clippy::unsafe_vector_initialization",
"the replacement suggested by this lint had substantially different behavior",
);
store.register_removed(
"clippy::unused_collect",
"`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint",
);
store.register_removed(
"clippy::replace_consts",
"associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants",
);
store.register_removed(
"clippy::regex_macro",
"the regex! macro has been removed from the regex crate in 2018",
);
store.register_removed(
"clippy::find_map",
"this lint has been replaced by `manual_find_map`, a more specific lint",
);
store.register_removed(
"clippy::filter_map",
"this lint has been replaced by `manual_filter_map`, a more specific lint",
);
store.register_removed(
"clippy::pub_enum_variant_names",
"set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items",
);
store.register_removed(
"clippy::wrong_pub_self_convention",
"set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items",
);
store.register_removed(
"clippy::maybe_misused_cfg",
"this lint has been replaced by `unexpected_cfgs`",
);
store.register_removed(
"clippy::mismatched_target_os",
"this lint has been replaced by `unexpected_cfgs`",
);
}

View file

@ -13,7 +13,6 @@
#![feature(stmt_expr_attributes)] #![feature(stmt_expr_attributes)]
#![feature(unwrap_infallible)] #![feature(unwrap_infallible)]
#![recursion_limit = "512"] #![recursion_limit = "512"]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![allow( #![allow(
clippy::missing_docs_in_private_items, clippy::missing_docs_in_private_items,
clippy::must_use_candidate, clippy::must_use_candidate,
@ -64,13 +63,11 @@ extern crate clippy_utils;
#[macro_use] #[macro_use]
extern crate declare_clippy_lint; extern crate declare_clippy_lint;
#[cfg(feature = "internal")]
pub mod deprecated_lints;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
mod utils; mod utils;
mod declared_lints; mod declared_lints;
mod renamed_lints; mod deprecated_lints;
// begin lints modules, do not remove this comment, its used in `update_lints` // begin lints modules, do not remove this comment, its used in `update_lints`
mod absolute_paths; mod absolute_paths;
@ -372,6 +369,7 @@ mod unsafe_removed_from_name;
mod unused_async; mod unused_async;
mod unused_io_amount; mod unused_io_amount;
mod unused_peekable; mod unused_peekable;
mod unused_result_ok;
mod unused_rounding; mod unused_rounding;
mod unused_self; mod unused_self;
mod unused_unit; mod unused_unit;
@ -495,7 +493,7 @@ pub fn explain(name: &str) -> i32 {
// Check if the lint has configuration // Check if the lint has configuration
let mut mdconf = get_configuration_metadata(); let mut mdconf = get_configuration_metadata();
let name = name.to_ascii_lowercase(); let name = name.to_ascii_lowercase();
mdconf.retain(|cconf| cconf.lints.contains(&name)); mdconf.retain(|cconf| cconf.lints.contains(&&*name));
if !mdconf.is_empty() { if !mdconf.is_empty() {
println!("### Configuration for {}:\n", info.lint.name_lower()); println!("### Configuration for {}:\n", info.lint.name_lower());
for conf in mdconf { for conf in mdconf {
@ -531,10 +529,14 @@ fn register_categories(store: &mut rustc_lint::LintStore) {
/// Used in `./src/driver.rs`. /// Used in `./src/driver.rs`.
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
register_removed_non_tool_lints(store);
register_categories(store); register_categories(store);
include!("lib.deprecated.rs"); for (old_name, new_name) in deprecated_lints::RENAMED {
store.register_renamed(old_name, new_name);
}
for (name, reason) in deprecated_lints::DEPRECATED {
store.register_removed(name, reason);
}
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
{ {
@ -669,6 +671,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf))); store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf)));
store.register_late_pass(|_| Box::new(missing_inline::MissingInline)); store.register_late_pass(|_| Box::new(missing_inline::MissingInline));
store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems)); store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems));
store.register_late_pass(|_| Box::new(unused_result_ok::UnusedResultOk));
store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk)); store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk));
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));
@ -818,7 +821,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(conf))); store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(conf)));
store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate));
store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); store.register_late_pass(move |_| Box::new(operators::Operators::new(conf)));
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default()); store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf)));
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf))); store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf)));
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf)));
@ -907,68 +910,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf)));
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf)));
store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers));
store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains)); store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert));
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
// add lints here, do not remove this comment, it's used in `new_lint` // add lints here, do not remove this comment, it's used in `new_lint`
} }
#[rustfmt::skip]
fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
store.register_removed(
"should_assert_eq",
"`assert!()` will be more flexible with RFC 2011",
);
store.register_removed(
"extend_from_slice",
"`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
);
store.register_removed(
"range_step_by_zero",
"`iterator.step_by(0)` panics nowadays",
);
store.register_removed(
"unstable_as_slice",
"`Vec::as_slice` has been stabilized in 1.7",
);
store.register_removed(
"unstable_as_mut_slice",
"`Vec::as_mut_slice` has been stabilized in 1.7",
);
store.register_removed(
"misaligned_transmute",
"this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
);
store.register_removed(
"assign_ops",
"using compound assignment operators (e.g., `+=`) is harmless",
);
store.register_removed(
"if_let_redundant_pattern_matching",
"this lint has been changed to redundant_pattern_matching",
);
store.register_removed(
"unsafe_vector_initialization",
"the replacement suggested by this lint had substantially different behavior",
);
store.register_removed(
"reverse_range_loop",
"this lint is now included in reversed_empty_ranges",
);
}
/// Register renamed lints.
///
/// Used in `./src/driver.rs`.
pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
for (old_name, new_name) in renamed_lints::RENAMED_LINTS {
ls.register_renamed(old_name, new_name);
}
}
// only exists to let the dogfood integration test works.
// Don't run clippy as an executable directly
#[allow(dead_code)]
fn main() {
panic!("Please use the cargo-clippy executable");
}

View file

@ -2,13 +2,13 @@
//! floating-point literal expressions. //! floating-point literal expressions.
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::numeric_literal::{NumericLiteral, Radix}; use clippy_utils::numeric_literal::{NumericLiteral, Radix};
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Expr, ExprKind, LitKind}; use rustc_ast::ast::{Expr, ExprKind, LitKind};
use rustc_ast::token; use rustc_ast::token;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::Span; use rustc_span::Span;
@ -159,63 +159,39 @@ enum WarningType {
} }
impl WarningType { impl WarningType {
fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: Span) { fn lint_and_text(&self) -> (&'static Lint, &'static str, &'static str) {
match self { match self {
Self::MistypedLiteralSuffix => span_lint_and_sugg( Self::MistypedLiteralSuffix => (
cx,
MISTYPED_LITERAL_SUFFIXES, MISTYPED_LITERAL_SUFFIXES,
span,
"mistyped literal suffix", "mistyped literal suffix",
"did you mean to write", "did you mean to write",
suggested_format,
Applicability::MaybeIncorrect,
), ),
Self::UnreadableLiteral => span_lint_and_sugg( Self::UnreadableLiteral => (UNREADABLE_LITERAL, "long literal lacking separators", "consider"),
cx, Self::LargeDigitGroups => (LARGE_DIGIT_GROUPS, "digit groups should be smaller", "consider"),
UNREADABLE_LITERAL, Self::InconsistentDigitGrouping => (
span,
"long literal lacking separators",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
Self::LargeDigitGroups => span_lint_and_sugg(
cx,
LARGE_DIGIT_GROUPS,
span,
"digit groups should be smaller",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
Self::InconsistentDigitGrouping => span_lint_and_sugg(
cx,
INCONSISTENT_DIGIT_GROUPING, INCONSISTENT_DIGIT_GROUPING,
span,
"digits grouped inconsistently by underscores", "digits grouped inconsistently by underscores",
"consider", "consider",
suggested_format,
Applicability::MachineApplicable,
), ),
Self::DecimalRepresentation => span_lint_and_sugg( Self::DecimalRepresentation => (
cx,
DECIMAL_LITERAL_REPRESENTATION, DECIMAL_LITERAL_REPRESENTATION,
span,
"integer literal has a better hexadecimal representation", "integer literal has a better hexadecimal representation",
"consider", "consider",
suggested_format,
Applicability::MachineApplicable,
), ),
Self::UnusualByteGroupings => span_lint_and_sugg( Self::UnusualByteGroupings => (
cx,
UNUSUAL_BYTE_GROUPINGS, UNUSUAL_BYTE_GROUPINGS,
span,
"digits of hex, binary or octal literal not in groups of equal size", "digits of hex, binary or octal literal not in groups of equal size",
"consider", "consider",
suggested_format,
Applicability::MachineApplicable,
), ),
}; }
}
fn display(&self, num_lit: &NumericLiteral<'_>, cx: &EarlyContext<'_>, span: Span) {
let (lint, message, try_msg) = self.lint_and_text();
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, lint, span, message, |diag| {
diag.span_suggestion(span, try_msg, num_lit.format(), Applicability::MaybeIncorrect);
});
} }
} }
@ -293,7 +269,7 @@ impl LiteralDigitGrouping {
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true, WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true,
}; };
if should_warn { if should_warn {
warning_type.display(num_lit.format(), cx, span); warning_type.display(&num_lit, cx, span);
} }
} }
} }
@ -346,11 +322,14 @@ impl LiteralDigitGrouping {
} }
} }
*part = main_part; *part = main_part;
let mut sugg = num_lit.format(); let (lint, message, try_msg) = WarningType::MistypedLiteralSuffix.lint_and_text();
sugg.push('_'); span_lint_and_then(cx, lint, span, message, |diag| {
sugg.push(missing_char); let mut sugg = num_lit.format();
sugg.push_str(last_group); sugg.push('_');
WarningType::MistypedLiteralSuffix.display(sugg, cx, span); sugg.push(missing_char);
sugg.push_str(last_group);
diag.span_suggestion(span, try_msg, sugg, Applicability::MaybeIncorrect);
});
false false
} else { } else {
true true
@ -471,7 +450,7 @@ impl DecimalLiteralRepresentation {
let hex = format!("{val:#X}"); let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| { let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, span); warning_type.display(&num_lit, cx, span);
}); });
} }
} }

View file

@ -2,6 +2,7 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_enclosing_block, is_integer_const}; use clippy_utils::{get_enclosing_block, is_integer_const};
use rustc_ast::Label;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::intravisit::{walk_block, walk_expr};
use rustc_hir::{Expr, Pat}; use rustc_hir::{Expr, Pat};
@ -17,6 +18,7 @@ pub(super) fn check<'tcx>(
arg: &'tcx Expr<'_>, arg: &'tcx Expr<'_>,
body: &'tcx Expr<'_>, body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>,
label: Option<Label>,
) { ) {
// Look for variables that are incremented once per loop iteration. // Look for variables that are incremented once per loop iteration.
let mut increment_visitor = IncrementVisitor::new(cx); let mut increment_visitor = IncrementVisitor::new(cx);
@ -34,7 +36,7 @@ pub(super) fn check<'tcx>(
{ {
let mut applicability = Applicability::MaybeIncorrect; let mut applicability = Applicability::MaybeIncorrect;
let span = expr.span.with_hi(arg.span.hi()); let span = expr.span.with_hi(arg.span.hi());
let loop_label = label.map_or(String::new(), |l| format!("{}: ", l.ident.name));
let int_name = match ty.map(Ty::kind) { let int_name = match ty.map(Ty::kind) {
// usize or inferred // usize or inferred
Some(ty::Uint(UintTy::Usize)) | None => { Some(ty::Uint(UintTy::Usize)) | None => {
@ -45,7 +47,7 @@ pub(super) fn check<'tcx>(
format!("the variable `{name}` is used as a loop counter"), format!("the variable `{name}` is used as a loop counter"),
"consider using", "consider using",
format!( format!(
"for ({name}, {}) in {}.enumerate()", "{loop_label}for ({name}, {}) in {}.enumerate()",
snippet_with_applicability(cx, pat.span, "item", &mut applicability), snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability), make_iterator_snippet(cx, arg, &mut applicability),
), ),
@ -68,7 +70,7 @@ pub(super) fn check<'tcx>(
span, span,
"consider using", "consider using",
format!( format!(
"for ({name}, {}) in (0_{int_name}..).zip({})", "{loop_label}for ({name}, {}) in (0_{int_name}..).zip({})",
snippet_with_applicability(cx, pat.span, "item", &mut applicability), snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability), make_iterator_snippet(cx, arg, &mut applicability),
), ),

View file

@ -1,8 +1,9 @@
use super::FOR_KV_MAP; use super::FOR_KV_MAP;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{pat_is_wild, sugg}; use clippy_utils::{pat_is_wild, sugg};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
@ -40,13 +41,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx
format!("you seem to want to iterate on a map's {kind}s"), format!("you seem to want to iterate on a map's {kind}s"),
|diag| { |diag| {
let map = sugg::Sugg::hir(cx, arg, "map"); let map = sugg::Sugg::hir(cx, arg, "map");
multispan_sugg( diag.multipart_suggestion(
diag,
"use the corresponding method", "use the corresponding method",
vec![ vec![
(pat_span, snippet(cx, new_pat_span, kind).into_owned()), (pat_span, snippet(cx, new_pat_span, kind).into_owned()),
(arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_par())), (arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_par())),
], ],
Applicability::MachineApplicable,
); );
}, },
); );

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::{match_def_path, paths, SpanlessEq}; use clippy_utils::{match_def_path, paths, SpanlessEq};
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -38,11 +38,10 @@ fn report_lint(cx: &LateContext<'_>, pop_span: Span, pop_stmt_kind: PopStmt<'_>,
}; };
let loop_replacement = format!("while let Some({}) = {}.pop()", pat, snippet(cx, receiver_span, "..")); let loop_replacement = format!("while let Some({}) = {}.pop()", pat, snippet(cx, receiver_span, ".."));
multispan_sugg_with_applicability( diag.multipart_suggestion(
diag,
"consider using a `while..let` loop", "consider using a `while..let` loop",
vec![(loop_span, loop_replacement), (pop_span, pop_replacement)],
Applicability::MachineApplicable, Applicability::MachineApplicable,
[(loop_span, loop_replacement), (pop_span, pop_replacement)],
); );
}, },
); );

View file

@ -25,6 +25,7 @@ mod while_let_on_iterator;
use clippy_config::msrvs::Msrv; use clippy_config::msrvs::Msrv;
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::higher; use clippy_utils::higher;
use rustc_ast::Label;
use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
@ -448,7 +449,7 @@ declare_clippy_lint! {
#[clippy::version = "1.80.0"] #[clippy::version = "1.80.0"]
pub WHILE_FLOAT, pub WHILE_FLOAT,
nursery, nursery,
"while loops comaparing floating point values" "while loops comparing floating point values"
} }
declare_clippy_lint! { declare_clippy_lint! {
@ -760,6 +761,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
body, body,
loop_id, loop_id,
span, span,
label,
}) = for_loop }) = for_loop
{ {
// we don't want to check expanded macros // we don't want to check expanded macros
@ -768,7 +770,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
if body.span.from_expansion() { if body.span.from_expansion() {
return; return;
} }
self.check_for_loop(cx, pat, arg, body, expr, span); self.check_for_loop(cx, pat, arg, body, expr, span, label);
if let ExprKind::Block(block, _) = body.kind { if let ExprKind::Block(block, _) = body.kind {
never_loop::check(cx, block, loop_id, span, for_loop.as_ref()); never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
} }
@ -808,6 +810,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
} }
impl Loops { impl Loops {
#[allow(clippy::too_many_arguments)]
fn check_for_loop<'tcx>( fn check_for_loop<'tcx>(
&self, &self,
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
@ -816,11 +819,12 @@ impl Loops {
body: &'tcx Expr<'_>, body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>,
span: Span, span: Span,
label: Option<Label>,
) { ) {
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr); let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
if !is_manual_memcpy_triggered { if !is_manual_memcpy_triggered {
needless_range_loop::check(cx, pat, arg, body, expr); needless_range_loop::check(cx, pat, arg, body, expr);
explicit_counter_loop::check(cx, pat, arg, body, expr); explicit_counter_loop::check(cx, pat, arg, body, expr, label);
} }
self.check_for_loop_arg(cx, pat, arg); self.check_for_loop_arg(cx, pat, arg);
for_kv_map::check(cx, pat, arg, body); for_kv_map::check(cx, pat, arg, body);

View file

@ -1,11 +1,12 @@
use super::NEEDLESS_RANGE_LOOP; use super::NEEDLESS_RANGE_LOOP;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method; use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used; use clippy_utils::visitors::is_local_used;
use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq}; use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq};
use rustc_ast::ast; use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BinOpKind, BorrowKind, Closure, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}; use rustc_hir::{BinOpKind, BorrowKind, Closure, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath};
@ -145,8 +146,7 @@ pub(super) fn check<'tcx>(
arg.span, arg.span,
format!("the loop variable `{}` is used to index `{indexed}`", ident.name), format!("the loop variable `{}` is used to index `{indexed}`", ident.name),
|diag| { |diag| {
multispan_sugg( diag.multipart_suggestion(
diag,
"consider using an iterator and enumerate()", "consider using an iterator and enumerate()",
vec![ vec![
(pat.span, format!("({}, <item>)", ident.name)), (pat.span, format!("({}, <item>)", ident.name)),
@ -155,6 +155,7 @@ pub(super) fn check<'tcx>(
format!("{indexed}.{method}().enumerate(){method_1}{method_2}"), format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
), ),
], ],
Applicability::HasPlaceholders,
); );
}, },
); );
@ -171,10 +172,10 @@ pub(super) fn check<'tcx>(
arg.span, arg.span,
format!("the loop variable `{}` is only used to index `{indexed}`", ident.name), format!("the loop variable `{}` is only used to index `{indexed}`", ident.name),
|diag| { |diag| {
multispan_sugg( diag.multipart_suggestion(
diag,
"consider using an iterator", "consider using an iterator",
vec![(pat.span, "<item>".to_string()), (arg.span, repl)], vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
Applicability::HasPlaceholders,
); );
}, },
); );

View file

@ -1,7 +1,8 @@
use super::UNUSED_ENUMERATE_INDEX; use super::UNUSED_ENUMERATE_INDEX;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::{pat_is_wild, sugg}; use clippy_utils::{pat_is_wild, sugg};
use rustc_errors::Applicability;
use rustc_hir::def::DefKind; use rustc_hir::def::DefKind;
use rustc_hir::{Expr, ExprKind, Pat, PatKind}; use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -28,13 +29,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_
"you seem to use `.enumerate()` and immediately discard the index", "you seem to use `.enumerate()` and immediately discard the index",
|diag| { |diag| {
let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter");
multispan_sugg( diag.multipart_suggestion(
diag,
"remove the `.enumerate()` call", "remove the `.enumerate()` call",
vec![ vec![
(pat.span, snippet(cx, elem.span, "..").into_owned()), (pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()), (arg.span, base_iter.to_string()),
], ],
Applicability::MachineApplicable,
); );
}, },
); );

View file

@ -1,5 +1,5 @@
use super::WHILE_IMMUTABLE_CONDITION; use super::WHILE_IMMUTABLE_CONDITION;
use clippy_utils::consts::constant; use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
@ -10,7 +10,7 @@ use rustc_lint::LateContext;
use std::ops::ControlFlow; use std::ops::ControlFlow;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
if constant(cx, cx.typeck_results(), cond).is_some() { if ConstEvalCtxt::new(cx).eval(cond).is_some() {
// A pure constant condition (e.g., `while false`) is not linted. // A pure constant condition (e.g., `while false`) is not linted.
return; return;
} }

View file

@ -14,7 +14,7 @@ use rustc_span::symbol::sym;
use rustc_span::Symbol; use rustc_span::Symbol;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(higher::WhileLet { if_then, let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) if let Some(higher::WhileLet { if_then, let_pat, let_expr, label, .. }) = higher::WhileLet::hir(expr)
// check for `Some(..)` pattern // check for `Some(..)` pattern
&& let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind && let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome) && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
@ -27,6 +27,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
&& !uses_iter(cx, &iter_expr_struct, if_then) && !uses_iter(cx, &iter_expr_struct, if_then)
{ {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
let loop_label = label.map_or(String::new(), |l| format!("{}: ", l.ident.name));
let loop_var = if let Some(some_pat) = some_pat.first() { let loop_var = if let Some(some_pat) = some_pat.first() {
if is_refutable(cx, some_pat) { if is_refutable(cx, some_pat) {
// Refutable patterns don't work with for loops. // Refutable patterns don't work with for loops.
@ -57,7 +60,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
expr.span.with_hi(let_expr.span.hi()), expr.span.with_hi(let_expr.span.hi()),
"this loop could be written as a `for` loop", "this loop could be written as a `for` loop",
"try", "try",
format!("for {loop_var} in {iterator}{by_ref}"), format!("{loop_label}for {loop_var} in {iterator}{by_ref}"),
applicability, applicability,
); );
} }

View file

@ -1,13 +1,13 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::higher::If; use clippy_utils::higher::If;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::is_const_evaluatable; use clippy_utils::visitors::is_const_evaluatable;
use clippy_utils::{ use clippy_utils::{
eq_expr_value, in_constant, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks, eq_expr_value, is_diag_trait_item, is_in_const_context, is_trait_method, path_res, path_to_local_id, peel_blocks,
peel_blocks_with_stmt, MaybePath, peel_blocks_with_stmt, MaybePath,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -122,8 +122,9 @@ impl<'tcx> ClampSuggestion<'tcx> {
if max_type != min_type { if max_type != min_type {
return false; return false;
} }
if let Some(max) = constant(cx, cx.typeck_results(), self.params.max) let ecx = ConstEvalCtxt::new(cx);
&& let Some(min) = constant(cx, cx.typeck_results(), self.params.min) if let Some(max) = ecx.eval(self.params.max)
&& let Some(min) = ecx.eval(self.params.min)
&& let Some(ord) = Constant::partial_cmp(cx.tcx, max_type, &min, &max) && let Some(ord) = Constant::partial_cmp(cx.tcx, max_type, &min, &max)
{ {
ord != Ordering::Greater ord != Ordering::Greater
@ -146,7 +147,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp {
if !self.msrv.meets(msrvs::CLAMP) { if !self.msrv.meets(msrvs::CLAMP) {
return; return;
} }
if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) { if !expr.span.from_expansion() && !is_in_const_context(cx) {
let suggestion = is_if_elseif_else_pattern(cx, expr) let suggestion = is_if_elseif_else_pattern(cx, expr)
.or_else(|| is_max_min_pattern(cx, expr)) .or_else(|| is_max_min_pattern(cx, expr))
.or_else(|| is_call_max_min_pattern(cx, expr)) .or_else(|| is_call_max_min_pattern(cx, expr))
@ -159,7 +160,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp {
} }
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
if !self.msrv.meets(msrvs::CLAMP) || in_constant(cx, block.hir_id) { if !self.msrv.meets(msrvs::CLAMP) || is_in_const_context(cx) {
return; return;
} }
for suggestion in is_two_if_pattern(cx, block) { for suggestion in is_two_if_pattern(cx, block) {

View file

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::{is_from_proc_macro, path_to_local}; use clippy_utils::{is_from_proc_macro, path_to_local};
@ -95,8 +95,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
|| cx.tcx.features().declared(sym!(const_float_classify)) || cx.tcx.features().declared(sym!(const_float_classify))
) )
&& let [first, second, const_1, const_2] = exprs && let [first, second, const_1, const_2] = exprs
&& let Some(const_1) = constant(cx, cx.typeck_results(), const_1) && let ecx = ConstEvalCtxt::new(cx)
&& let Some(const_2) = constant(cx, cx.typeck_results(), const_2) && let Some(const_1) = ecx.eval(const_1)
&& let Some(const_2) = ecx.eval(const_2)
&& path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s)) && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s))
// The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in // The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in
// case somebody does that for some reason // case somebody does that for some reason

View file

@ -3,7 +3,7 @@ use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::matching_root_macro_call; use clippy_utils::macros::matching_root_macro_call;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{higher, in_constant, path_to_local, peel_ref_operators}; use clippy_utils::{higher, is_in_const_context, path_to_local, peel_ref_operators};
use rustc_ast::ast::RangeLimits; use rustc_ast::ast::RangeLimits;
use rustc_ast::LitKind::{Byte, Char}; use rustc_ast::LitKind::{Byte, Char};
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -95,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
return; return;
} }
if in_constant(cx, expr.hir_id) && !self.msrv.meets(msrvs::IS_ASCII_DIGIT_CONST) { if is_in_const_context(cx) && !self.msrv.meets(msrvs::IS_ASCII_DIGIT_CONST) {
return; return;
} }

View file

@ -1,9 +1,9 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::consts::{constant_full_int, FullInt}; use clippy_utils::consts::{ConstEvalCtxt, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::{in_constant, path_to_local}; use clippy_utils::{is_in_const_context, path_to_local};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
&& add_rhs.span.ctxt() == ctxt && add_rhs.span.ctxt() == ctxt
&& !in_external_macro(cx.sess(), expr.span) && !in_external_macro(cx.sess(), expr.span)
&& self.msrv.meets(msrvs::REM_EUCLID) && self.msrv.meets(msrvs::REM_EUCLID)
&& (self.msrv.meets(msrvs::REM_EUCLID_CONST) || !in_constant(cx, expr.hir_id)) && (self.msrv.meets(msrvs::REM_EUCLID_CONST) || !is_in_const_context(cx))
&& let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs) && let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs)
&& let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs) && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs)
&& let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind && let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind
@ -117,7 +117,7 @@ fn check_for_either_unsigned_int_constant<'a>(
} }
fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> { fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
let int_const = constant_full_int(cx, cx.typeck_results(), expr)?; let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr)?;
match int_const { match int_const {
FullInt::S(s) => s.try_into().ok(), FullInt::S(s) => s.try_into().ok(),
FullInt::U(u) => Some(u), FullInt::U(u) => Some(u),

View file

@ -1,6 +1,6 @@
use std::fmt::Display; use std::fmt::Display;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg; use clippy_utils::sugg;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -66,7 +66,7 @@ fn parse_shift<'tcx>(
BinOpKind::Shr => ShiftDirection::Right, BinOpKind::Shr => ShiftDirection::Right,
_ => return None, _ => return None,
}; };
let const_expr = constant(cx, cx.typeck_results(), r)?; let const_expr = ConstEvalCtxt::new(cx).eval(r)?;
if let Constant::Int(shift) = const_expr { if let Constant::Int(shift) = const_expr {
return Some((dir, shift, l)); return Some((dir, shift, l));
} }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context; use clippy_utils::source::snippet_with_context;
use clippy_utils::{expr_or_init, in_constant, std_or_core}; use clippy_utils::{expr_or_init, is_in_const_context, std_or_core};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -44,7 +44,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
&& BinOpKind::Mul == op.node && BinOpKind::Mul == op.node
&& !expr.span.from_expansion() && !expr.span.from_expansion()
// Does not apply inside const because size_of_val is not cost in stable. // Does not apply inside const because size_of_val is not cost in stable.
&& !in_constant(cx, expr.hir_id) && !is_in_const_context(cx)
&& let Some(receiver) = simplify(cx, left, right) && let Some(receiver) = simplify(cx, left, right)
{ {
let ctxt = expr.span.ctxt(); let ctxt = expr.span.ctxt();

View file

@ -1,11 +1,12 @@
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use clippy_utils::{eq_expr_value, higher, match_def_path, paths}; use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind};
@ -14,6 +15,7 @@ use rustc_middle::ty;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::source_map::Spanned; use rustc_span::source_map::Spanned;
use rustc_span::Span; use rustc_span::Span;
use std::iter;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -108,19 +110,19 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
format!("stripping a {kind_word} manually"), format!("stripping a {kind_word} manually"),
|diag| { |diag| {
diag.span_note(test_span, format!("the {kind_word} was tested here")); diag.span_note(test_span, format!("the {kind_word} was tested here"));
multispan_sugg( diag.multipart_suggestion(
diag,
format!("try using the `strip_{kind_word}` method"), format!("try using the `strip_{kind_word}` method"),
vec![( iter::once((
test_span, test_span,
format!( format!(
"if let Some(<stripped>) = {}.strip_{kind_word}({}) ", "if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
snippet(cx, target_arg.span, ".."), snippet(cx, target_arg.span, ".."),
snippet(cx, pattern.span, "..") snippet(cx, pattern.span, "..")
), ),
)] ))
.into_iter() .chain(strippings.into_iter().map(|span| (span, "<stripped>".into())))
.chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))), .collect(),
Applicability::HasPlaceholders,
); );
}, },
); );
@ -145,7 +147,7 @@ fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
// Returns the length of the `expr` if it's a constant string or char. // Returns the length of the `expr` if it's a constant string or char.
fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> { fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
let value = constant(cx, cx.typeck_results(), expr)?; let value = ConstEvalCtxt::new(cx).eval(expr)?;
match value { match value {
Constant::Str(value) => Some(value.len() as u128), Constant::Str(value) => Some(value.len() as u128),
Constant::Char(value) => Some(value.len_utf8() as u128), Constant::Char(value) => Some(value.len_utf8() as u128),
@ -183,9 +185,9 @@ fn peel_ref<'a>(expr: &'a Expr<'_>) -> &'a Expr<'a> {
} }
} }
// Find expressions where `target` is stripped using the length of `pattern`. /// Find expressions where `target` is stripped using the length of `pattern`.
// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}` /// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}`
// method. /// method.
fn find_stripping<'tcx>( fn find_stripping<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
strip_kind: StripKind, strip_kind: StripKind,

View file

@ -10,7 +10,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::IfLetOrMatch; use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::{in_constant, is_default_equivalent, peel_blocks, span_contains_comment}; use clippy_utils::{is_default_equivalent, is_in_const_context, peel_blocks, span_contains_comment};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -174,7 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr) if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr)
&& !expr.span.from_expansion() && !expr.span.from_expansion()
&& !in_constant(cx, expr.hir_id) && !is_in_const_context(cx)
{ {
handle(cx, if_let_or_match, expr); handle(cx, if_let_or_match, expr);
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::consts::constant_simple; use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
@ -69,7 +69,7 @@ fn check_and_lint<'tcx>(
&& let Some(ty_name) = find_type_name(cx, ty) && let Some(ty_name) = find_type_name(cx, ty)
&& let Some(or_body_snippet) = snippet_opt(cx, else_expr.span) && let Some(or_body_snippet) = snippet_opt(cx, else_expr.span)
&& let Some(indent) = indent_of(cx, expr.span) && let Some(indent) = indent_of(cx, expr.span)
&& constant_simple(cx, cx.typeck_results(), else_expr).is_some() && ConstEvalCtxt::new(cx).eval_simple(else_expr).is_some()
{ {
lint(cx, expr, let_expr, ty_name, or_body_snippet, indent); lint(cx, expr, let_expr, ty_name, or_body_snippet, indent);
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet, walk_span_to_context}; use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use core::iter::once; use core::iter::once;
@ -54,7 +54,11 @@ where
span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| {
if !expr.span.from_expansion() { if !expr.span.from_expansion() {
multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs)); diag.multipart_suggestion(
msg,
first_sugg.chain(remaining_suggs).collect(),
Applicability::MachineApplicable,
);
} }
}); });
} }

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns}; use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns};
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -148,23 +148,27 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
Applicability::MaybeIncorrect, Applicability::MaybeIncorrect,
), ),
variants => { variants => {
let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect(); let (message, add_wildcard) = if adt_def.is_variant_list_non_exhaustive() || has_external_hidden {
let message = if adt_def.is_variant_list_non_exhaustive() || has_external_hidden { (
suggestions.push("_".into()); "wildcard matches known variants and will also match future added variants",
"wildcard matches known variants and will also match future added variants" true,
)
} else { } else {
"wildcard match will also match any future added variants" ("wildcard match will also match any future added variants", false)
}; };
span_lint_and_sugg( span_lint_and_then(cx, WILDCARD_ENUM_MATCH_ARM, wildcard_span, message, |diag| {
cx, let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
WILDCARD_ENUM_MATCH_ARM, if add_wildcard {
wildcard_span, suggestions.push("_".into());
message, }
"try", diag.span_suggestion(
suggestions.join(" | "), wildcard_span,
Applicability::MaybeIncorrect, "try",
); suggestions.join(" | "),
Applicability::MaybeIncorrect,
);
});
}, },
}; };
} }
@ -176,9 +180,8 @@ enum CommonPrefixSearcher<'a> {
} }
impl<'a> CommonPrefixSearcher<'a> { impl<'a> CommonPrefixSearcher<'a> {
fn with_path(&mut self, path: &'a [PathSegment<'a>]) { fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
match path { if let [path @ .., _] = path {
[path @ .., _] => self.with_prefix(path), self.with_prefix(path);
[] => (),
} }
} }

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::macros::{is_panic, root_macro_call}; use clippy_utils::macros::{is_panic, root_macro_call};
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::is_local_used; use clippy_utils::visitors::is_local_used;
use clippy_utils::{in_constant, is_wild, peel_blocks_with_stmt}; use clippy_utils::{is_in_const_context, is_wild, peel_blocks_with_stmt};
use rustc_hir::{Arm, Expr, PatKind}; use rustc_hir::{Arm, Expr, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::symbol::{kw, sym}; use rustc_span::symbol::{kw, sym};
@ -11,7 +11,7 @@ use super::MATCH_WILD_ERR_ARM;
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) {
// `unwrap`/`expect` is not (yet) const, so we want to allow this in const contexts for now // `unwrap`/`expect` is not (yet) const, so we want to allow this in const contexts for now
if in_constant(cx, ex.hir_id) { if is_in_const_context(cx) {
return; return;
} }

View file

@ -27,7 +27,7 @@ mod wild_in_or_pats;
use clippy_config::msrvs::{self, Msrv}; use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf; use clippy_config::Conf;
use clippy_utils::source::walk_span_to_context; use clippy_utils::source::walk_span_to_context;
use clippy_utils::{higher, in_constant, is_direct_expn_of, is_span_match, span_contains_cfg}; use clippy_utils::{higher, is_direct_expn_of, is_in_const_context, is_span_match, span_contains_cfg};
use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind}; use rustc_hir::{Arm, Expr, ExprKind, LetStmt, MatchSource, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -1069,7 +1069,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
match_str_case_mismatch::check(cx, ex, arms); match_str_case_mismatch::check(cx, ex, arms);
redundant_guards::check(cx, arms, &self.msrv); redundant_guards::check(cx, arms, &self.msrv);
if !in_constant(cx, expr.hir_id) { if !is_in_const_context(cx) {
manual_unwrap_or::check_match(cx, expr, ex, arms); manual_unwrap_or::check_match(cx, expr, ex, arms);
manual_map::check_match(cx, expr, ex, arms); manual_map::check_match(cx, expr, ex, arms);
manual_filter::check_match(cx, ex, arms, expr); manual_filter::check_match(cx, ex, arms, expr);
@ -1098,7 +1098,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
else_expr, else_expr,
); );
} }
if !in_constant(cx, expr.hir_id) { if !is_in_const_context(cx) {
manual_unwrap_or::check_if_let( manual_unwrap_or::check_if_let(
cx, cx,
expr, expr,

View file

@ -1,4 +1,4 @@
use clippy_utils::consts::{constant, constant_full_int, mir_to_const, FullInt}; use clippy_utils::consts::{mir_to_const, ConstEvalCtxt, FullInt};
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::span_lint_and_note;
use core::cmp::Ordering; use core::cmp::Ordering;
use rustc_hir::{Arm, Expr, PatKind, RangeEnd}; use rustc_hir::{Arm, Expr, PatKind, RangeEnd};
@ -33,22 +33,20 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
.filter_map(|arm| { .filter_map(|arm| {
if let Arm { pat, guard: None, .. } = *arm { if let Arm { pat, guard: None, .. } = *arm {
if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
let lhs_const = match lhs { let lhs_const = if let Some(lhs) = lhs {
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?, ConstEvalCtxt::new(cx).eval(lhs)?
None => { } else {
let min_val_const = ty.numeric_min_val(cx.tcx)?; let min_val_const = ty.numeric_min_val(cx.tcx)?;
mir_to_const(cx, mir::Const::from_ty_const(min_val_const, ty, cx.tcx))? mir_to_const(cx.tcx, mir::Const::from_ty_const(min_val_const, ty, cx.tcx))?
},
}; };
let rhs_const = match rhs { let rhs_const = if let Some(rhs) = rhs {
Some(rhs) => constant(cx, cx.typeck_results(), rhs)?, ConstEvalCtxt::new(cx).eval(rhs)?
None => { } else {
let max_val_const = ty.numeric_max_val(cx.tcx)?; let max_val_const = ty.numeric_max_val(cx.tcx)?;
mir_to_const(cx, mir::Const::from_ty_const(max_val_const, ty, cx.tcx))? mir_to_const(cx.tcx, mir::Const::from_ty_const(max_val_const, ty, cx.tcx))?
},
}; };
let lhs_val = lhs_const.int_value(cx, ty)?; let lhs_val = lhs_const.int_value(cx.tcx, ty)?;
let rhs_val = rhs_const.int_value(cx, ty)?; let rhs_val = rhs_const.int_value(cx.tcx, ty)?;
let rhs_bound = match range_end { let rhs_bound = match range_end {
RangeEnd::Included => EndBound::Included(rhs_val), RangeEnd::Included => EndBound::Included(rhs_val),
RangeEnd::Excluded => EndBound::Excluded(rhs_val), RangeEnd::Excluded => EndBound::Excluded(rhs_val),
@ -60,7 +58,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
} }
if let PatKind::Lit(value) = pat.kind { if let PatKind::Lit(value) = pat.kind {
let value = constant_full_int(cx, cx.typeck_results(), value)?; let value = ConstEvalCtxt::new(cx).eval_full_int(value)?;
return Some(SpannedRange { return Some(SpannedRange {
span: pat.span, span: pat.span,
node: (value, EndBound::Included(value)), node: (value, EndBound::Included(value)),

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::matching_root_macro_call; use clippy_utils::macros::matching_root_macro_call;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::visitors::{for_each_expr_without_closures, is_local_used}; use clippy_utils::visitors::{for_each_expr_without_closures, is_local_used};
use clippy_utils::{in_constant, path_to_local}; use clippy_utils::{is_in_const_context, path_to_local};
use rustc_ast::{BorrowKind, LitKind}; use rustc_ast::{BorrowKind, LitKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
@ -116,7 +116,7 @@ fn check_method_calls<'tcx>(
// `s if s.is_empty()` becomes "" // `s if s.is_empty()` becomes ""
// `arr if arr.is_empty()` becomes [] // `arr if arr.is_empty()` becomes []
if ty.is_str() && !in_constant(cx, if_expr.hir_id) { if ty.is_str() && !is_in_const_context(cx) {
r#""""#.into() r#""""#.into()
} else if slice_like { } else if slice_like {
"[]".into() "[]".into()

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_then;
use rustc_hir::{Pat, PatKind, QPath}; use rustc_hir::{Pat, PatKind, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty;
@ -15,13 +15,15 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) {
&& fields.len() == def.non_enum_variant().fields.len() && fields.len() == def.non_enum_variant().fields.len()
&& !def.non_enum_variant().is_field_list_non_exhaustive() && !def.non_enum_variant().is_field_list_non_exhaustive()
{ {
span_lint_and_help( #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx, cx,
REST_PAT_IN_FULLY_BOUND_STRUCTS, REST_PAT_IN_FULLY_BOUND_STRUCTS,
pat.span, pat.span,
"unnecessary use of `..` pattern in struct binding. All fields were already bound", "unnecessary use of `..` pattern in struct binding. All fields were already bound",
None, |diag| {
"consider removing `..` from this binding", diag.help("consider removing `..` from this binding");
},
); );
} }
} }

View file

@ -1,12 +1,17 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{expr_block, snippet, SpanRangeExt}; use clippy_utils::source::{expr_block, snippet, SpanRangeExt};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs}; use clippy_utils::ty::implements_trait;
use clippy_utils::{is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs}; use clippy_utils::{
use core::cmp::max; is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs,
};
use core::ops::ControlFlow;
use rustc_arena::DroplessArena;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Arm, BindingMode, Block, Expr, ExprKind, Pat, PatKind}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_pat, Visitor};
use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, AdtDef, ParamEnv, TyCtxt, TypeckResults, VariantDef};
use rustc_span::{sym, Span}; use rustc_span::{sym, Span};
use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE};
@ -27,52 +32,58 @@ fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
} }
#[rustfmt::skip] #[rustfmt::skip]
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>) {
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { if let [arm1, arm2] = arms
if expr.span.from_expansion() { && arm1.guard.is_none()
// Don't lint match expressions present in && arm2.guard.is_none()
// macro_rules! block && !expr.span.from_expansion()
return; // don't lint for or patterns for now, this makes
} // the lint noisy in unnecessary situations
if let PatKind::Or(..) = arms[0].pat.kind { && !matches!(arm1.pat.kind, PatKind::Or(..))
// don't lint for or patterns for now, this makes {
// the lint noisy in unnecessary situations let els = if is_unit_expr(peel_blocks(arm2.body)) && !empty_arm_has_comment(cx, arm2.body.span) {
return;
}
let els = arms[1].body;
let els = if is_unit_expr(peel_blocks(els)) && !empty_arm_has_comment(cx, els.span) {
None None
} else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { } else if let ExprKind::Block(block, _) = arm2.body.kind {
if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { if matches!((block.stmts, block.expr), ([], Some(_)) | ([_], None)) {
// single statement/expr "else" block, don't lint // single statement/expr "else" block, don't lint
return; return;
} }
// block with 2+ statements or 1 expr and 1+ statement // block with 2+ statements or 1 expr and 1+ statement
Some(els) Some(arm2.body)
} else { } else {
// not a block or an empty block w/ comments, don't lint // not a block or an empty block w/ comments, don't lint
return; return;
}; };
let ty = cx.typeck_results().expr_ty(ex); let typeck = cx.typeck_results();
if (*ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id)) && if *typeck.expr_ty(ex).peel_refs().kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
(check_single_pattern(arms) || check_opt_like(cx, arms, ty)) { let mut v = PatVisitor {
report_single_pattern(cx, ex, arms, expr, els); typeck,
has_enum: false,
};
if v.visit_pat(arm2.pat).is_break() {
return;
}
if v.has_enum {
let cx = PatCtxt {
tcx: cx.tcx,
param_env: cx.param_env,
typeck,
arena: DroplessArena::default(),
};
let mut state = PatState::Other;
if !(state.add_pat(&cx, arm2.pat) || state.add_pat(&cx, arm1.pat)) {
// Don't lint if the pattern contains an enum which doesn't have a wild match.
return;
}
}
report_single_pattern(cx, ex, arm1, expr, els);
} }
} }
} }
fn check_single_pattern(arms: &[Arm<'_>]) -> bool { fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, expr: &Expr<'_>, els: Option<&Expr<'_>>) {
is_wild(arms[1].pat)
}
fn report_single_pattern(
cx: &LateContext<'_>,
ex: &Expr<'_>,
arms: &[Arm<'_>],
expr: &Expr<'_>,
els: Option<&Expr<'_>>,
) {
let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
let ctxt = expr.span.ctxt(); let ctxt = expr.span.ctxt();
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
@ -80,9 +91,9 @@ fn report_single_pattern(
format!(" else {}", expr_block(cx, els, ctxt, "..", Some(expr.span), &mut app)) format!(" else {}", expr_block(cx, els, ctxt, "..", Some(expr.span), &mut app))
}); });
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); let (pat, pat_ref_count) = peel_hir_pat_refs(arm.pat);
let (msg, sugg) = if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind let (msg, sugg) = if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind
&& let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)) && let (ty, ty_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(ex))
&& let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait() && let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait()
&& let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait() && let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait()
&& (ty.is_integral() && (ty.is_integral()
@ -114,17 +125,17 @@ fn report_single_pattern(
snippet(cx, ex.span, ".."), snippet(cx, ex.span, ".."),
// PartialEq for different reference counts may not exist. // PartialEq for different reference counts may not exist.
"&".repeat(ref_count_diff), "&".repeat(ref_count_diff),
snippet(cx, arms[0].pat.span, ".."), snippet(cx, arm.pat.span, ".."),
expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app), expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app),
); );
(msg, sugg) (msg, sugg)
} else { } else {
let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
let sugg = format!( let sugg = format!(
"if let {} = {} {}{els_str}", "if let {} = {} {}{els_str}",
snippet(cx, arms[0].pat.span, ".."), snippet(cx, arm.pat.span, ".."),
snippet(cx, ex.span, ".."), snippet(cx, ex.span, ".."),
expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app), expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app),
); );
(msg, sugg) (msg, sugg)
}; };
@ -132,106 +143,227 @@ fn report_single_pattern(
span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app); span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app);
} }
fn check_opt_like<'a>(cx: &LateContext<'a>, arms: &[Arm<'_>], ty: Ty<'a>) -> bool { struct PatVisitor<'tcx> {
// We don't want to lint if the second arm contains an enum which could typeck: &'tcx TypeckResults<'tcx>,
// have more variants in the future. has_enum: bool,
form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat)
} }
impl<'tcx> Visitor<'tcx> for PatVisitor<'tcx> {
/// Returns `true` if all of the types in the pattern are enums which we know type Result = ControlFlow<()>;
/// won't be expanded in the future fn visit_pat(&mut self, pat: &'tcx Pat<'_>) -> Self::Result {
fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool { if matches!(pat.kind, PatKind::Binding(..)) {
let mut paths_and_types = Vec::new(); ControlFlow::Break(())
collect_pat_paths(&mut paths_and_types, cx, pat, ty); } else {
paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) self.has_enum |= self.typeck.pat_ty(pat).ty_adt_def().map_or(false, AdtDef::is_enum);
} walk_pat(self, pat)
/// Returns `true` if the given type is an enum we know won't be expanded in the future
fn in_candidate_enum(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
// list of candidate `Enum`s we know will never get any more members
let candidates = [sym::Cow, sym::Option, sym::Result];
for candidate_ty in candidates {
if is_type_diagnostic_item(cx, ty, candidate_ty) {
return true;
} }
} }
false
} }
/// Collects types from the given pattern /// The context needed to manipulate a `PatState`.
fn collect_pat_paths<'a>(acc: &mut Vec<Ty<'a>>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) { struct PatCtxt<'tcx> {
match pat.kind { tcx: TyCtxt<'tcx>,
PatKind::Tuple(inner, _) => inner.iter().for_each(|p| { param_env: ParamEnv<'tcx>,
let p_ty = cx.typeck_results().pat_ty(p); typeck: &'tcx TypeckResults<'tcx>,
collect_pat_paths(acc, cx, p, p_ty); arena: DroplessArena,
}), }
PatKind::TupleStruct(..) | PatKind::Binding(BindingMode::NONE, .., None) | PatKind::Path(_) => {
acc.push(ty); /// State for tracking whether a match can become non-exhaustive by adding a variant to a contained
}, /// enum.
_ => {}, ///
/// This treats certain std enums as if they will never be extended.
enum PatState<'a> {
/// Either a wild match or an uninteresting type. Uninteresting types include:
/// * builtin types (e.g. `i32` or `!`)
/// * A struct/tuple/array containing only uninteresting types.
/// * A std enum containing only uninteresting types.
Wild,
/// A std enum we know won't be extended. Tracks the states of each variant separately.
///
/// This is not used for `Option` since it uses the current pattern to track it's state.
StdEnum(&'a mut [PatState<'a>]),
/// Either the initial state for a pattern or a non-std enum. There is currently no need to
/// distinguish these cases.
///
/// For non-std enums there's no need to track the state of sub-patterns as the state of just
/// this pattern on it's own is enough for linting. Consider two cases:
/// * This enum has no wild match. This case alone is enough to determine we can lint.
/// * This enum has a wild match and therefore all sub-patterns also have a wild match.
///
/// In both cases the sub patterns are not needed to determine whether to lint.
Other,
}
impl<'a> PatState<'a> {
/// Adds a set of patterns as a product type to the current state. Returns whether or not the
/// current state is a wild match after the merge.
fn add_product_pat<'tcx>(
&mut self,
cx: &'a PatCtxt<'tcx>,
pats: impl IntoIterator<Item = &'tcx Pat<'tcx>>,
) -> bool {
// Ideally this would actually keep track of the state separately for each pattern. Doing so would
// require implementing something similar to exhaustiveness checking which is a significant increase
// in complexity.
//
// For now treat this as a wild match only if all the sub-patterns are wild
let is_wild = pats.into_iter().all(|p| {
let mut state = Self::Other;
state.add_pat(cx, p)
});
if is_wild {
*self = Self::Wild;
}
is_wild
} }
}
/// Returns true if the given arm of pattern matching contains wildcard patterns. /// Attempts to get the state for the enum variant, initializing the current state if necessary.
fn contains_only_wilds(pat: &Pat<'_>) -> bool { fn get_std_enum_variant<'tcx>(
match pat.kind { &mut self,
PatKind::Wild => true, cx: &'a PatCtxt<'tcx>,
PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds), adt: AdtDef<'tcx>,
_ => false, path: &'tcx QPath<'_>,
hir_id: HirId,
) -> Option<(&mut Self, &'tcx VariantDef)> {
let states = match self {
Self::Wild => return None,
Self::Other => {
*self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other)));
let Self::StdEnum(x) = self else {
unreachable!();
};
x
},
Self::StdEnum(x) => x,
};
let i = match cx.typeck.qpath_res(path, hir_id) {
Res::Def(DefKind::Ctor(..), id) => adt.variant_index_with_ctor_id(id),
Res::Def(DefKind::Variant, id) => adt.variant_index_with_id(id),
_ => return None,
};
Some((&mut states[i.as_usize()], adt.variant(i)))
} }
}
/// Returns true if the given patterns forms only exhaustive matches that don't contain enum fn check_all_wild_enum(&mut self) -> bool {
/// patterns without a wildcard. if let Self::StdEnum(states) = self
fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool { && states.iter().all(|s| matches!(s, Self::Wild))
match (&left.kind, &right.kind) { {
(PatKind::Wild, _) | (_, PatKind::Wild) => true, *self = Self::Wild;
(PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => {
// We don't actually know the position and the presence of the `..` (dotdot) operator
// in the arms, so we need to evaluate the correct offsets here in order to iterate in
// both arms at the same time.
let left_pos = left_pos.as_opt_usize();
let right_pos = right_pos.as_opt_usize();
let len = max(
left_in.len() + usize::from(left_pos.is_some()),
right_in.len() + usize::from(right_pos.is_some()),
);
let mut left_pos = left_pos.unwrap_or(usize::MAX);
let mut right_pos = right_pos.unwrap_or(usize::MAX);
let mut left_dot_space = 0;
let mut right_dot_space = 0;
for i in 0..len {
let mut found_dotdot = false;
if i == left_pos {
left_dot_space += 1;
if left_dot_space < len - left_in.len() {
left_pos += 1;
}
found_dotdot = true;
}
if i == right_pos {
right_dot_space += 1;
if right_dot_space < len - right_in.len() {
right_pos += 1;
}
found_dotdot = true;
}
if found_dotdot {
continue;
}
if !contains_only_wilds(&left_in[i - left_dot_space])
&& !contains_only_wilds(&right_in[i - right_dot_space])
{
return false;
}
}
true true
}, } else {
(PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right), false
(PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => { }
pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds) }
},
_ => false, #[expect(clippy::similar_names)]
fn add_struct_pats<'tcx>(
&mut self,
cx: &'a PatCtxt<'tcx>,
pat: &'tcx Pat<'tcx>,
path: &'tcx QPath<'tcx>,
single_pat: Option<&'tcx Pat<'tcx>>,
pats: impl IntoIterator<Item = &'tcx Pat<'tcx>>,
) -> bool {
let ty::Adt(adt, _) = *cx.typeck.pat_ty(pat).kind() else {
// Should never happen
*self = Self::Wild;
return true;
};
if adt.is_struct() {
return if let Some(pat) = single_pat
&& adt.non_enum_variant().fields.len() == 1
{
self.add_pat(cx, pat)
} else {
self.add_product_pat(cx, pats)
};
}
match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Option) => {
if let Some(pat) = single_pat {
self.add_pat(cx, pat)
} else {
*self = Self::Wild;
true
}
},
Some(sym::Result | sym::Cow) => {
let Some((state, variant)) = self.get_std_enum_variant(cx, adt, path, pat.hir_id) else {
return matches!(self, Self::Wild);
};
let is_wild = if let Some(pat) = single_pat
&& variant.fields.len() == 1
{
state.add_pat(cx, pat)
} else {
state.add_product_pat(cx, pats)
};
is_wild && self.check_all_wild_enum()
},
_ => matches!(self, Self::Wild),
}
}
/// Adds the pattern into the current state. Returns whether or not the current state is a wild
/// match after the merge.
#[expect(clippy::similar_names)]
fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool {
match pat.kind {
PatKind::Path(_)
if match *cx.typeck.pat_ty(pat).peel_refs().kind() {
ty::Adt(adt, _) => adt.is_enum() || (adt.is_struct() && !adt.non_enum_variant().fields.is_empty()),
ty::Tuple(tys) => !tys.is_empty(),
ty::Array(_, len) => len.try_eval_target_usize(cx.tcx, cx.param_env) != Some(1),
ty::Slice(..) => true,
_ => false,
} =>
{
matches!(self, Self::Wild)
},
// Patterns for things which can only contain a single sub-pattern.
PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) | PatKind::Box(pat) | PatKind::Deref(pat) => {
self.add_pat(cx, pat)
},
PatKind::Tuple([sub_pat], pos)
if pos.as_opt_usize().is_none() || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 =>
{
self.add_pat(cx, sub_pat)
},
PatKind::Slice([sub_pat], _, []) | PatKind::Slice([], _, [sub_pat])
if let ty::Array(_, len) = *cx.typeck.pat_ty(pat).kind()
&& len.try_eval_target_usize(cx.tcx, cx.param_env) == Some(1) =>
{
self.add_pat(cx, sub_pat)
},
PatKind::Or(pats) => pats.iter().any(|p| self.add_pat(cx, p)),
PatKind::Tuple(pats, _) => self.add_product_pat(cx, pats),
PatKind::Slice(head, _, tail) => self.add_product_pat(cx, head.iter().chain(tail)),
PatKind::TupleStruct(ref path, pats, _) => self.add_struct_pats(
cx,
pat,
path,
if let [pat] = pats { Some(pat) } else { None },
pats.iter(),
),
PatKind::Struct(ref path, pats, _) => self.add_struct_pats(
cx,
pat,
path,
if let [pat] = pats { Some(pat.pat) } else { None },
pats.iter().map(|p| p.pat),
),
PatKind::Wild
| PatKind::Binding(_, _, _, None)
| PatKind::Lit(_)
| PatKind::Range(..)
| PatKind::Path(_)
| PatKind::Never
| PatKind::Err(_) => {
*self = PatState::Wild;
true
},
}
} }
} }

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