Merge commit '26ac6aab023393c94edf42f38f6ad31196009643'
This commit is contained in:
parent
beeaee9785
commit
aa220c7ee7
2
.github/workflows/clippy.yml
vendored
2
.github/workflows/clippy.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
|
10
.github/workflows/clippy_bors.yml
vendored
10
.github/workflows/clippy_bors.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install i686 dependencies
|
||||
if: matrix.host == 'i686-unknown-linux-gnu'
|
||||
@ -151,7 +151,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -175,7 +175,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -231,7 +231,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
|
2
.github/workflows/clippy_dev.yml
vendored
2
.github/workflows/clippy_dev.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Run
|
||||
- name: Build
|
||||
|
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@ -21,10 +21,10 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
path: 'out'
|
||||
|
2
.github/workflows/remark.yml
vendored
2
.github/workflows/remark.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
|
@ -5105,6 +5105,7 @@ Released 2018-09-13
|
||||
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
|
||||
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
|
||||
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
|
||||
[`empty_enum_variants_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum_variants_with_brackets
|
||||
[`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments
|
||||
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
|
||||
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
|
||||
@ -5294,6 +5295,7 @@ Released 2018-09-13
|
||||
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
|
||||
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
|
||||
[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
|
||||
[`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and
|
||||
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
|
||||
[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str
|
||||
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
|
||||
@ -5440,6 +5442,7 @@ Released 2018-09-13
|
||||
[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
|
||||
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
|
||||
[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
|
||||
[`option_as_ref_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_cloned
|
||||
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
|
||||
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
|
||||
[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
|
||||
@ -5483,6 +5486,7 @@ Released 2018-09-13
|
||||
[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
|
||||
[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
|
||||
[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
|
||||
[`pub_underscore_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields
|
||||
[`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use
|
||||
[`pub_with_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_with_shorthand
|
||||
[`pub_without_shorthand`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_without_shorthand
|
||||
@ -5580,6 +5584,7 @@ Released 2018-09-13
|
||||
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
|
||||
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
|
||||
[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
|
||||
[`str_split_at_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_split_at_newline
|
||||
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
|
||||
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
|
||||
[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
|
||||
@ -5612,6 +5617,7 @@ Released 2018-09-13
|
||||
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
|
||||
[`test_attr_in_doctest`]: https://rust-lang.github.io/rust-clippy/master/index.html#test_attr_in_doctest
|
||||
[`tests_outside_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#tests_outside_test_module
|
||||
[`thread_local_initializer_can_be_made_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#thread_local_initializer_can_be_made_const
|
||||
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
|
||||
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
|
||||
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
|
||||
@ -5810,4 +5816,5 @@ Released 2018-09-13
|
||||
[`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles
|
||||
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
|
||||
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
|
||||
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
|
||||
<!-- end autogenerated links to configuration documentation -->
|
||||
|
@ -1,6 +1,6 @@
|
||||
// REUSE-IgnoreStart
|
||||
|
||||
Copyright 2014-2022 The Rust Project Developers
|
||||
Copyright 2014-2024 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
|
@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014-2022 The Rust Project Developers
|
||||
Copyright 2014-2024 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-2022 The Rust Project Developers
|
||||
Copyright (c) 2014-2024 The Rust Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 650 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
|
||||
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.
|
||||
@ -278,7 +278,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT
|
||||
|
||||
<!-- REUSE-IgnoreStart -->
|
||||
|
||||
Copyright 2014-2023 The Rust Project Developers
|
||||
Copyright 2014-2024 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license
|
||||
|
@ -6,7 +6,7 @@
|
||||
A collection of lints to catch common mistakes and improve your
|
||||
[Rust](https://github.com/rust-lang/rust) code.
|
||||
|
||||
[There are over 650 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
|
||||
|
||||
Lints are divided into categories, each with a default [lint
|
||||
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how
|
||||
|
@ -9,6 +9,7 @@
|
||||
- [Clippy's Lints](lints.md)
|
||||
- [Continuous Integration](continuous_integration/README.md)
|
||||
- [GitHub Actions](continuous_integration/github_actions.md)
|
||||
- [GitLab CI](continuous_integration/gitlab.md)
|
||||
- [Travis CI](continuous_integration/travis.md)
|
||||
- [Development](development/README.md)
|
||||
- [Basics](development/basics.md)
|
||||
|
@ -15,7 +15,7 @@ jobs:
|
||||
clippy_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Clippy
|
||||
run: cargo clippy --all-targets --all-features
|
||||
```
|
||||
|
16
book/src/continuous_integration/gitlab.md
Normal file
16
book/src/continuous_integration/gitlab.md
Normal file
@ -0,0 +1,16 @@
|
||||
# GitLab CI
|
||||
|
||||
You can add Clippy to GitLab CI by using the latest stable [rust docker image](https://hub.docker.com/_/rust),
|
||||
as it is shown in the `.gitlab-ci.yml` CI configuration file below,
|
||||
|
||||
```yml
|
||||
# Make sure CI fails on all warnings, including Clippy lints
|
||||
variables:
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
|
||||
clippy_check:
|
||||
image: rust:latest
|
||||
script:
|
||||
- rustup component add clippy
|
||||
- cargo clippy --all-targets --all-features
|
||||
```
|
@ -102,7 +102,7 @@ let x: Option<u32> = Some(42);
|
||||
m!(x, x.unwrap());
|
||||
```
|
||||
|
||||
If the `m!(x, x.unwrapp());` line is expanded, we would get two expanded
|
||||
If the `m!(x, x.unwrap());` line is expanded, we would get two expanded
|
||||
expressions:
|
||||
|
||||
- `x.is_some()` (from the `$a.is_some()` line in the `m` macro)
|
||||
|
@ -133,7 +133,7 @@ in this chapter:
|
||||
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
|
||||
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
|
||||
|
||||
[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/sty/enum.TyKind.html#variant.Adt
|
||||
[Adt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/ty_kind/enum.TyKind.html#variant.Adt
|
||||
[AdtDef]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/adt/struct.AdtDef.html
|
||||
[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
|
||||
[node_type]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.node_type
|
||||
@ -144,7 +144,7 @@ in this chapter:
|
||||
[LateLintPass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
|
||||
[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/typeck_results/struct.TypeckResults.html#method.pat_ty
|
||||
[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html
|
||||
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/sty/enum.TyKind.html
|
||||
[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/ty_kind/enum.TyKind.html
|
||||
[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
|
||||
[middle_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/ty/struct.Ty.html
|
||||
[hir_ty]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/struct.Ty.html
|
||||
|
@ -805,3 +805,13 @@ for _ in &mut *rmvec {}
|
||||
* [`missing_errors_doc`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc)
|
||||
|
||||
|
||||
## `pub-underscore-fields-behavior`
|
||||
|
||||
|
||||
**Default Value:** `"PublicallyExported"`
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
* [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields)
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::msrvs::Msrv;
|
||||
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, Rename};
|
||||
use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename};
|
||||
use crate::ClippyConfiguration;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_session::Session;
|
||||
@ -547,6 +547,11 @@ pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
|
||||
///
|
||||
/// 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::PublicallyExported),
|
||||
}
|
||||
|
||||
/// Search for the configuration file.
|
||||
|
@ -96,6 +96,9 @@ fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
|
||||
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();
|
||||
|
@ -17,7 +17,7 @@ macro_rules! msrv_aliases {
|
||||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
|
||||
1,70,0 { OPTION_IS_SOME_AND, BINARY_HEAP_RETAIN }
|
||||
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
|
||||
1,68,0 { PATH_MAIN_SEPARATOR_STR }
|
||||
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
|
||||
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }
|
||||
|
@ -126,3 +126,9 @@ fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
||||
Rename,
|
||||
MacroMatcher,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum PubUnderscoreFieldsBehaviour {
|
||||
PublicallyExported,
|
||||
AllPubFields,
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ pub fn create(
|
||||
if pass == "early" {
|
||||
println!(
|
||||
"\n\
|
||||
NOTE: Use a late pass unless you need something specific from\
|
||||
NOTE: Use a late pass unless you need something specific from\n\
|
||||
an early pass, as they lack many features and utilities"
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{method_chain_args, sext};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use clippy_utils::{clip, method_chain_args, sext};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::{self, Ty, UintTy};
|
||||
|
||||
use super::CAST_SIGN_LOSS;
|
||||
|
||||
const METHODS_RET_POSITIVE: &[&str] = &["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
if should_lint(cx, cast_op, cast_from, cast_to) {
|
||||
span_lint(
|
||||
@ -25,33 +27,28 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't lint for positive constants.
|
||||
let const_val = constant(cx, cx.typeck_results(), cast_op);
|
||||
if let Some(Constant::Int(n)) = const_val
|
||||
&& let ty::Int(ity) = *cast_from.kind()
|
||||
&& sext(cx.tcx, n, ity) >= 0
|
||||
{
|
||||
// Don't lint if `cast_op` is known to be positive.
|
||||
if let Sign::ZeroOrPositive = expr_sign(cx, cast_op, cast_from) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't lint for the result of methods that always return non-negative values.
|
||||
if let ExprKind::MethodCall(path, ..) = cast_op.kind {
|
||||
let mut method_name = path.ident.name.as_str();
|
||||
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
|
||||
|
||||
if method_name == "unwrap"
|
||||
&& let Some(arglist) = method_chain_args(cast_op, &["unwrap"])
|
||||
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
|
||||
{
|
||||
method_name = inner_path.ident.name.as_str();
|
||||
}
|
||||
|
||||
if allowed_methods.iter().any(|&name| method_name == name) {
|
||||
return false;
|
||||
}
|
||||
let (mut uncertain_count, mut negative_count) = (0, 0);
|
||||
// Peel off possible binary expressions, e.g. x * x * y => [x, x, y]
|
||||
let Some(exprs) = exprs_with_selected_binop_peeled(cast_op) else {
|
||||
// Assume cast sign lose if we cannot determine the sign of `cast_op`
|
||||
return true;
|
||||
};
|
||||
for expr in exprs {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
match expr_sign(cx, expr, ty) {
|
||||
Sign::Negative => negative_count += 1,
|
||||
Sign::Uncertain => uncertain_count += 1,
|
||||
Sign::ZeroOrPositive => (),
|
||||
};
|
||||
}
|
||||
|
||||
true
|
||||
// Lint if there are odd number of uncertain or negative results
|
||||
uncertain_count % 2 == 1 || negative_count % 2 == 1
|
||||
},
|
||||
|
||||
(false, true) => !cast_to.is_signed(),
|
||||
@ -59,3 +56,97 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_const_int_eval(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Option<i128> {
|
||||
if let Constant::Int(n) = constant(cx, cx.typeck_results(), expr)?
|
||||
&& let ty::Int(ity) = *ty.kind()
|
||||
{
|
||||
return Some(sext(cx.tcx, n, ity));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
enum Sign {
|
||||
ZeroOrPositive,
|
||||
Negative,
|
||||
Uncertain,
|
||||
}
|
||||
|
||||
fn expr_sign(cx: &LateContext<'_>, expr: &Expr<'_>, ty: Ty<'_>) -> Sign {
|
||||
// Try evaluate this expr first to see if it's positive
|
||||
if let Some(val) = get_const_int_eval(cx, expr, ty) {
|
||||
return if val >= 0 { Sign::ZeroOrPositive } else { Sign::Negative };
|
||||
}
|
||||
// Calling on methods that always return non-negative values.
|
||||
if let ExprKind::MethodCall(path, caller, args, ..) = expr.kind {
|
||||
let mut method_name = path.ident.name.as_str();
|
||||
|
||||
if method_name == "unwrap"
|
||||
&& let Some(arglist) = method_chain_args(expr, &["unwrap"])
|
||||
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
|
||||
{
|
||||
method_name = inner_path.ident.name.as_str();
|
||||
}
|
||||
|
||||
if method_name == "pow"
|
||||
&& let [arg] = args
|
||||
{
|
||||
return pow_call_result_sign(cx, caller, arg);
|
||||
} else if METHODS_RET_POSITIVE.iter().any(|&name| method_name == name) {
|
||||
return Sign::ZeroOrPositive;
|
||||
}
|
||||
}
|
||||
|
||||
Sign::Uncertain
|
||||
}
|
||||
|
||||
/// Return the sign of the `pow` call's result.
|
||||
///
|
||||
/// If the caller is a positive number, the result is always positive,
|
||||
/// If the `power_of` is a even number, the result is always positive as well,
|
||||
/// Otherwise a [`Sign::Uncertain`] will be returned.
|
||||
fn pow_call_result_sign(cx: &LateContext<'_>, caller: &Expr<'_>, power_of: &Expr<'_>) -> Sign {
|
||||
let caller_ty = cx.typeck_results().expr_ty(caller);
|
||||
if let Some(caller_val) = get_const_int_eval(cx, caller, caller_ty)
|
||||
&& caller_val >= 0
|
||||
{
|
||||
return Sign::ZeroOrPositive;
|
||||
}
|
||||
|
||||
if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), power_of)
|
||||
&& clip(cx.tcx, n, UintTy::U32) % 2 == 0
|
||||
{
|
||||
return Sign::ZeroOrPositive;
|
||||
}
|
||||
|
||||
Sign::Uncertain
|
||||
}
|
||||
|
||||
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
|
||||
/// which the result could always be positive under certain condition.
|
||||
///
|
||||
/// Other operators such as `+`/`-` causing the result's sign hard to determine, which we will
|
||||
/// return `None`
|
||||
fn exprs_with_selected_binop_peeled<'a>(expr: &'a Expr<'_>) -> Option<Vec<&'a Expr<'a>>> {
|
||||
#[inline]
|
||||
fn collect_operands<'a>(expr: &'a Expr<'a>, operands: &mut Vec<&'a Expr<'a>>) -> Option<()> {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(op, lhs, rhs) => {
|
||||
if matches!(op.node, BinOpKind::Mul | BinOpKind::Div | BinOpKind::Rem) {
|
||||
collect_operands(lhs, operands);
|
||||
operands.push(rhs);
|
||||
} else {
|
||||
// Things are complicated when there are other binary ops exist,
|
||||
// abort checking by returning `None` for now.
|
||||
return None;
|
||||
}
|
||||
},
|
||||
_ => operands.push(expr),
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
let mut res = vec![];
|
||||
collect_operands(expr, &mut res)?;
|
||||
Some(res)
|
||||
}
|
||||
|
@ -150,7 +150,8 @@
|
||||
crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
|
||||
crate::empty_drop::EMPTY_DROP_INFO,
|
||||
crate::empty_enum::EMPTY_ENUM_INFO,
|
||||
crate::empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO,
|
||||
crate::empty_with_brackets::EMPTY_ENUM_VARIANTS_WITH_BRACKETS_INFO,
|
||||
crate::empty_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS_INFO,
|
||||
crate::endian_bytes::BIG_ENDIAN_BYTES_INFO,
|
||||
crate::endian_bytes::HOST_ENDIAN_BYTES_INFO,
|
||||
crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO,
|
||||
@ -385,6 +386,7 @@
|
||||
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
crate::methods::MANUAL_FIND_MAP_INFO,
|
||||
crate::methods::MANUAL_IS_VARIANT_AND_INFO,
|
||||
crate::methods::MANUAL_NEXT_BACK_INFO,
|
||||
crate::methods::MANUAL_OK_OR_INFO,
|
||||
crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO,
|
||||
@ -408,6 +410,7 @@
|
||||
crate::methods::NO_EFFECT_REPLACE_INFO,
|
||||
crate::methods::OBFUSCATED_IF_ELSE_INFO,
|
||||
crate::methods::OK_EXPECT_INFO,
|
||||
crate::methods::OPTION_AS_REF_CLONED_INFO,
|
||||
crate::methods::OPTION_AS_REF_DEREF_INFO,
|
||||
crate::methods::OPTION_FILTER_MAP_INFO,
|
||||
crate::methods::OPTION_MAP_OR_ERR_OK_INFO,
|
||||
@ -433,6 +436,7 @@
|
||||
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
|
||||
crate::methods::STRING_EXTEND_CHARS_INFO,
|
||||
crate::methods::STRING_LIT_CHARS_ANY_INFO,
|
||||
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
|
||||
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,
|
||||
crate::methods::SUSPICIOUS_MAP_INFO,
|
||||
crate::methods::SUSPICIOUS_SPLITN_INFO,
|
||||
@ -576,6 +580,7 @@
|
||||
crate::ptr::MUT_FROM_REF_INFO,
|
||||
crate::ptr::PTR_ARG_INFO,
|
||||
crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO,
|
||||
crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO,
|
||||
crate::pub_use::PUB_USE_INFO,
|
||||
crate::question_mark::QUESTION_MARK_INFO,
|
||||
crate::question_mark_used::QUESTION_MARK_USED_INFO,
|
||||
@ -648,6 +653,7 @@
|
||||
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
|
||||
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
|
||||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
|
||||
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
|
||||
|
@ -4,7 +4,7 @@
|
||||
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor};
|
||||
use rustc_hir::{Body, Expr, ExprKind, HirId, ItemKind, Lit, Node, Stmt, StmtKind};
|
||||
use rustc_hir::{Block, Body, Expr, ExprKind, FnRetTy, HirId, ItemKind, Lit, Node, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, FloatTy, IntTy, PolyFnSig, Ty};
|
||||
@ -122,13 +122,42 @@ fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) {
|
||||
impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
match &expr.kind {
|
||||
ExprKind::Block(
|
||||
Block {
|
||||
stmts, expr: Some(_), ..
|
||||
},
|
||||
_,
|
||||
) => {
|
||||
if let Some(parent) = self.cx.tcx.hir().find_parent(expr.hir_id)
|
||||
&& let Some(fn_sig) = parent.fn_sig()
|
||||
&& let FnRetTy::Return(_ty) = fn_sig.decl.output
|
||||
{
|
||||
// We cannot check the exact type since it's a `hir::Ty`` which does not implement `is_numeric`
|
||||
self.ty_bounds.push(ExplicitTyBound(true));
|
||||
for stmt in *stmts {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.ty_bounds.pop();
|
||||
// Ignore return expr since we know its type was inferred from return ty
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
// Ignore return expr since we know its type was inferred from return ty
|
||||
ExprKind::Ret(_) => return,
|
||||
|
||||
ExprKind::Call(func, args) => {
|
||||
if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
|
||||
for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) {
|
||||
// Push found arg type, then visit arg.
|
||||
self.ty_bounds.push((*bound).into());
|
||||
self.visit_expr(expr);
|
||||
self.ty_bounds.pop();
|
||||
// If is from macro, try to use last bound type (typically pushed when visiting stmt),
|
||||
// otherwise push found arg type, then visit arg,
|
||||
if expr.span.from_expansion() {
|
||||
self.visit_expr(expr);
|
||||
} else {
|
||||
self.ty_bounds.push((*bound).into());
|
||||
self.visit_expr(expr);
|
||||
self.ty_bounds.pop();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -137,7 +166,7 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
ExprKind::MethodCall(_, receiver, args, _) => {
|
||||
if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
let fn_sig = self.cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
|
||||
for (expr, bound) in iter::zip(std::iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) {
|
||||
for (expr, bound) in iter::zip(iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) {
|
||||
self.ty_bounds.push((*bound).into());
|
||||
self.visit_expr(expr);
|
||||
self.ty_bounds.pop();
|
||||
|
@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_ast::ast::{Item, ItemKind, VariantData};
|
||||
use rustc_ast::ast::{Item, ItemKind, Variant, VariantData};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
@ -27,9 +27,38 @@
|
||||
restriction,
|
||||
"finds struct declarations with empty brackets"
|
||||
}
|
||||
declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]);
|
||||
|
||||
impl EarlyLintPass for EmptyStructsWithBrackets {
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Finds enum variants without fields that are declared with empty brackets.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Empty brackets while defining enum variants are redundant and can be omitted.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// enum MyEnum {
|
||||
/// HasData(u8),
|
||||
/// HasNoData(), // redundant parentheses
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// enum MyEnum {
|
||||
/// HasData(u8),
|
||||
/// HasNoData,
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
|
||||
restriction,
|
||||
"finds enum variants with empty brackets"
|
||||
}
|
||||
|
||||
declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
|
||||
|
||||
impl EarlyLintPass for EmptyWithBrackets {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
let span_after_ident = item.span.with_lo(item.ident.span.hi());
|
||||
|
||||
@ -53,6 +82,27 @@ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) {
|
||||
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
|
||||
|
||||
if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
|
||||
span_after_ident,
|
||||
"enum variant has empty brackets",
|
||||
|diagnostic| {
|
||||
diagnostic.span_suggestion_hidden(
|
||||
span_after_ident,
|
||||
"remove the brackets",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_no_ident_token(braces_span_str: &str) -> bool {
|
@ -3,7 +3,7 @@
|
||||
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks};
|
||||
use clippy_utils::{contains_return, higher, in_constant, is_else_clause, is_res_lang_ctor, path_res, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -74,6 +74,11 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
return;
|
||||
}
|
||||
|
||||
// `bool::then()` and `bool::then_some()` are not const
|
||||
if in_constant(cx, expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ctxt = expr.span.ctxt();
|
||||
|
||||
if let Some(higher::If {
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints subtraction between an [`Instant`] and a [`Duration`].
|
||||
/// Lints subtraction between an `Instant` and a `Duration`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Unchecked subtraction could cause underflow on certain platforms, leading to
|
||||
@ -57,9 +57,6 @@
|
||||
/// # use std::time::{Instant, Duration};
|
||||
/// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
|
||||
/// ```
|
||||
///
|
||||
/// [`Duration`]: std::time::Duration
|
||||
/// [`Instant::now()`]: std::time::Instant::now;
|
||||
#[clippy::version = "1.67.0"]
|
||||
pub UNCHECKED_DURATION_SUBTRACTION,
|
||||
pedantic,
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! lint on enum variants that are prefixed or suffixed by the same characters
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
|
||||
use clippy_utils::is_bool;
|
||||
use clippy_utils::macros::span_is_local;
|
||||
use clippy_utils::source::is_present_in_source;
|
||||
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
|
||||
@ -231,6 +232,10 @@ fn check_fields(cx: &LateContext<'_>, threshold: u64, item: &Item<'_>, fields: &
|
||||
(false, _) => ("pre", prefix),
|
||||
(true, false) => ("post", postfix),
|
||||
};
|
||||
if fields.iter().all(|field| is_bool(field.ty)) {
|
||||
// If all fields are booleans, we don't want to emit this lint.
|
||||
return;
|
||||
}
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
STRUCT_FIELD_NAMES,
|
||||
|
@ -5,7 +5,8 @@
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{FnRetTy, ImplItemKind, ImplicitSelfKind, ItemKind, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::{sym, Symbol};
|
||||
@ -152,7 +153,8 @@ fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol
|
||||
|
||||
impl LateLintPass<'_> for IterWithoutIntoIter {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
|
||||
if let ItemKind::Impl(imp) = item.kind
|
||||
if !in_external_macro(cx.sess(), item.span)
|
||||
&& let ItemKind::Impl(imp) = item.kind
|
||||
&& let TyKind::Ref(_, self_ty_without_ref) = &imp.self_ty.kind
|
||||
&& let Some(trait_ref) = imp.of_trait
|
||||
&& trait_ref
|
||||
@ -219,7 +221,8 @@ fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let ImplItemKind::Fn(sig, _) = item.kind
|
||||
if !in_external_macro(cx.sess(), item.span)
|
||||
&& let ImplItemKind::Fn(sig, _) = item.kind
|
||||
&& let FnRetTy::Return(ret) = sig.decl.output
|
||||
&& is_nameable_in_impl_trait(ret)
|
||||
&& cx.tcx.generics_of(item_did).params.is_empty()
|
||||
|
@ -115,7 +115,7 @@
|
||||
mod else_if_without_else;
|
||||
mod empty_drop;
|
||||
mod empty_enum;
|
||||
mod empty_structs_with_brackets;
|
||||
mod empty_with_brackets;
|
||||
mod endian_bytes;
|
||||
mod entry;
|
||||
mod enum_clike;
|
||||
@ -272,6 +272,7 @@
|
||||
mod precedence;
|
||||
mod ptr;
|
||||
mod ptr_offset_with_cast;
|
||||
mod pub_underscore_fields;
|
||||
mod pub_use;
|
||||
mod question_mark;
|
||||
mod question_mark_used;
|
||||
@ -322,6 +323,7 @@
|
||||
mod tabs_in_doc_comments;
|
||||
mod temporary_assignment;
|
||||
mod tests_outside_test_module;
|
||||
mod thread_local_initializer_can_be_made_const;
|
||||
mod to_digit_is_some;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
@ -571,6 +573,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
verbose_bit_mask_threshold,
|
||||
warn_on_all_wildcard_imports,
|
||||
check_private_items,
|
||||
pub_underscore_fields_behavior,
|
||||
|
||||
blacklisted_names: _,
|
||||
cyclomatic_complexity_threshold: _,
|
||||
@ -947,7 +950,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
})
|
||||
});
|
||||
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
|
||||
store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
|
||||
store.register_early_pass(|| Box::new(empty_with_brackets::EmptyWithBrackets));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
|
||||
store.register_early_pass(|| Box::new(pub_use::PubUse));
|
||||
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
|
||||
@ -1080,7 +1083,15 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
store.register_late_pass(|_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity));
|
||||
store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences));
|
||||
store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions));
|
||||
store.register_late_pass(|_| Box::new(unconditional_recursion::UnconditionalRecursion));
|
||||
store.register_late_pass(|_| Box::<unconditional_recursion::UnconditionalRecursion>::default());
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(pub_underscore_fields::PubUnderscoreFields {
|
||||
behavior: pub_underscore_fields_behavior,
|
||||
})
|
||||
});
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
|
||||
});
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
@ -96,8 +96,7 @@ fn should_lint(cx: &LateContext<'_>, args: &[Expr<'_>], method_str: &str) -> boo
|
||||
ExprKind::Path(qpath) => cx
|
||||
.qpath_res(qpath, fm_arg.hir_id)
|
||||
.opt_def_id()
|
||||
.map(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD))
|
||||
.unwrap_or_default(),
|
||||
.is_some_and(|did| match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)),
|
||||
// Detect `|x| x.ok()`
|
||||
ExprKind::Closure(Closure { body, .. }) => {
|
||||
if let Body {
|
||||
|
@ -26,13 +26,14 @@ fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) ->
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(body.value);
|
||||
let arg_id = body.params[0].pat.hir_id;
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
|
||||
if ident.name == method_name
|
||||
&& let hir::ExprKind::Path(path) = &receiver.kind
|
||||
&& let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
|
||||
&& !body.params.is_empty()
|
||||
{
|
||||
let arg_id = body.params[0].pat.hir_id;
|
||||
return arg_id == *local;
|
||||
}
|
||||
false
|
||||
|
@ -1,80 +1,181 @@
|
||||
use clippy_utils::ty::get_iterator_item_ty;
|
||||
use hir::ExprKind;
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
|
||||
use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{indent_of, reindent_multiline};
|
||||
use clippy_utils::{is_trait_method, peel_blocks, span_contains_comment};
|
||||
use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::QPath;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||
use rustc_span::Span;
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn is_method(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol) -> bool {
|
||||
match &expr.kind {
|
||||
hir::ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name,
|
||||
hir::ExprKind::Path(QPath::Resolved(_, segments)) => {
|
||||
segments.segments.last().unwrap().ident.name == method_name
|
||||
///
|
||||
/// Returns true if the expression is a method call to `method_name`
|
||||
/// e.g. `a.method_name()` or `Option::method_name`.
|
||||
///
|
||||
/// The type-checker verifies for us that the method accepts the right kind of items
|
||||
/// (e.g. `Option::is_some` accepts `Option<_>`), so we don't need to check that.
|
||||
///
|
||||
/// How to capture each case:
|
||||
///
|
||||
/// `.filter(|a| { std::option::Option::is_some(a) })`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a closure, getting unwrapped and
|
||||
/// recursively checked.
|
||||
/// `std::option::Option::is_some(a)`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a call. It unwraps to a path with
|
||||
/// `QPath::TypeRelative`. Since this is a type relative path, we need to check the method name, the
|
||||
/// type, and that the parameter of the closure is passed in the call. This part is the dual of
|
||||
/// `receiver.method_name()` below.
|
||||
///
|
||||
/// `filter(std::option::Option::is_some);`
|
||||
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ <- this is a type relative path, like above, we check the
|
||||
/// type and the method name.
|
||||
///
|
||||
/// `filter(|a| a.is_some());`
|
||||
/// ^^^^^^^^^^^^^^^ <- this is a method call inside a closure,
|
||||
/// we check that the parameter of the closure is the receiver of the method call and don't allow
|
||||
/// any other parameters.
|
||||
fn is_method(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
type_symbol: Symbol,
|
||||
method_name: Symbol,
|
||||
params: &[&hir::Pat<'_>],
|
||||
) -> bool {
|
||||
fn pat_is_recv(ident: Ident, param: &hir::Pat<'_>) -> bool {
|
||||
match param.kind {
|
||||
hir::PatKind::Binding(_, _, other, _) => ident == other,
|
||||
hir::PatKind::Ref(pat, _) => pat_is_recv(ident, pat),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
match expr.kind {
|
||||
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, recv, ..) => {
|
||||
// compare the identifier of the receiver to the parameter
|
||||
// we are in a filter => closure has a single parameter and a single, non-block
|
||||
// expression, this means that the parameter shadows all outside variables with
|
||||
// the same name => avoid FPs. If the parameter is not the receiver, then this hits
|
||||
// outside variables => avoid FP
|
||||
if ident.name == method_name
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = recv.kind
|
||||
&& let &[seg] = path.segments
|
||||
&& params.iter().any(|p| pat_is_recv(seg.ident, p))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
},
|
||||
// This is used to check for complete paths via `|a| std::option::Option::is_some(a)`
|
||||
// this then unwraps to a path with `QPath::TypeRelative`
|
||||
// we pass the params as they've been passed to the current call through the closure
|
||||
hir::ExprKind::Call(expr, [param]) => {
|
||||
// this will hit the `QPath::TypeRelative` case and check that the method name is correct
|
||||
if is_method(cx, expr, type_symbol, method_name, params)
|
||||
// we then check that this is indeed passing the parameter of the closure
|
||||
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.kind
|
||||
&& let &[seg] = path.segments
|
||||
&& params.iter().any(|p| pat_is_recv(seg.ident, p))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
},
|
||||
hir::ExprKind::Path(QPath::TypeRelative(ty, mname)) => {
|
||||
let ty = cx.typeck_results().node_type(ty.hir_id);
|
||||
if let Some(did) = cx.tcx.get_diagnostic_item(type_symbol)
|
||||
&& ty.ty_adt_def() == cx.tcx.type_of(did).skip_binder().ty_adt_def()
|
||||
{
|
||||
return mname.ident.name == method_name;
|
||||
}
|
||||
false
|
||||
},
|
||||
hir::ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name,
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(body.value);
|
||||
let arg_id = body.params[0].pat.hir_id;
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, receiver, ..) => {
|
||||
if ident.name == method_name
|
||||
&& let hir::ExprKind::Path(path) = &receiver.kind
|
||||
&& let Res::Local(ref local) = cx.qpath_res(path, receiver.hir_id)
|
||||
{
|
||||
return arg_id == *local;
|
||||
}
|
||||
false
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
let params = body.params.iter().map(|param| param.pat).collect::<Vec<_>>();
|
||||
is_method(cx, closure_expr, type_symbol, method_name, params.as_slice())
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
if let hir::Node::Expr(parent_expr) = cx.tcx.hir().get_parent(expr.hir_id) {
|
||||
is_method(cx, parent_expr, rustc_span::sym::map)
|
||||
} else {
|
||||
false
|
||||
if let Some(expr) = get_parent_expr(cx, expr)
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let hir::ExprKind::MethodCall(path, _, _, _) = expr.kind
|
||||
&& path.ident.name == rustc_span::sym::map
|
||||
{
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
|
||||
let is_iterator = is_trait_method(cx, expr, sym::Iterator);
|
||||
let parent_is_not_map = !parent_is_map(cx, expr);
|
||||
enum FilterType {
|
||||
IsSome,
|
||||
IsOk,
|
||||
}
|
||||
|
||||
if is_iterator
|
||||
&& parent_is_not_map
|
||||
&& is_method(cx, filter_arg, sym!(is_some))
|
||||
&& !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
|
||||
/// Returns the `FilterType` of the expression if it is a filter over an Iter<Option> or
|
||||
/// Iter<Result> with the parent expression not being a map, and not having a comment in the span of
|
||||
/// the filter. If it is not a filter over an Iter<Option> or Iter<Result> then it returns None
|
||||
///
|
||||
/// How this is done:
|
||||
/// 1. we know that this is invoked in a method call with `filter` as the method name via `mod.rs`
|
||||
/// 2. we check that we are in a trait method. Therefore we are in an
|
||||
/// `(x as Iterator).filter({filter_arg})` method call.
|
||||
/// 3. we check that the parent expression is not a map. This is because we don't want to lint
|
||||
/// twice, and we already have a specialized lint for that.
|
||||
/// 4. we check that the span of the filter does not contain a comment.
|
||||
/// 5. we get the type of the `Item` in the `Iterator`, and compare against the type of Option and
|
||||
/// Result.
|
||||
/// 6. we finally check the contents of the filter argument to see if it is a call to `is_some` or
|
||||
/// `is_ok`.
|
||||
/// 7. if all of the above are true, then we return the `FilterType`
|
||||
fn expression_type(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &hir::Expr<'_>,
|
||||
filter_arg: &hir::Expr<'_>,
|
||||
filter_span: Span,
|
||||
) -> Option<FilterType> {
|
||||
if !is_trait_method(cx, expr, sym::Iterator)
|
||||
|| parent_is_map(cx, expr)
|
||||
|| span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ITER_FILTER_IS_SOME,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
"`filter` for `is_some` on iterator over `Option`",
|
||||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if is_iterator
|
||||
&& parent_is_not_map
|
||||
&& is_method(cx, filter_arg, sym!(is_ok))
|
||||
&& !span_contains_comment(cx.sess().source_map(), filter_span.with_hi(expr.span.hi()))
|
||||
if let hir::ExprKind::MethodCall(_, receiver, _, _) = expr.kind
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Option)
|
||||
&& let opt_ty = cx.tcx.type_of(opt_defid).skip_binder()
|
||||
&& iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def()
|
||||
&& is_method(cx, filter_arg, sym::Option, sym!(is_some), &[])
|
||||
{
|
||||
return Some(FilterType::IsSome);
|
||||
}
|
||||
|
||||
if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Result)
|
||||
&& let opt_ty = cx.tcx.type_of(opt_defid).skip_binder()
|
||||
&& iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def()
|
||||
&& is_method(cx, filter_arg, sym::Result, sym!(is_ok), &[])
|
||||
{
|
||||
return Some(FilterType::IsOk);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, filter_span: Span) {
|
||||
// we are in a filter inside an iterator
|
||||
match expression_type(cx, expr, filter_arg, filter_span) {
|
||||
None => (),
|
||||
Some(FilterType::IsOk) => span_lint_and_sugg(
|
||||
cx,
|
||||
ITER_FILTER_IS_OK,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
@ -82,6 +183,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_arg: &hir
|
||||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
),
|
||||
Some(FilterType::IsSome) => span_lint_and_sugg(
|
||||
cx,
|
||||
ITER_FILTER_IS_SOME,
|
||||
filter_span.with_hi(expr.span.hi()),
|
||||
"`filter` for `is_some` on iterator over `Option`",
|
||||
"consider using `flatten` instead",
|
||||
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, filter_span)).into_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
59
clippy_lints/src/methods/manual_is_variant_and.rs
Normal file
59
clippy_lints/src/methods/manual_is_variant_and.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use clippy_config::msrvs::{Msrv, OPTION_RESULT_IS_VARIANT_AND};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::MANUAL_IS_VARIANT_AND;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &'tcx rustc_hir::Expr<'_>,
|
||||
map_recv: &'tcx rustc_hir::Expr<'_>,
|
||||
map_arg: &'tcx rustc_hir::Expr<'_>,
|
||||
map_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
// Don't lint if:
|
||||
|
||||
// 1. the `expr` is generated by a macro
|
||||
if expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. the caller of `map()` is neither `Option` nor `Result`
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(map_recv), sym::Result);
|
||||
if !is_option && !is_result {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
|
||||
if !cx.typeck_results().expr_ty(expr).is_bool() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. msrv doesn't meet `OPTION_RESULT_IS_VARIANT_AND`
|
||||
if !msrv.meets(OPTION_RESULT_IS_VARIANT_AND) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lint_msg = if is_option {
|
||||
"called `map(<f>).unwrap_or_default()` on an `Option` value"
|
||||
} else {
|
||||
"called `map(<f>).unwrap_or_default()` on a `Result` value"
|
||||
};
|
||||
let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
expr.span.with_lo(map_span.lo()),
|
||||
lint_msg,
|
||||
"use",
|
||||
format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
@ -2,9 +2,10 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{is_diag_trait_item, peel_blocks};
|
||||
use clippy_utils::{is_diag_trait_item, match_def_path, paths, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::Mutability;
|
||||
use rustc_middle::ty;
|
||||
@ -14,60 +15,110 @@
|
||||
|
||||
use super::MAP_CLONE;
|
||||
|
||||
// If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't
|
||||
// run this lint because it would overlap with `useless_asref` which provides a better suggestion
|
||||
// in this case.
|
||||
fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> bool {
|
||||
if is_diag_trait_item(cx, method_id, sym::Iterator) {
|
||||
return true;
|
||||
}
|
||||
// We check if it's an `Option` or a `Result`.
|
||||
if let Some(id) = cx.tcx.impl_of_method(method_id) {
|
||||
let identity = cx.tcx.type_of(id).instantiate_identity();
|
||||
if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// We check if the previous method call is `as_ref`.
|
||||
if let hir::ExprKind::MethodCall(path1, receiver, _, _) = &e.kind
|
||||
&& let hir::ExprKind::MethodCall(path2, _, _, _) = &receiver.kind
|
||||
{
|
||||
return path2.ident.name != sym::as_ref || path1.ident.name != sym::map;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: &Msrv) {
|
||||
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||
&& (cx.tcx.impl_of_method(method_id).map_or(false, |id| {
|
||||
is_type_diagnostic_item(cx, cx.tcx.type_of(id).instantiate_identity(), sym::Option)
|
||||
}) || is_diag_trait_item(cx, method_id, sym::Iterator))
|
||||
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind
|
||||
&& should_run_lint(cx, e, method_id)
|
||||
{
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(closure_body.value);
|
||||
match closure_body.params[0].pat.kind {
|
||||
hir::PatKind::Ref(inner, hir::Mutability::Not) => {
|
||||
if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind {
|
||||
if ident_eq(name, closure_expr) {
|
||||
lint_explicit_closure(cx, e.span, recv.span, true, msrv);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
|
||||
if ident_eq(name, inner) {
|
||||
if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
|
||||
match arg.kind {
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(closure_body.value);
|
||||
match closure_body.params[0].pat.kind {
|
||||
hir::PatKind::Ref(inner, hir::Mutability::Not) => {
|
||||
if let hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) = inner.kind {
|
||||
if ident_eq(name, closure_expr) {
|
||||
lint_explicit_closure(cx, e.span, recv.span, true, msrv);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||
if ident_eq(name, obj) && method.ident.name == sym::clone
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
|
||||
// no autoderefs
|
||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||
{
|
||||
let obj_ty = cx.typeck_results().expr_ty(obj);
|
||||
if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
|
||||
if matches!(mutability, Mutability::Not) {
|
||||
let copy = is_copy(cx, *ty);
|
||||
lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
|
||||
hir::PatKind::Binding(hir::BindingAnnotation::NONE, .., name, None) => {
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
|
||||
if ident_eq(name, inner) {
|
||||
if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
|
||||
lint_explicit_closure(cx, e.span, recv.span, true, msrv);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lint_needless_cloning(cx, e.span, recv.span);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||
if ident_eq(name, obj) && method.ident.name == sym::clone
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
|
||||
// no autoderefs
|
||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||
{
|
||||
let obj_ty = cx.typeck_results().expr_ty(obj);
|
||||
if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
|
||||
if matches!(mutability, Mutability::Not) {
|
||||
let copy = is_copy(cx, *ty);
|
||||
lint_explicit_closure(cx, e.span, recv.span, copy, msrv);
|
||||
}
|
||||
} else {
|
||||
lint_needless_cloning(cx, e.span, recv.span);
|
||||
}
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Call(call, [_]) => {
|
||||
if let hir::ExprKind::Path(qpath) = call.kind {
|
||||
handle_path(cx, call, &qpath, e, recv);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Path(qpath) => handle_path(cx, arg, &qpath, e, recv),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_path(
|
||||
cx: &LateContext<'_>,
|
||||
arg: &hir::Expr<'_>,
|
||||
qpath: &hir::QPath<'_>,
|
||||
e: &hir::Expr<'_>,
|
||||
recv: &hir::Expr<'_>,
|
||||
) {
|
||||
if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id()
|
||||
&& match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
|
||||
{
|
||||
// FIXME: It would be better to infer the type to check if it's copyable or not
|
||||
// to suggest to use `.copied()` instead of `.cloned()` where applicable.
|
||||
lint_path(cx, e.span, recv.span);
|
||||
}
|
||||
}
|
||||
|
||||
fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
|
||||
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
|
||||
path.segments.len() == 1 && path.segments[0].ident == name
|
||||
@ -88,6 +139,23 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
|
||||
);
|
||||
}
|
||||
|
||||
fn lint_path(cx: &LateContext<'_>, replace: Span, root: Span) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAP_CLONE,
|
||||
replace,
|
||||
"you are explicitly cloning with `.map()`",
|
||||
"consider calling the dedicated `cloned` method",
|
||||
format!(
|
||||
"{}.cloned()",
|
||||
snippet_with_applicability(cx, root, "..", &mut applicability),
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: &Msrv) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
|
@ -51,6 +51,7 @@
|
||||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod manual_is_variant_and;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
mod manual_saturating_arithmetic;
|
||||
@ -70,6 +71,7 @@
|
||||
mod obfuscated_if_else;
|
||||
mod ok_expect;
|
||||
mod open_options;
|
||||
mod option_as_ref_cloned;
|
||||
mod option_as_ref_deref;
|
||||
mod option_map_or_err_ok;
|
||||
mod option_map_or_none;
|
||||
@ -93,6 +95,7 @@
|
||||
mod single_char_push_string;
|
||||
mod skip_while_next;
|
||||
mod stable_sort_primitive;
|
||||
mod str_split;
|
||||
mod str_splitn;
|
||||
mod string_extend_chars;
|
||||
mod string_lit_chars_any;
|
||||
@ -2589,11 +2592,11 @@
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `x.get(0)` instead of
|
||||
/// `x.first()`.
|
||||
/// `x.first()` or `x.front()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `x.first()` is easier to read and has the same
|
||||
/// result.
|
||||
/// Using `x.first()` for `Vec`s and slices or `x.front()`
|
||||
/// for `VecDeque`s is easier to read and has the same result.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@ -2609,7 +2612,7 @@
|
||||
#[clippy::version = "1.63.0"]
|
||||
pub GET_FIRST,
|
||||
style,
|
||||
"Using `x.get(0)` when `x.first()` is simpler"
|
||||
"Using `x.get(0)` when `x.first()` or `x.front()` is simpler"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
@ -3784,7 +3787,7 @@
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This pattern is often followed by manual unwrapping of the `Option`. The simplification
|
||||
/// results in more readable and succint code without the need for manual unwrapping.
|
||||
/// results in more readable and succinct code without the need for manual unwrapping.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@ -3810,7 +3813,7 @@
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This pattern is often followed by manual unwrapping of `Result`. The simplification
|
||||
/// results in more readable and succint code without the need for manual unwrapping.
|
||||
/// results in more readable and succinct code without the need for manual unwrapping.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@ -3829,6 +3832,87 @@
|
||||
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability. These can be written more concisely as `option.is_some_and(f)` and `result.is_ok_and(f)`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let option = Some(1);
|
||||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.map(|a| a > 10).unwrap_or_default();
|
||||
/// result.map(|a| a > 10).unwrap_or_default();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # let option = Some(1);
|
||||
/// # let result: Result<usize, ()> = Ok(1);
|
||||
/// option.is_some_and(|a| a > 10);
|
||||
/// result.is_ok_and(|a| a > 10);
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub MANUAL_IS_VARIANT_AND,
|
||||
pedantic,
|
||||
"using `.map(f).unwrap_or_default()`, which is more succinctly expressed as `is_some_and(f)` or `is_ok_and(f)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// Checks for usages of `str.trim().split("\n")` and `str.trim().split("\r\n")`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Hard-coding the line endings makes the code less compatible. `str.lines` should be used instead.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// "some\ntext\nwith\nnewlines\n".trim().split('\n');
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// "some\ntext\nwith\nnewlines\n".lines();
|
||||
/// ```
|
||||
///
|
||||
/// ### Known Problems
|
||||
///
|
||||
/// This lint cannot detect if the split is intentionally restricted to a single type of newline (`"\n"` or
|
||||
/// `"\r\n"`), for example during the parsing of a specific file format in which precisely one newline type is
|
||||
/// valid.
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub STR_SPLIT_AT_NEWLINE,
|
||||
pedantic,
|
||||
"splitting a trimmed string at hard-coded newlines"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.as_ref().cloned()` and `.as_mut().cloned()` on `Option`s
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely by cloning the `Option` directly.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
|
||||
/// bar.as_ref().cloned()
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn foo(bar: &Option<Vec<u8>>) -> Option<Vec<u8>> {
|
||||
/// bar.clone()
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub OPTION_AS_REF_CLONED,
|
||||
pedantic,
|
||||
"cloning an `Option` via `as_ref().cloned()`"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
@ -3983,6 +4067,9 @@ pub fn new(
|
||||
RESULT_FILTER_MAP,
|
||||
ITER_FILTER_IS_SOME,
|
||||
ITER_FILTER_IS_OK,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
OPTION_AS_REF_CLONED,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
@ -4230,7 +4317,10 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
|
||||
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
|
||||
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
|
||||
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv),
|
||||
("cloned", []) => {
|
||||
cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
|
||||
option_as_ref_cloned::check(cx, recv, span);
|
||||
},
|
||||
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
|
||||
needless_collect::check(cx, span, expr, recv, call_span);
|
||||
match method_call(recv) {
|
||||
@ -4569,6 +4659,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
("sort_unstable_by", [arg]) => {
|
||||
unnecessary_sort_by::check(cx, expr, recv, arg, true);
|
||||
},
|
||||
("split", [arg]) => {
|
||||
str_split::check(cx, expr, recv, arg);
|
||||
},
|
||||
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
|
||||
if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
|
||||
suspicious_splitn::check(cx, name, expr, recv, count);
|
||||
@ -4664,7 +4757,13 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
}
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_or_default" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
|
||||
("unwrap_or_default", []) => {
|
||||
if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) {
|
||||
manual_is_variant_and::check(cx, expr, m_recv, arg, span, &self.msrv);
|
||||
}
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
|
||||
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
|
||||
},
|
||||
("unwrap_or_else", [u_arg]) => {
|
||||
|
24
clippy_lints/src/methods/option_as_ref_cloned.rs
Normal file
24
clippy_lints/src/methods/option_as_ref_cloned.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::{method_call, OPTION_AS_REF_CLONED};
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, cloned_recv: &Expr<'_>, cloned_ident_span: Span) {
|
||||
if let Some((method @ ("as_ref" | "as_mut"), as_ref_recv, [], as_ref_ident_span, _)) = method_call(cloned_recv)
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(as_ref_recv).peel_refs(), sym::Option)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OPTION_AS_REF_CLONED,
|
||||
as_ref_ident_span.to(cloned_ident_span),
|
||||
&format!("cloning an `Option<_>` using `.{method}().cloned()`"),
|
||||
"this can be written more concisely by cloning the `Option<_>` directly",
|
||||
"clone".into(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
@ -72,7 +72,7 @@ pub(super) fn check<'tcx>(
|
||||
}
|
||||
|
||||
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
|
||||
let suggest_is_some_and = msrv.meets(msrvs::OPTION_IS_SOME_AND)
|
||||
let suggest_is_some_and = msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND)
|
||||
&& matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
|
||||
if matches!(lit.node, rustc_ast::LitKind::Bool(false)));
|
||||
|
||||
|
@ -33,7 +33,7 @@ pub(super) fn check(
|
||||
&& path.chars().all(char::is_alphanumeric)
|
||||
{
|
||||
let mut sugg = snippet(cx, recv.span, "..").into_owned();
|
||||
if msrv.meets(msrvs::OPTION_IS_SOME_AND) {
|
||||
if msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) {
|
||||
let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#);
|
||||
} else {
|
||||
let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#);
|
||||
|
@ -13,7 +13,11 @@ pub(super) fn check(
|
||||
as_str_span: Span,
|
||||
other_method_span: Span,
|
||||
) {
|
||||
if cx.typeck_results().expr_ty(recv).ty_adt_def().is_some_and(|adt| Some(adt.did()) == cx.tcx.lang_items().string())
|
||||
if cx
|
||||
.typeck_results()
|
||||
.expr_ty(recv)
|
||||
.ty_adt_def()
|
||||
.is_some_and(|adt| Some(adt.did()) == cx.tcx.lang_items().string())
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
|
38
clippy_lints/src/methods/str_split.rs
Normal file
38
clippy_lints/src/methods/str_split.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::visitors::is_const_evaluatable;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::STR_SPLIT_AT_NEWLINE;
|
||||
|
||||
pub(super) fn check<'a>(cx: &LateContext<'a>, expr: &'_ Expr<'_>, split_recv: &'a Expr<'_>, split_arg: &'_ Expr<'_>) {
|
||||
// We're looking for `A.trim().split(B)`, where the adjusted type of `A` is `&str` (e.g. an
|
||||
// expression returning `String`), and `B` is a `Pattern` that hard-codes a newline (either `"\n"`
|
||||
// or `"\r\n"`). There are a lot of ways to specify a pattern, and this lint only checks the most
|
||||
// basic ones: a `'\n'`, `"\n"`, and `"\r\n"`.
|
||||
if let ExprKind::MethodCall(trim_method_name, trim_recv, [], _) = split_recv.kind
|
||||
&& trim_method_name.ident.as_str() == "trim"
|
||||
&& cx.typeck_results().expr_ty_adjusted(trim_recv).peel_refs().is_str()
|
||||
&& !is_const_evaluatable(cx, trim_recv)
|
||||
&& let ExprKind::Lit(split_lit) = split_arg.kind
|
||||
&& (matches!(split_lit.node, LitKind::Char('\n'))
|
||||
|| matches!(split_lit.node, LitKind::Str(sym, _) if (sym.as_str() == "\n" || sym.as_str() == "\r\n")))
|
||||
{
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
expr.span,
|
||||
"using `str.trim().split()` with hard-coded newlines",
|
||||
"use `str.lines()` instead",
|
||||
format!(
|
||||
"{}.lines()",
|
||||
snippet_with_context(cx, trim_recv.span, expr.span.ctxt(), "..", &mut app).0
|
||||
),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
@ -3,13 +3,13 @@
|
||||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs};
|
||||
use clippy_utils::visitors::find_all_ret_expressions;
|
||||
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, Node};
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
|
||||
use rustc_hir_typeck::{FnCtxt, Inherited};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
@ -246,6 +246,19 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
|
||||
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
|
||||
&& let Some(arg_snippet) = snippet_opt(cx, argument_expr.span)
|
||||
{
|
||||
// We may end-up here because of an expression like `x.to_string().split(…)` where the type of `x`
|
||||
// implements `AsRef<str>` but does not implement `Deref<Target = str>`. In this case, we have to
|
||||
// add `.as_ref()` to the suggestion.
|
||||
let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
|
||||
&& let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
|
||||
&& cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target")
|
||||
!= Some(cx.tcx.types.str_)
|
||||
{
|
||||
".as_ref()"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
// The next suggestion may be incorrect because the removal of the `to_owned`-like
|
||||
// function could cause the iterator to hold a reference to a resource that is used
|
||||
// mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
|
||||
@ -255,7 +268,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
|
||||
parent.span,
|
||||
&format!("unnecessary use of `{method_name}`"),
|
||||
"use",
|
||||
format!("{receiver_snippet}.split({arg_snippet})"),
|
||||
format!("{receiver_snippet}{as_ref}.split({arg_snippet})"),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
return true;
|
||||
@ -445,7 +458,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
|
||||
{
|
||||
let bound_fn_sig = cx.tcx.fn_sig(callee_def_id);
|
||||
let fn_sig = bound_fn_sig.skip_binder();
|
||||
if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id)
|
||||
if let Some(arg_index) = recv
|
||||
.into_iter()
|
||||
.chain(call_args)
|
||||
.position(|arg| arg.hir_id == expr.hir_id)
|
||||
&& let param_ty = fn_sig.input(arg_index).skip_binder()
|
||||
&& let ty::Param(ParamTy { index: param_index , ..}) = *param_ty.kind()
|
||||
// https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021
|
||||
|
@ -1,19 +1,52 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::walk_ptrs_ty_depth;
|
||||
use clippy_utils::{get_parent_expr, is_trait_method};
|
||||
use clippy_utils::{get_parent_expr, is_diag_trait_item, match_def_path, paths, peel_blocks};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use core::ops::ControlFlow;
|
||||
|
||||
use super::USELESS_ASREF;
|
||||
|
||||
/// Returns the first type inside the `Option`/`Result` type passed as argument.
|
||||
fn get_enum_ty(enum_ty: Ty<'_>) -> Option<Ty<'_>> {
|
||||
struct ContainsTyVisitor {
|
||||
level: usize,
|
||||
}
|
||||
|
||||
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTyVisitor {
|
||||
type BreakTy = Ty<'tcx>;
|
||||
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
self.level += 1;
|
||||
if self.level == 1 {
|
||||
t.super_visit_with(self)
|
||||
} else {
|
||||
ControlFlow::Break(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match enum_ty.visit_with(&mut ContainsTyVisitor { level: 0 }) {
|
||||
ControlFlow::Break(ty) => Some(ty),
|
||||
ControlFlow::Continue(()) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for the `USELESS_ASREF` lint.
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
|
||||
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
|
||||
// check if the call is to the actual `AsRef` or `AsMut` trait
|
||||
if is_trait_method(cx, expr, sym::AsRef) || is_trait_method(cx, expr, sym::AsMut) {
|
||||
let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if is_diag_trait_item(cx, def_id, sym::AsRef) || is_diag_trait_item(cx, def_id, sym::AsMut) {
|
||||
// check if the type after `as_ref` or `as_mut` is the same as before
|
||||
let rcv_ty = cx.typeck_results().expr_ty(recvr);
|
||||
let res_ty = cx.typeck_results().expr_ty(expr);
|
||||
@ -39,5 +72,89 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
} else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
|
||||
|| match_def_path(cx, def_id, &["core", "result", "Result", call_name])
|
||||
{
|
||||
let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
|
||||
let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();
|
||||
|
||||
if let Some(rcv_ty) = get_enum_ty(rcv_ty)
|
||||
&& let Some(res_ty) = get_enum_ty(res_ty)
|
||||
// If the only thing the `as_mut`/`as_ref` call is doing is adding references and not
|
||||
// changing the type, then we can move forward.
|
||||
&& rcv_ty.peel_refs() == res_ty.peel_refs()
|
||||
&& let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let hir::ExprKind::MethodCall(segment, _, args, _) = parent.kind
|
||||
&& segment.ident.span != expr.span
|
||||
// We check that the called method name is `map`.
|
||||
&& segment.ident.name == sym::map
|
||||
// And that it only has one argument.
|
||||
&& let [arg] = args
|
||||
&& is_calling_clone(cx, arg)
|
||||
{
|
||||
lint_as_ref_clone(cx, expr.span.with_hi(parent.span.hi()), recvr, call_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_qpath(cx: &LateContext<'_>, qpath: hir::QPath<'_>, hir_id: hir::HirId) -> bool {
|
||||
// We check it's calling the `clone` method of the `Clone` trait.
|
||||
if let Some(path_def_id) = cx.qpath_res(&qpath, hir_id).opt_def_id() {
|
||||
match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
|
||||
match arg.kind {
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
// If it's a closure, we need to check what is called.
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
let closure_expr = peel_blocks(closure_body.value);
|
||||
match closure_expr.kind {
|
||||
hir::ExprKind::MethodCall(method, obj, [], _) => {
|
||||
if method.ident.name == sym::clone
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
// We check it's the `Clone` trait.
|
||||
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
|
||||
// no autoderefs
|
||||
&& !cx.typeck_results().expr_adjustments(obj).iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Call(call, [_]) => {
|
||||
if let hir::ExprKind::Path(qpath) = call.kind {
|
||||
check_qpath(cx, qpath, call.hir_id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Path(qpath) => check_qpath(cx, qpath, arg.hir_id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
USELESS_ASREF,
|
||||
span,
|
||||
&format!("this call to `{call_name}.map(...)` does nothing"),
|
||||
"try",
|
||||
format!(
|
||||
"{}.clone()",
|
||||
snippet_with_applicability(cx, recvr.span, "..", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ pub(super) fn get_hint_if_single_char_arg(
|
||||
match ch {
|
||||
"'" => "\\'",
|
||||
r"\" => "\\\\",
|
||||
"\\\"" => "\"", // no need to escape `"` in `'"'`
|
||||
_ => ch,
|
||||
}
|
||||
);
|
||||
|
@ -331,7 +331,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>>
|
||||
slice,
|
||||
} if indexes.len() > 1 => {
|
||||
// if we have found an `assert!`, let's also check that it's actually right
|
||||
// and if it convers the highest index and if not, suggest the correct length
|
||||
// and if it covers the highest index and if not, suggest the correct length
|
||||
let sugg = match comparison {
|
||||
// `v.len() < 5` and `v.len() <= 5` does nothing in terms of bounds checks.
|
||||
// The user probably meant `v.len() > 5`
|
||||
|
@ -13,21 +13,23 @@
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for imports that do not rename the item as specified
|
||||
/// in the `enforce-import-renames` config option.
|
||||
/// in the `enforced-import-renames` config option.
|
||||
///
|
||||
/// Note: Even though this lint is warn-by-default, it will only trigger if
|
||||
/// import renames are defined in the clippy.toml file.
|
||||
/// import renames are defined in the `clippy.toml` file.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Consistency is important, if a project has defined import
|
||||
/// renames they should be followed. More practically, some item names are too
|
||||
/// vague outside of their defining scope this can enforce a more meaningful naming.
|
||||
/// Consistency is important; if a project has defined import renames, then they should be
|
||||
/// followed. More practically, some item names are too vague outside of their defining scope,
|
||||
/// in which case this can enforce a more meaningful naming.
|
||||
///
|
||||
/// ### Example
|
||||
/// An example clippy.toml configuration:
|
||||
/// ```toml
|
||||
/// # clippy.toml
|
||||
/// enforced-import-renames = [ { path = "serde_json::Value", rename = "JsonValue" }]
|
||||
/// enforced-import-renames = [
|
||||
/// { path = "serde_json::Value", rename = "JsonValue" },
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// ```rust,ignore
|
||||
|
@ -6,7 +6,7 @@
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::{self, IntTy, Ty, UintTy};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
@ -105,8 +105,28 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> {
|
||||
match ty.kind() {
|
||||
ty::Bool => Some("AtomicBool"),
|
||||
ty::Uint(_) => Some("AtomicUsize"),
|
||||
ty::Int(_) => Some("AtomicIsize"),
|
||||
ty::Uint(uint_ty) => {
|
||||
match uint_ty {
|
||||
UintTy::U8 => Some("AtomicU8"),
|
||||
UintTy::U16 => Some("AtomicU16"),
|
||||
UintTy::U32 => Some("AtomicU32"),
|
||||
UintTy::U64 => Some("AtomicU64"),
|
||||
UintTy::Usize => Some("AtomicUsize"),
|
||||
// There's no `AtomicU128`.
|
||||
UintTy::U128 => None,
|
||||
}
|
||||
},
|
||||
ty::Int(int_ty) => {
|
||||
match int_ty {
|
||||
IntTy::I8 => Some("AtomicI8"),
|
||||
IntTy::I16 => Some("AtomicI16"),
|
||||
IntTy::I32 => Some("AtomicI32"),
|
||||
IntTy::I64 => Some("AtomicI64"),
|
||||
IntTy::Isize => Some("AtomicIsize"),
|
||||
// There's no `AtomicI128`.
|
||||
IntTy::I128 => None,
|
||||
}
|
||||
},
|
||||
ty::RawPtr(_) => Some("AtomicPtr"),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ fn check_no_effect(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
|
||||
}
|
||||
|
||||
fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
// It's very hard or impossable to check whether overridden operator have side-effect this lint.
|
||||
// It's very hard or impossible to check whether overridden operator have side-effect this lint.
|
||||
// So, this function assume user-defined operator is overridden with an side-effect.
|
||||
// The definition of user-defined structure here is ADT-type,
|
||||
// Althrough this will weaken the ability of this lint, less error lint-fix happen.
|
||||
|
@ -53,14 +53,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
&& cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did())))
|
||||
&& let ExprKind::Lit(_) = param.kind
|
||||
&& param.span.eq_ctxt(expr.span)
|
||||
&& let Some(snip) = snippet_opt(cx, param.span)
|
||||
&& !(snip.starts_with("0o") || snip.starts_with("0b"))
|
||||
{
|
||||
let Some(snip) = snippet_opt(cx, param.span) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !snip.starts_with("0o") {
|
||||
show_error(cx, param);
|
||||
}
|
||||
show_error(cx, param);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(func, [param]) => {
|
||||
@ -70,7 +66,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
&& let ExprKind::Lit(_) = param.kind
|
||||
&& param.span.eq_ctxt(expr.span)
|
||||
&& let Some(snip) = snippet_opt(cx, param.span)
|
||||
&& !snip.starts_with("0o")
|
||||
&& !(snip.starts_with("0o") || snip.starts_with("0b"))
|
||||
{
|
||||
show_error(cx, param);
|
||||
}
|
||||
|
@ -18,82 +18,118 @@ pub(crate) fn check<'tcx>(
|
||||
right: &'tcx Expr<'_>,
|
||||
) {
|
||||
if !is_allowed(cx, op, left, right) {
|
||||
match op {
|
||||
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
|
||||
check_op(
|
||||
cx,
|
||||
left,
|
||||
0,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(right).0.span,
|
||||
needs_parenthesis(cx, expr, right),
|
||||
);
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
0,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(left).0.span,
|
||||
Parens::Unneeded,
|
||||
);
|
||||
},
|
||||
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
0,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(left).0.span,
|
||||
Parens::Unneeded,
|
||||
);
|
||||
},
|
||||
BinOpKind::Mul => {
|
||||
check_op(
|
||||
cx,
|
||||
left,
|
||||
1,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(right).0.span,
|
||||
needs_parenthesis(cx, expr, right),
|
||||
);
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
1,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(left).0.span,
|
||||
Parens::Unneeded,
|
||||
);
|
||||
},
|
||||
BinOpKind::Div => check_op(
|
||||
return;
|
||||
}
|
||||
|
||||
// we need to know whether a ref is coerced to a value
|
||||
// if a ref is coerced, then the suggested lint must deref it
|
||||
// e.g. `let _: i32 = x+0` with `x: &i32` should be replaced with `let _: i32 = *x`.
|
||||
// we do this by checking the _kind_ of the type of the expression
|
||||
// if it's a ref, we then check whether it is erased, and that's it.
|
||||
let (peeled_left_span, left_is_coerced_to_value) = {
|
||||
let expr = peel_hir_expr_refs(left).0;
|
||||
let span = expr.span;
|
||||
let is_coerced = expr_is_erased_ref(cx, expr);
|
||||
(span, is_coerced)
|
||||
};
|
||||
|
||||
let (peeled_right_span, right_is_coerced_to_value) = {
|
||||
let expr = peel_hir_expr_refs(right).0;
|
||||
let span = expr.span;
|
||||
let is_coerced = expr_is_erased_ref(cx, expr);
|
||||
(span, is_coerced)
|
||||
};
|
||||
|
||||
match op {
|
||||
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
|
||||
check_op(
|
||||
cx,
|
||||
left,
|
||||
0,
|
||||
expr.span,
|
||||
peeled_right_span,
|
||||
needs_parenthesis(cx, expr, right),
|
||||
right_is_coerced_to_value,
|
||||
);
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
0,
|
||||
expr.span,
|
||||
peeled_left_span,
|
||||
Parens::Unneeded,
|
||||
left_is_coerced_to_value,
|
||||
);
|
||||
},
|
||||
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
0,
|
||||
expr.span,
|
||||
peeled_left_span,
|
||||
Parens::Unneeded,
|
||||
left_is_coerced_to_value,
|
||||
);
|
||||
},
|
||||
BinOpKind::Mul => {
|
||||
check_op(
|
||||
cx,
|
||||
left,
|
||||
1,
|
||||
expr.span,
|
||||
peeled_right_span,
|
||||
needs_parenthesis(cx, expr, right),
|
||||
right_is_coerced_to_value,
|
||||
);
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
1,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(left).0.span,
|
||||
peeled_left_span,
|
||||
Parens::Unneeded,
|
||||
),
|
||||
BinOpKind::BitAnd => {
|
||||
check_op(
|
||||
cx,
|
||||
left,
|
||||
-1,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(right).0.span,
|
||||
needs_parenthesis(cx, expr, right),
|
||||
);
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
-1,
|
||||
expr.span,
|
||||
peel_hir_expr_refs(left).0.span,
|
||||
Parens::Unneeded,
|
||||
);
|
||||
},
|
||||
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
|
||||
_ => (),
|
||||
}
|
||||
left_is_coerced_to_value,
|
||||
);
|
||||
},
|
||||
BinOpKind::Div => check_op(
|
||||
cx,
|
||||
right,
|
||||
1,
|
||||
expr.span,
|
||||
peeled_left_span,
|
||||
Parens::Unneeded,
|
||||
left_is_coerced_to_value,
|
||||
),
|
||||
BinOpKind::BitAnd => {
|
||||
check_op(
|
||||
cx,
|
||||
left,
|
||||
-1,
|
||||
expr.span,
|
||||
peeled_right_span,
|
||||
needs_parenthesis(cx, expr, right),
|
||||
right_is_coerced_to_value,
|
||||
);
|
||||
check_op(
|
||||
cx,
|
||||
right,
|
||||
-1,
|
||||
expr.span,
|
||||
peeled_left_span,
|
||||
Parens::Unneeded,
|
||||
left_is_coerced_to_value,
|
||||
);
|
||||
},
|
||||
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_is_erased_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
match cx.typeck_results().expr_ty(expr).kind() {
|
||||
ty::Ref(r, ..) => r.is_erased(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,11 +180,11 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>)
|
||||
}
|
||||
|
||||
fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
// This lint applies to integers
|
||||
!cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|
||||
|| !cx.typeck_results().expr_ty(right).peel_refs().is_integral()
|
||||
// This lint applies to integers and their references
|
||||
cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|
||||
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
|
||||
// `1 << 0` is a common pattern in bit manipulation code
|
||||
|| (cmp == BinOpKind::Shl
|
||||
&& !(cmp == BinOpKind::Shl
|
||||
&& constant_simple(cx, cx.typeck_results(), right) == Some(Constant::Int(0))
|
||||
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
|
||||
}
|
||||
@ -161,11 +197,11 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
|
||||
(Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
|
||||
_ => return,
|
||||
} {
|
||||
span_ineffective_operation(cx, span, arg, Parens::Unneeded);
|
||||
span_ineffective_operation(cx, span, arg, Parens::Unneeded, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) {
|
||||
fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens, is_erased: bool) {
|
||||
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
|
||||
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
|
||||
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
|
||||
@ -178,18 +214,28 @@ fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, pa
|
||||
1 => v == 1,
|
||||
_ => unreachable!(),
|
||||
} {
|
||||
span_ineffective_operation(cx, span, arg, parens);
|
||||
span_ineffective_operation(cx, span, arg, parens, is_erased);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span, parens: Parens) {
|
||||
fn span_ineffective_operation(
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
arg: Span,
|
||||
parens: Parens,
|
||||
is_ref_coerced_to_val: bool,
|
||||
) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let expr_snippet = snippet_with_applicability(cx, arg, "..", &mut applicability);
|
||||
|
||||
let expr_snippet = if is_ref_coerced_to_val {
|
||||
format!("*{expr_snippet}")
|
||||
} else {
|
||||
expr_snippet.into_owned()
|
||||
};
|
||||
let suggestion = match parens {
|
||||
Parens::Needed => format!("({expr_snippet})"),
|
||||
Parens::Unneeded => expr_snippet.into_owned(),
|
||||
Parens::Unneeded => expr_snippet,
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
|
83
clippy_lints/src/pub_underscore_fields.rs
Normal file
83
clippy_lints/src/pub_underscore_fields.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use clippy_config::types::PubUnderscoreFieldsBehaviour;
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_path_lang_item;
|
||||
use rustc_hir::{FieldDef, Item, ItemKind, LangItem};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks whether any field of the struct is prefixed with an `_` (underscore) and also marked
|
||||
/// `pub` (public)
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Fields prefixed with an `_` are inferred as unused, which suggests it should not be marked
|
||||
/// as `pub`, because marking it as `pub` infers it will be used.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct FileHandle {
|
||||
/// pub _descriptor: usize,
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct FileHandle {
|
||||
/// _descriptor: usize,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// OR
|
||||
///
|
||||
/// ```rust
|
||||
/// struct FileHandle {
|
||||
/// pub descriptor: usize,
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub PUB_UNDERSCORE_FIELDS,
|
||||
pedantic,
|
||||
"struct field prefixed with underscore and marked public"
|
||||
}
|
||||
|
||||
pub struct PubUnderscoreFields {
|
||||
pub behavior: PubUnderscoreFieldsBehaviour,
|
||||
}
|
||||
impl_lint_pass!(PubUnderscoreFields => [PUB_UNDERSCORE_FIELDS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for PubUnderscoreFields {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
// This lint only pertains to structs.
|
||||
let ItemKind::Struct(variant_data, _) = &item.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
let is_visible = |field: &FieldDef<'_>| match self.behavior {
|
||||
PubUnderscoreFieldsBehaviour::PublicallyExported => cx.effective_visibilities.is_reachable(field.def_id),
|
||||
PubUnderscoreFieldsBehaviour::AllPubFields => {
|
||||
// If there is a visibility span then the field is marked pub in some way.
|
||||
!field.vis_span.is_empty()
|
||||
},
|
||||
};
|
||||
|
||||
for field in variant_data.fields() {
|
||||
// Only pertains to fields that start with an underscore, and are public.
|
||||
if field.ident.as_str().starts_with('_') && is_visible(field)
|
||||
// We ignore fields that have `#[doc(hidden)]`.
|
||||
&& !is_doc_hidden(cx.tcx.hir().attrs(field.hir_id))
|
||||
// We ignore fields that are `PhantomData`.
|
||||
&& !is_path_lang_item(cx, field.ty, LangItem::PhantomData)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
PUB_UNDERSCORE_FIELDS,
|
||||
field.vis_span.to(field.ident.span),
|
||||
"field marked as public but also inferred as unused because it's prefixed with `_`",
|
||||
None,
|
||||
"consider removing the underscore, or making the field private",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -75,12 +75,7 @@ enum IfBlockType<'hir> {
|
||||
/// An `if x.is_xxx() { a } else { b } ` expression.
|
||||
///
|
||||
/// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b)
|
||||
IfIs(
|
||||
&'hir Expr<'hir>,
|
||||
Ty<'hir>,
|
||||
Symbol,
|
||||
&'hir Expr<'hir>,
|
||||
),
|
||||
IfIs(&'hir Expr<'hir>, Ty<'hir>, Symbol, &'hir Expr<'hir>),
|
||||
/// An `if let Xxx(a) = b { c } else { d }` expression.
|
||||
///
|
||||
/// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for mis-uses of the serde API.
|
||||
/// Checks for misuses of the serde API.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Serde is very finnicky about how its API should be
|
||||
|
102
clippy_lints/src/thread_local_initializer_can_be_made_const.rs
Normal file
102
clippy_lints/src/thread_local_initializer_can_be_made_const.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use clippy_config::msrvs::Msrv;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::fn_has_unsatisfiable_preds;
|
||||
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{intravisit, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::sym::thread_local_macro;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Suggests to use `const` in `thread_local!` macro if possible.
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// The `thread_local!` macro wraps static declarations and makes them thread-local.
|
||||
/// It supports using a `const` keyword that may be used for declarations that can
|
||||
/// be evaluated as a constant expression. This can enable a more efficient thread
|
||||
/// local implementation that can avoid lazy initialization. For types that do not
|
||||
/// need to be dropped, this can enable an even more efficient implementation that
|
||||
/// does not need to track any additional state.
|
||||
///
|
||||
/// https://doc.rust-lang.org/std/macro.thread_local.html
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // example code where clippy issues a warning
|
||||
/// thread_local! {
|
||||
/// static BUF: String = String::new();
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// // example code which does not raise clippy warning
|
||||
/// thread_local! {
|
||||
/// static BUF: String = const { String::new() };
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
|
||||
perf,
|
||||
"suggest using `const` in `thread_local!` macro"
|
||||
}
|
||||
|
||||
pub struct ThreadLocalInitializerCanBeMadeConst {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl ThreadLocalInitializerCanBeMadeConst {
|
||||
#[must_use]
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self { msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
fn_kind: rustc_hir::intravisit::FnKind<'tcx>,
|
||||
_: &'tcx rustc_hir::FnDecl<'tcx>,
|
||||
body: &'tcx rustc_hir::Body<'tcx>,
|
||||
span: rustc_span::Span,
|
||||
defid: rustc_span::def_id::LocalDefId,
|
||||
) {
|
||||
if in_external_macro(cx.sess(), span)
|
||||
&& let Some(callee) = span.source_callee()
|
||||
&& let Some(macro_def_id) = callee.macro_def_id
|
||||
&& cx.tcx.is_diagnostic_item(thread_local_macro, macro_def_id)
|
||||
&& let intravisit::FnKind::ItemFn(..) = fn_kind
|
||||
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
|
||||
&& !fn_has_unsatisfiable_preds(cx, defid.to_def_id())
|
||||
&& let mir = cx.tcx.optimized_mir(defid.to_def_id())
|
||||
&& let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv)
|
||||
// this is the `__init` function emitted by the `thread_local!` macro
|
||||
// when the `const` keyword is not used. We avoid checking the `__init` directly
|
||||
// as that is not a public API.
|
||||
// we know that the function is const-qualifiable, so now we need only to get the
|
||||
// initializer expression to span-lint it.
|
||||
&& let ExprKind::Block(block, _) = body.value.kind
|
||||
&& let Some(ret_expr) = block.expr
|
||||
&& let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }")
|
||||
&& initializer_snippet != "thread_local! { ... }"
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
|
||||
ret_expr.span,
|
||||
"initializer for `thread_local` value can be made `const`",
|
||||
"replace with",
|
||||
format!("const {{ {initializer_snippet} }}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::ty::is_normalizable;
|
||||
use clippy_utils::{path_to_local, path_to_local_id};
|
||||
use clippy_utils::{eq_expr_value, path_to_local};
|
||||
use rustc_abi::WrappingRange;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
@ -25,6 +25,52 @@ fn range_fully_contained(from: WrappingRange, to: WrappingRange) -> bool {
|
||||
to.contains(from.start) && to.contains(from.end)
|
||||
}
|
||||
|
||||
/// Checks if a given expression is a binary operation involving a local variable or is made up of
|
||||
/// other (nested) binary expressions involving the local. There must be at least one local
|
||||
/// reference that is the same as `local_expr`.
|
||||
///
|
||||
/// This is used as a heuristic to detect if a variable
|
||||
/// is checked to be within the valid range of a transmuted type.
|
||||
/// All of these would return true:
|
||||
/// * `x < 4`
|
||||
/// * `x < 4 && x > 1`
|
||||
/// * `x.field < 4 && x.field > 1` (given `x.field`)
|
||||
/// * `x.field < 4 && unrelated()`
|
||||
/// * `(1..=3).contains(&x)`
|
||||
fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_>) -> bool {
|
||||
match expr.kind {
|
||||
ExprKind::Binary(_, lhs, rhs) => {
|
||||
binops_with_local(cx, local_expr, lhs) || binops_with_local(cx, local_expr, rhs)
|
||||
},
|
||||
ExprKind::MethodCall(path, receiver, [arg], _)
|
||||
if path.ident.name == sym!(contains)
|
||||
// ... `contains` called on some kind of range
|
||||
&& let Some(receiver_adt) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def()
|
||||
&& let lang_items = cx.tcx.lang_items()
|
||||
&& [
|
||||
lang_items.range_from_struct(),
|
||||
lang_items.range_inclusive_struct(),
|
||||
lang_items.range_struct(),
|
||||
lang_items.range_to_inclusive_struct(),
|
||||
lang_items.range_to_struct()
|
||||
].into_iter().any(|did| did == Some(receiver_adt.did())) =>
|
||||
{
|
||||
eq_expr_value(cx, local_expr, arg.peel_borrows())
|
||||
},
|
||||
_ => eq_expr_value(cx, local_expr, expr),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if an expression is a path to a local variable (with optional projections), e.g.
|
||||
/// `x.field[0].field2` would return true.
|
||||
fn is_local_with_projections(expr: &Expr<'_>) -> bool {
|
||||
match expr.kind {
|
||||
ExprKind::Path(_) => path_to_local(expr).is_some(),
|
||||
ExprKind::Field(expr, _) | ExprKind::Index(expr, ..) => is_local_with_projections(expr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
@ -36,9 +82,8 @@ pub(super) fn check<'tcx>(
|
||||
&& let ExprKind::MethodCall(path, receiver, [arg], _) = then_some_call.kind
|
||||
&& cx.typeck_results().expr_ty(receiver).is_bool()
|
||||
&& path.ident.name == sym!(then_some)
|
||||
&& let ExprKind::Binary(_, lhs, rhs) = receiver.kind
|
||||
&& let Some(local_id) = path_to_local(transmutable)
|
||||
&& (path_to_local_id(lhs, local_id) || path_to_local_id(rhs, local_id))
|
||||
&& is_local_with_projections(transmutable)
|
||||
&& binops_with_local(cx, transmutable, receiver)
|
||||
&& is_normalizable(cx, cx.param_env, from_ty)
|
||||
&& is_normalizable(cx, cx.param_env, to_ty)
|
||||
// we only want to lint if the target type has a niche that is larger than the one of the source type
|
||||
|
@ -37,9 +37,7 @@ pub(super) fn check<'tcx>(
|
||||
to_ty = to_sub_ty;
|
||||
continue;
|
||||
},
|
||||
(ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty)))
|
||||
if reduced_tys.from_fat_ptr =>
|
||||
{
|
||||
(ReducedTy::Other(from_sub_ty), ReducedTy::OrderedFields(Some(to_sub_ty))) if reduced_tys.from_fat_ptr => {
|
||||
from_ty = from_sub_ty;
|
||||
to_ty = to_sub_ty;
|
||||
continue;
|
||||
|
@ -1,14 +1,21 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{get_trait_def_id, path_res};
|
||||
use clippy_utils::{expr_or_init, get_trait_def_id, path_def_id};
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node};
|
||||
use rustc_hir::intravisit::{walk_body, walk_expr, FnKind, Visitor};
|
||||
use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, Node, QPath, TyKind};
|
||||
use rustc_hir_analysis::hir_ty_to_ty;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::{kw, Ident};
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -41,7 +48,26 @@
|
||||
"detect unconditional recursion in some traits implementation"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]);
|
||||
#[derive(Default)]
|
||||
pub struct UnconditionalRecursion {
|
||||
/// The key is the `DefId` of the type implementing the `Default` trait and the value is the
|
||||
/// `DefId` of the return call.
|
||||
default_impl_for_type: FxHashMap<DefId, DefId>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(UnconditionalRecursion => [UNCONDITIONAL_RECURSION]);
|
||||
|
||||
fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNCONDITIONAL_RECURSION,
|
||||
method_span,
|
||||
"function cannot return without recursing",
|
||||
|diag| {
|
||||
diag.span_note(expr.span, "recursive call site");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
|
||||
match ty.peel_refs().kind() {
|
||||
@ -51,84 +77,329 @@ fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
matches!(path_res(cx, expr), Res::Local(_))
|
||||
fn get_hir_ty_def_id(tcx: TyCtxt<'_>, hir_ty: rustc_hir::Ty<'_>) -> Option<DefId> {
|
||||
let TyKind::Path(qpath) = hir_ty.kind else { return None };
|
||||
match qpath {
|
||||
QPath::Resolved(_, path) => path.res.opt_def_id(),
|
||||
QPath::TypeRelative(_, _) => {
|
||||
let ty = hir_ty_to_ty(tcx, &hir_ty);
|
||||
|
||||
match ty.kind() {
|
||||
ty::Alias(ty::Projection, proj) => {
|
||||
Res::<HirId>::Def(DefKind::Trait, proj.trait_ref(tcx).def_id).opt_def_id()
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
},
|
||||
QPath::LangItem(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_return_calls_in_body<'tcx>(body: &'tcx Body<'tcx>) -> Vec<&'tcx Expr<'tcx>> {
|
||||
let mut visitor = ReturnsVisitor::default();
|
||||
|
||||
visitor.visit_body(body);
|
||||
visitor.returns
|
||||
}
|
||||
|
||||
fn has_conditional_return(body: &Body<'_>, expr: &Expr<'_>) -> bool {
|
||||
match get_return_calls_in_body(body).as_slice() {
|
||||
[] => false,
|
||||
[return_expr] => return_expr.hir_id != expr.hir_id,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Option<DefId> {
|
||||
let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id);
|
||||
if let Some((
|
||||
_,
|
||||
Node::Item(Item {
|
||||
kind: ItemKind::Impl(impl_),
|
||||
owner_id,
|
||||
..
|
||||
}),
|
||||
)) = cx.tcx.hir().parent_iter(hir_id).next()
|
||||
// We exclude `impl` blocks generated from rustc's proc macros.
|
||||
&& !cx.tcx.has_attr(*owner_id, sym::automatically_derived)
|
||||
// It is a implementation of a trait.
|
||||
&& let Some(trait_) = impl_.of_trait
|
||||
{
|
||||
trait_.trait_def_id()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
|
||||
let args = cx
|
||||
.tcx
|
||||
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
|
||||
.inputs();
|
||||
// That has two arguments.
|
||||
if let [self_arg, other_arg] = args
|
||||
&& let Some(self_arg) = get_ty_def_id(*self_arg)
|
||||
&& let Some(other_arg) = get_ty_def_id(*other_arg)
|
||||
// The two arguments are of the same type.
|
||||
&& self_arg == other_arg
|
||||
&& let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id)
|
||||
// The trait is `PartialEq`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
|
||||
{
|
||||
let to_check_op = if name.name == sym::eq {
|
||||
BinOpKind::Eq
|
||||
} else {
|
||||
BinOpKind::Ne
|
||||
};
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::Binary(op, left, right) if op.node == to_check_op => {
|
||||
// Then we check if the left-hand element is of the same type as `self`.
|
||||
if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left)
|
||||
&& let Some(left_id) = get_ty_def_id(left_ty)
|
||||
&& self_arg == left_id
|
||||
&& let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
|
||||
&& let Some(right_id) = get_ty_def_id(right_ty)
|
||||
&& other_arg == right_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& trait_id == trait_def_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if is_bad {
|
||||
span_error(cx, method_span, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
|
||||
let args = cx
|
||||
.tcx
|
||||
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
|
||||
.inputs();
|
||||
// That has one argument.
|
||||
if let [_self_arg] = args
|
||||
&& let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id)
|
||||
&& let Some((
|
||||
_,
|
||||
Node::Item(Item {
|
||||
kind: ItemKind::Impl(impl_),
|
||||
owner_id,
|
||||
..
|
||||
}),
|
||||
)) = cx.tcx.hir().parent_iter(hir_id).next()
|
||||
// We exclude `impl` blocks generated from rustc's proc macros.
|
||||
&& !cx.tcx.has_attr(*owner_id, sym::automatically_derived)
|
||||
// It is a implementation of a trait.
|
||||
&& let Some(trait_) = impl_.of_trait
|
||||
&& let Some(trait_def_id) = trait_.trait_def_id()
|
||||
// The trait is `ToString`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"])
|
||||
{
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& trait_id == trait_def_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if is_bad {
|
||||
span_error(cx, method_span, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_method_on_current_ty(tcx: TyCtxt<'_>, qpath: QPath<'_>, implemented_ty_id: DefId) -> bool {
|
||||
match qpath {
|
||||
QPath::Resolved(_, path) => match path.segments {
|
||||
[first, .., last] => last.ident.name == kw::Default && first.res.opt_def_id() == Some(implemented_ty_id),
|
||||
_ => false,
|
||||
},
|
||||
QPath::TypeRelative(ty, segment) => {
|
||||
if segment.ident.name != kw::Default {
|
||||
return false;
|
||||
}
|
||||
if matches!(
|
||||
ty.kind,
|
||||
TyKind::Path(QPath::Resolved(
|
||||
_,
|
||||
hir::Path {
|
||||
res: Res::SelfTyAlias { .. },
|
||||
..
|
||||
},
|
||||
))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
get_hir_ty_def_id(tcx, *ty) == Some(implemented_ty_id)
|
||||
},
|
||||
QPath::LangItem(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckCalls<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
map: Map<'tcx>,
|
||||
implemented_ty_id: DefId,
|
||||
found_default_call: bool,
|
||||
method_span: Span,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for CheckCalls<'a, 'tcx>
|
||||
where
|
||||
'tcx: 'a,
|
||||
{
|
||||
type NestedFilter = nested_filter::OnlyBodies;
|
||||
|
||||
fn nested_visit_map(&mut self) -> Self::Map {
|
||||
self.map
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
if self.found_default_call {
|
||||
return;
|
||||
}
|
||||
walk_expr(self, expr);
|
||||
|
||||
if let ExprKind::Call(f, _) = expr.kind
|
||||
&& let ExprKind::Path(qpath) = f.kind
|
||||
&& is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id)
|
||||
&& let Some(method_def_id) = path_def_id(self.cx, f)
|
||||
&& let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id)
|
||||
&& Some(trait_def_id) == get_trait_def_id(self.cx, &["core", "default", "Default"])
|
||||
{
|
||||
self.found_default_call = true;
|
||||
span_error(self.cx, self.method_span, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnconditionalRecursion {
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn init_default_impl_for_type_if_needed(&mut self, cx: &LateContext<'_>) {
|
||||
if self.default_impl_for_type.is_empty()
|
||||
&& let Some(default_trait_id) = get_trait_def_id(cx, &["core", "default", "Default"])
|
||||
{
|
||||
let impls = cx.tcx.trait_impls_of(default_trait_id);
|
||||
for (ty, impl_def_ids) in impls.non_blanket_impls() {
|
||||
let Some(self_def_id) = ty.def() else { continue };
|
||||
for impl_def_id in impl_def_ids {
|
||||
if !cx.tcx.has_attr(*impl_def_id, sym::automatically_derived) &&
|
||||
let Some(assoc_item) = cx
|
||||
.tcx
|
||||
.associated_items(impl_def_id)
|
||||
.in_definition_order()
|
||||
// We're not interested in foreign implementations of the `Default` trait.
|
||||
.find(|item| {
|
||||
item.kind == AssocKind::Fn && item.def_id.is_local() && item.name == kw::Default
|
||||
})
|
||||
&& let Some(body_node) = cx.tcx.hir().get_if_local(assoc_item.def_id)
|
||||
&& let Some(body_id) = body_node.body_id()
|
||||
&& let body = cx.tcx.hir().body(body_id)
|
||||
// We don't want to keep it if it has conditional return.
|
||||
&& let [return_expr] = get_return_calls_in_body(body).as_slice()
|
||||
&& let ExprKind::Call(call_expr, _) = return_expr.kind
|
||||
// We need to use typeck here to infer the actual function being called.
|
||||
&& let body_def_id = cx.tcx.hir().enclosing_body_owner(call_expr.hir_id)
|
||||
&& let Some(body_owner) = cx.tcx.hir().maybe_body_owned_by(body_def_id)
|
||||
&& let typeck = cx.tcx.typeck_body(body_owner)
|
||||
&& let Some(call_def_id) = typeck.type_dependent_def_id(call_expr.hir_id)
|
||||
{
|
||||
self.default_impl_for_type.insert(self_def_id, call_def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_default_new<'tcx>(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
decl: &FnDecl<'tcx>,
|
||||
body: &'tcx Body<'tcx>,
|
||||
method_span: Span,
|
||||
method_def_id: LocalDefId,
|
||||
) {
|
||||
// We're only interested into static methods.
|
||||
if decl.implicit_self.has_implicit_self() {
|
||||
return;
|
||||
}
|
||||
// We don't check trait implementations.
|
||||
if get_impl_trait_def_id(cx, method_def_id).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let hir_id = cx.tcx.local_def_id_to_hir_id(method_def_id);
|
||||
if let Some((
|
||||
_,
|
||||
Node::Item(Item {
|
||||
kind: ItemKind::Impl(impl_),
|
||||
..
|
||||
}),
|
||||
)) = cx.tcx.hir().parent_iter(hir_id).next()
|
||||
&& let Some(implemented_ty_id) = get_hir_ty_def_id(cx.tcx, *impl_.self_ty)
|
||||
&& {
|
||||
self.init_default_impl_for_type_if_needed(cx);
|
||||
true
|
||||
}
|
||||
&& let Some(return_def_id) = self.default_impl_for_type.get(&implemented_ty_id)
|
||||
&& method_def_id.to_def_id() == *return_def_id
|
||||
{
|
||||
let mut c = CheckCalls {
|
||||
cx,
|
||||
map: cx.tcx.hir(),
|
||||
implemented_ty_id,
|
||||
found_default_call: false,
|
||||
method_span,
|
||||
};
|
||||
walk_body(&mut c, body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: FnKind<'tcx>,
|
||||
_decl: &'tcx FnDecl<'tcx>,
|
||||
decl: &'tcx FnDecl<'tcx>,
|
||||
body: &'tcx Body<'tcx>,
|
||||
method_span: Span,
|
||||
def_id: LocalDefId,
|
||||
method_def_id: LocalDefId,
|
||||
) {
|
||||
// If the function is a method...
|
||||
if let FnKind::Method(name, _) = kind
|
||||
// That has two arguments.
|
||||
&& let [self_arg, other_arg] = cx
|
||||
.tcx
|
||||
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(def_id).skip_binder())
|
||||
.inputs()
|
||||
&& let Some(self_arg) = get_ty_def_id(*self_arg)
|
||||
&& let Some(other_arg) = get_ty_def_id(*other_arg)
|
||||
// The two arguments are of the same type.
|
||||
&& self_arg == other_arg
|
||||
&& let hir_id = cx.tcx.local_def_id_to_hir_id(def_id)
|
||||
&& let Some((
|
||||
_,
|
||||
Node::Item(Item {
|
||||
kind: ItemKind::Impl(impl_),
|
||||
owner_id,
|
||||
..
|
||||
}),
|
||||
)) = cx.tcx.hir().parent_iter(hir_id).next()
|
||||
// We exclude `impl` blocks generated from rustc's proc macros.
|
||||
&& !cx.tcx.has_attr(*owner_id, sym::automatically_derived)
|
||||
// It is a implementation of a trait.
|
||||
&& let Some(trait_) = impl_.of_trait
|
||||
&& let Some(trait_def_id) = trait_.trait_def_id()
|
||||
// The trait is `PartialEq`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
|
||||
&& let expr = expr_or_init(cx, body.value).peel_blocks()
|
||||
// Doesn't have a conditional return.
|
||||
&& !has_conditional_return(body, expr)
|
||||
{
|
||||
let to_check_op = if name.name == sym::eq {
|
||||
BinOpKind::Eq
|
||||
} else {
|
||||
BinOpKind::Ne
|
||||
};
|
||||
let expr = body.value.peel_blocks();
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::Binary(op, left, right) if op.node == to_check_op => {
|
||||
is_local(cx, left) && is_local(cx, right)
|
||||
},
|
||||
ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => {
|
||||
if is_local(cx, receiver)
|
||||
&& is_local(cx, &arg)
|
||||
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& trait_id == trait_def_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
if is_bad {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNCONDITIONAL_RECURSION,
|
||||
method_span,
|
||||
"function cannot return without recursing",
|
||||
|diag| {
|
||||
diag.span_note(expr.span, "recursive call site");
|
||||
},
|
||||
);
|
||||
if name.name == sym::eq || name.name == sym::ne {
|
||||
check_partial_eq(cx, method_span, method_def_id, name, expr);
|
||||
} else if name.name == sym::to_string {
|
||||
check_to_string(cx, method_span, method_def_id, name, expr);
|
||||
}
|
||||
self.check_default_new(cx, decl, body, method_span, method_def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -681,11 +681,19 @@ fn text_has_safety_comment(src: &str, line_starts: &[RelativeBytePos], start_pos
|
||||
.filter(|(_, text)| !text.is_empty());
|
||||
|
||||
let (line_start, line) = lines.next()?;
|
||||
let mut in_codeblock = false;
|
||||
// Check for a sequence of line comments.
|
||||
if line.starts_with("//") {
|
||||
let (mut line, mut line_start) = (line, line_start);
|
||||
loop {
|
||||
if line.to_ascii_uppercase().contains("SAFETY:") {
|
||||
// Don't lint if the safety comment is part of a codeblock in a doc comment.
|
||||
// It may or may not be required, and we can't very easily check it (and we shouldn't, since
|
||||
// the safety comment isn't referring to the node we're currently checking)
|
||||
if line.trim_start_matches("///").trim_start().starts_with("```") {
|
||||
in_codeblock = !in_codeblock;
|
||||
}
|
||||
|
||||
if line.to_ascii_uppercase().contains("SAFETY:") && !in_codeblock {
|
||||
return Some(start_pos + BytePos(u32::try_from(line_start).unwrap()));
|
||||
}
|
||||
match lines.next() {
|
||||
|
@ -13,12 +13,31 @@
|
||||
use super::LET_UNIT_VALUE;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
|
||||
// skip `let () = { ... }`
|
||||
if let PatKind::Tuple(fields, ..) = local.pat.kind
|
||||
&& fields.is_empty()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(init) = local.init
|
||||
&& !local.pat.span.from_expansion()
|
||||
&& !in_external_macro(cx.sess(), local.span)
|
||||
&& !is_from_async_await(local.span)
|
||||
&& cx.typeck_results().pat_ty(local.pat).is_unit()
|
||||
{
|
||||
// skip `let awa = ()`
|
||||
if let ExprKind::Tup([]) = init.kind {
|
||||
return;
|
||||
}
|
||||
|
||||
// skip `let _: () = { ... }`
|
||||
if let Some(ty) = local.ty
|
||||
&& let TyKind::Tup([]) = ty.kind
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
|
||||
|| matches!(local.pat.kind, PatKind::Tuple([], ddpos) if ddpos.as_opt_usize().is_none()))
|
||||
&& expr_needs_inferred_result(cx, init)
|
||||
@ -34,7 +53,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
local.pat.span,
|
||||
"use a wild (`_`) binding",
|
||||
"use a wildcard binding",
|
||||
"_",
|
||||
Applicability::MaybeIncorrect, // snippet
|
||||
);
|
||||
|
@ -11,8 +11,8 @@
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Node, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{sym, DesugaringKind, Span};
|
||||
|
||||
@ -79,7 +79,6 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// this is to avoid compile errors when doing the suggestion here: let _: Vec<_> = vec![..];
|
||||
&& local.ty.is_none()
|
||||
&& let PatKind::Binding(_, id, ..) = local.pat.kind
|
||||
&& is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(expr.peel_borrows())))
|
||||
{
|
||||
let only_slice_uses = for_each_local_use_after_expr(cx, id, expr.hir_id, |expr| {
|
||||
// allow indexing into a vec and some set of allowed method calls that exist on slices, too
|
||||
@ -185,6 +184,11 @@ fn check_vec_macro<'tcx>(
|
||||
let snippet = match *vec_args {
|
||||
higher::VecArgs::Repeat(elem, len) => {
|
||||
if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) {
|
||||
// vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also
|
||||
if !is_copy(cx, cx.typeck_results().expr_ty(elem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
|
||||
return;
|
||||
@ -241,12 +245,3 @@ fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
|
||||
let ty = cx.typeck_results().expr_ty_adjusted(expr);
|
||||
cx.layout_of(ty).map_or(0, |l| l.size.bytes())
|
||||
}
|
||||
|
||||
/// Returns the item type of the vector (i.e., the `T` in `Vec<T>`).
|
||||
fn vec_type(ty: Ty<'_>) -> Ty<'_> {
|
||||
if let ty::Adt(_, args) = ty.kind() {
|
||||
args.type_at(0)
|
||||
} else {
|
||||
panic!("The type of `vec!` is a not a struct?");
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
} else {
|
||||
// In this case, the `use_path.span` ends right before the `::*`, so we need to
|
||||
// extend it up to the `*`. Since it is hard to find the `*` in weird
|
||||
// formattings like `use _ :: *;`, we extend it up to, but not including the
|
||||
// formatting like `use _ :: *;`, we extend it up to, but not including the
|
||||
// `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
|
||||
// can just use the end of the item span
|
||||
let mut span = use_path.span.with_hi(item.span.hi());
|
||||
|
@ -99,7 +99,10 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg:
|
||||
}
|
||||
|
||||
fn res_has_significant_drop(res: Res, cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
if let Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) = res {
|
||||
if let Res::Def(DefKind::Ctor(..) | DefKind::Variant | DefKind::Enum | DefKind::Struct, _)
|
||||
| Res::SelfCtor(_)
|
||||
| Res::SelfTyAlias { .. } = res
|
||||
{
|
||||
cx.typeck_results()
|
||||
.expr_ty(e)
|
||||
.has_significant_drop(cx.tcx, cx.param_env)
|
||||
@ -173,6 +176,13 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
self.eagerness |= NoChange;
|
||||
return;
|
||||
},
|
||||
#[expect(clippy::match_same_arms)] // arm pattern can't be merged due to `ref`, see rust#105778
|
||||
ExprKind::Struct(path, ..) => {
|
||||
if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) {
|
||||
self.eagerness = ForceNoChange;
|
||||
return;
|
||||
}
|
||||
},
|
||||
ExprKind::Path(ref path) => {
|
||||
if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) {
|
||||
self.eagerness = ForceNoChange;
|
||||
@ -291,7 +301,6 @@ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
| ExprKind::Closure { .. }
|
||||
| ExprKind::Field(..)
|
||||
| ExprKind::AddrOf(..)
|
||||
| ExprKind::Struct(..)
|
||||
| ExprKind::Repeat(..)
|
||||
| ExprKind::Block(Block { stmts: [], .. }, _)
|
||||
| ExprKind::OffsetOf(..) => (),
|
||||
|
@ -865,7 +865,7 @@ pub fn hash_expr(&mut self, e: &Expr<'_>) {
|
||||
|
||||
for arm in arms {
|
||||
self.hash_pat(arm.pat);
|
||||
if let Some(ref e) = arm.guard {
|
||||
if let Some(e) = arm.guard {
|
||||
self.hash_expr(e);
|
||||
}
|
||||
self.hash_expr(arm.body);
|
||||
|
@ -420,7 +420,7 @@ pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId)
|
||||
ast_format_args
|
||||
.get()?
|
||||
.get(&format_args_expr.span.with_parent(None))
|
||||
.map(Rc::clone)
|
||||
.cloned()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
||||
|
||||
ExprKind::Call(callee, args) => {
|
||||
let lhs = expr_type_certainty(cx, callee);
|
||||
let rhs = if type_is_inferrable_from_arguments(cx, expr) {
|
||||
let rhs = if type_is_inferable_from_arguments(cx, expr) {
|
||||
meet(args.iter().map(|arg| expr_type_certainty(cx, arg)))
|
||||
} else {
|
||||
Certainty::Uncertain
|
||||
@ -57,7 +57,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
|
||||
receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id);
|
||||
};
|
||||
let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
|
||||
let rhs = if type_is_inferrable_from_arguments(cx, expr) {
|
||||
let rhs = if type_is_inferable_from_arguments(cx, expr) {
|
||||
meet(
|
||||
std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))),
|
||||
)
|
||||
@ -279,7 +279,7 @@ fn update_res(cx: &LateContext<'_>, parent_certainty: Certainty, path_segment: &
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn type_is_inferrable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
fn type_is_inferable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let Some(callee_def_id) = (match expr.kind {
|
||||
ExprKind::Call(callee, _) => {
|
||||
let callee_ty = cx.typeck_results().expr_ty(callee);
|
||||
|
@ -1,3 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2023-12-28"
|
||||
channel = "nightly-2024-01-11"
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
|
@ -51,7 +51,7 @@ The changelog for `rustc_tools_util` is available under:
|
||||
|
||||
<!-- REUSE-IgnoreStart -->
|
||||
|
||||
Copyright 2014-2022 The Rust Project Developers
|
||||
Copyright 2014-2024 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
|
@ -0,0 +1 @@
|
||||
pub-underscore-fields-behavior = "AllPubFields"
|
1
tests/ui-toml/pub_underscore_fields/exported/clippy.toml
Normal file
1
tests/ui-toml/pub_underscore_fields/exported/clippy.toml
Normal file
@ -0,0 +1 @@
|
||||
pub-underscore-fields-behavior = "PublicallyExported"
|
@ -0,0 +1,60 @@
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:15:9
|
||||
|
|
||||
LL | pub _b: u8,
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
= note: `-D clippy::pub-underscore-fields` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::pub_underscore_fields)]`
|
||||
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:23:13
|
||||
|
|
||||
LL | pub(in crate::inner) _f: Option<()>,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:27:13
|
||||
|
|
||||
LL | pub _g: String,
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:34:9
|
||||
|
|
||||
LL | pub _a: usize,
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:41:9
|
||||
|
|
||||
LL | pub _c: i64,
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:44:9
|
||||
|
|
||||
LL | pub _e: Option<u8>,
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:57:9
|
||||
|
|
||||
LL | pub(crate) _b: Option<String>,
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
|
@ -0,0 +1,12 @@
|
||||
error: field marked as public but also inferred as unused because it's prefixed with `_`
|
||||
--> $DIR/pub_underscore_fields.rs:15:9
|
||||
|
|
||||
LL | pub _b: u8,
|
||||
| ^^^^^^
|
||||
|
|
||||
= help: consider removing the underscore, or making the field private
|
||||
= note: `-D clippy::pub-underscore-fields` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::pub_underscore_fields)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
66
tests/ui-toml/pub_underscore_fields/pub_underscore_fields.rs
Normal file
66
tests/ui-toml/pub_underscore_fields/pub_underscore_fields.rs
Normal file
@ -0,0 +1,66 @@
|
||||
//@revisions: exported all_pub_fields
|
||||
//@[all_pub_fields] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/pub_underscore_fields/all_pub_fields
|
||||
//@[exported] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/pub_underscore_fields/exported
|
||||
|
||||
#![allow(unused)]
|
||||
#![warn(clippy::pub_underscore_fields)]
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub mod inner {
|
||||
use std::marker;
|
||||
|
||||
pub struct PubSuper {
|
||||
pub(super) a: usize,
|
||||
pub _b: u8,
|
||||
_c: i32,
|
||||
pub _mark: marker::PhantomData<u8>,
|
||||
}
|
||||
|
||||
mod inner2 {
|
||||
pub struct PubModVisibility {
|
||||
pub(in crate::inner) e: bool,
|
||||
pub(in crate::inner) _f: Option<()>,
|
||||
}
|
||||
|
||||
struct PrivateStructPubField {
|
||||
pub _g: String,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub struct StructWithOneViolation {
|
||||
pub _a: usize,
|
||||
}
|
||||
|
||||
// should handle structs with multiple violations
|
||||
pub struct StructWithMultipleViolations {
|
||||
a: u8,
|
||||
_b: usize,
|
||||
pub _c: i64,
|
||||
#[doc(hidden)]
|
||||
pub d: String,
|
||||
pub _e: Option<u8>,
|
||||
}
|
||||
|
||||
// shouldn't warn on anonymous fields
|
||||
pub struct AnonymousFields(pub usize, i32);
|
||||
|
||||
// don't warn on empty structs
|
||||
pub struct Empty1;
|
||||
pub struct Empty2();
|
||||
pub struct Empty3 {};
|
||||
|
||||
pub struct PubCrate {
|
||||
pub(crate) a: String,
|
||||
pub(crate) _b: Option<String>,
|
||||
}
|
||||
|
||||
// shouldn't warn on fields named pub
|
||||
pub struct NamedPub {
|
||||
r#pub: bool,
|
||||
_pub: String,
|
||||
pub(crate) _mark: PhantomData<u8>,
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
|
||||
missing-docs-in-crate-items
|
||||
msrv
|
||||
pass-by-value-size-limit
|
||||
pub-underscore-fields-behavior
|
||||
semicolon-inside-block-ignore-singleline
|
||||
semicolon-outside-block-ignore-multiline
|
||||
single-char-binding-names-threshold
|
||||
@ -124,6 +125,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
|
||||
missing-docs-in-crate-items
|
||||
msrv
|
||||
pass-by-value-size-limit
|
||||
pub-underscore-fields-behavior
|
||||
semicolon-inside-block-ignore-singleline
|
||||
semicolon-outside-block-ignore-multiline
|
||||
single-char-binding-names-threshold
|
||||
|
@ -17,7 +17,7 @@ fn main() {
|
||||
with_span!(
|
||||
span
|
||||
|
||||
fn coverting() {
|
||||
fn converting() {
|
||||
let x = 0u32 as u64;
|
||||
}
|
||||
);
|
||||
|
@ -365,3 +365,52 @@ fn avoid_subtract_overflow(q: u32) {
|
||||
fn issue11426() {
|
||||
(&42u8 >> 0xa9008fb6c9d81e42_0e25730562a601c8_u128) as usize;
|
||||
}
|
||||
|
||||
fn issue11642() {
|
||||
fn square(x: i16) -> u32 {
|
||||
let x = x as i32;
|
||||
(x * x) as u32;
|
||||
x.pow(2) as u32;
|
||||
(-2_i32).pow(2) as u32
|
||||
}
|
||||
|
||||
let _a = |x: i32| -> u32 { (x * x * x * x) as u32 };
|
||||
|
||||
(-2_i32).pow(3) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
|
||||
let x: i32 = 10;
|
||||
(x * x) as u32;
|
||||
(x * x * x) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
|
||||
let y: i16 = -2;
|
||||
(y * y * y * y * -2) as u16;
|
||||
//~^ ERROR: casting `i16` to `u16` may lose the sign of the value
|
||||
(y * y * y * y * 2) as u16;
|
||||
(y * y * y * 2) as u16;
|
||||
//~^ ERROR: casting `i16` to `u16` may lose the sign of the value
|
||||
(y * y * y * -2) as u16;
|
||||
//~^ ERROR: casting `i16` to `u16` may lose the sign of the value
|
||||
|
||||
fn foo(a: i32, b: i32, c: i32) -> u32 {
|
||||
(a * a * b * b * c * c) as u32;
|
||||
(a * b * c) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
(a * -b * c) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
(a * b * c * c) as u32;
|
||||
(a * -2) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
(a * b * c * -2) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
(a / b) as u32;
|
||||
(a / b * c) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
(a / b + b * c) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
a.pow(3) as u32;
|
||||
//~^ ERROR: casting `i32` to `u32` may lose the sign of the value
|
||||
(a.abs() * b.pow(2) / c.abs()) as u32
|
||||
}
|
||||
}
|
||||
|
@ -444,5 +444,77 @@ help: ... or use `try_from` and handle the error accordingly
|
||||
LL | let c = u8::try_from(q / 1000);
|
||||
| ~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: aborting due to 51 previous errors
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:379:5
|
||||
|
|
||||
LL | (-2_i32).pow(3) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:384:5
|
||||
|
|
||||
LL | (x * x * x) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i16` to `u16` may lose the sign of the value
|
||||
--> $DIR/cast.rs:388:5
|
||||
|
|
||||
LL | (y * y * y * y * -2) as u16;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i16` to `u16` may lose the sign of the value
|
||||
--> $DIR/cast.rs:391:5
|
||||
|
|
||||
LL | (y * y * y * 2) as u16;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i16` to `u16` may lose the sign of the value
|
||||
--> $DIR/cast.rs:393:5
|
||||
|
|
||||
LL | (y * y * y * -2) as u16;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:398:9
|
||||
|
|
||||
LL | (a * b * c) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:400:9
|
||||
|
|
||||
LL | (a * -b * c) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:403:9
|
||||
|
|
||||
LL | (a * -2) as u32;
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:405:9
|
||||
|
|
||||
LL | (a * b * c * -2) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:408:9
|
||||
|
|
||||
LL | (a / b * c) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:410:9
|
||||
|
|
||||
LL | (a / b + b * c) as u32;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `i32` to `u32` may lose the sign of the value
|
||||
--> $DIR/cast.rs:412:9
|
||||
|
|
||||
LL | a.pow(3) as u32;
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 63 previous errors
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
#![warn(clippy::let_unit_value)]
|
||||
|
||||
fn f() {}
|
||||
static FN: fn() = f;
|
||||
|
||||
fn main() {
|
||||
FN();
|
||||
//~^ ERROR: this let-binding has unit value
|
||||
//~| NOTE: `-D clippy::let-unit-value` implied by `-D warnings`
|
||||
}
|
@ -5,6 +5,4 @@ fn f() {}
|
||||
|
||||
fn main() {
|
||||
let _: () = FN();
|
||||
//~^ ERROR: this let-binding has unit value
|
||||
//~| NOTE: `-D clippy::let-unit-value` implied by `-D warnings`
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
error: this let-binding has unit value
|
||||
--> $DIR/ice-8821.rs:7:5
|
||||
|
|
||||
LL | let _: () = FN();
|
||||
| ^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `FN();`
|
||||
|
|
||||
= note: `-D clippy::let-unit-value` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::let_unit_value)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
@ -73,9 +73,7 @@ mod nested_local {
|
||||
|
||||
mod function_def {
|
||||
fn ret_f64() -> f64 {
|
||||
// Even though the output type is specified,
|
||||
// this unsuffixed literal is linted to reduce heuristics and keep codebase simple.
|
||||
1.0_f64
|
||||
1.
|
||||
}
|
||||
|
||||
fn test() {
|
||||
|
@ -73,8 +73,6 @@ fn test() {
|
||||
|
||||
mod function_def {
|
||||
fn ret_f64() -> f64 {
|
||||
// Even though the output type is specified,
|
||||
// this unsuffixed literal is linted to reduce heuristics and keep codebase simple.
|
||||
1.
|
||||
}
|
||||
|
||||
|
@ -86,66 +86,60 @@ LL | let y = 1.;
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:78:9
|
||||
|
|
||||
LL | 1.
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:84:27
|
||||
--> $DIR/default_numeric_fallback_f64.rs:82:27
|
||||
|
|
||||
LL | let f = || -> _ { 1. };
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:88:29
|
||||
--> $DIR/default_numeric_fallback_f64.rs:86:29
|
||||
|
|
||||
LL | let f = || -> f64 { 1. };
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:102:21
|
||||
--> $DIR/default_numeric_fallback_f64.rs:100:21
|
||||
|
|
||||
LL | generic_arg(1.);
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:105:32
|
||||
--> $DIR/default_numeric_fallback_f64.rs:103:32
|
||||
|
|
||||
LL | let x: _ = generic_arg(1.);
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:123:28
|
||||
--> $DIR/default_numeric_fallback_f64.rs:121:28
|
||||
|
|
||||
LL | GenericStruct { x: 1. };
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:126:36
|
||||
--> $DIR/default_numeric_fallback_f64.rs:124:36
|
||||
|
|
||||
LL | let _ = GenericStruct { x: 1. };
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:144:24
|
||||
--> $DIR/default_numeric_fallback_f64.rs:142:24
|
||||
|
|
||||
LL | GenericEnum::X(1.);
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:164:23
|
||||
--> $DIR/default_numeric_fallback_f64.rs:162:23
|
||||
|
|
||||
LL | s.generic_arg(1.);
|
||||
| ^^ help: consider adding suffix: `1.0_f64`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_f64.rs:174:25
|
||||
--> $DIR/default_numeric_fallback_f64.rs:172:25
|
||||
|
|
||||
LL | inline!(let x = 22.;);
|
||||
| ^^^ help: consider adding suffix: `22.0_f64`
|
||||
|
|
||||
= note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to 24 previous errors
|
||||
error: aborting due to 23 previous errors
|
||||
|
||||
|
@ -74,9 +74,7 @@ mod nested_local {
|
||||
|
||||
mod function_def {
|
||||
fn ret_i32() -> i32 {
|
||||
// Even though the output type is specified,
|
||||
// this unsuffixed literal is linted to reduce heuristics and keep codebase simple.
|
||||
1_i32
|
||||
1
|
||||
}
|
||||
|
||||
fn test() {
|
||||
@ -186,4 +184,36 @@ fn check_expect_suppression() {
|
||||
let x = 21;
|
||||
}
|
||||
|
||||
mod type_already_inferred {
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_i32() -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_if_i32(b: bool) -> i32 {
|
||||
if b { 100 } else { 0 }
|
||||
}
|
||||
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_i32_tuple() -> (i32, i32) {
|
||||
(0, 1)
|
||||
}
|
||||
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_stmt(b: bool) -> (i32, i32) {
|
||||
if b {
|
||||
return (0, 1);
|
||||
}
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_vec)]
|
||||
fn vec_macro() {
|
||||
// Should NOT lint in `vec!` call if the type was already stated
|
||||
let data_i32: Vec<i32> = vec![1, 2, 3];
|
||||
let data_i32 = vec![1_i32, 2_i32, 3_i32];
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
@ -74,8 +74,6 @@ fn test() {
|
||||
|
||||
mod function_def {
|
||||
fn ret_i32() -> i32 {
|
||||
// Even though the output type is specified,
|
||||
// this unsuffixed literal is linted to reduce heuristics and keep codebase simple.
|
||||
1
|
||||
}
|
||||
|
||||
@ -186,4 +184,36 @@ fn check_expect_suppression() {
|
||||
let x = 21;
|
||||
}
|
||||
|
||||
mod type_already_inferred {
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_i32() -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_if_i32(b: bool) -> i32 {
|
||||
if b { 100 } else { 0 }
|
||||
}
|
||||
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_i32_tuple() -> (i32, i32) {
|
||||
(0, 1)
|
||||
}
|
||||
|
||||
// Should NOT lint if bound to return type
|
||||
fn ret_stmt(b: bool) -> (i32, i32) {
|
||||
if b {
|
||||
return (0, 1);
|
||||
}
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_vec)]
|
||||
fn vec_macro() {
|
||||
// Should NOT lint in `vec!` call if the type was already stated
|
||||
let data_i32: Vec<i32> = vec![1, 2, 3];
|
||||
let data_i32 = vec![1, 2, 3];
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
@ -98,66 +98,78 @@ LL | let y = 1;
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:79:9
|
||||
|
|
||||
LL | 1
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:85:27
|
||||
--> $DIR/default_numeric_fallback_i32.rs:83:27
|
||||
|
|
||||
LL | let f = || -> _ { 1 };
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:89:29
|
||||
--> $DIR/default_numeric_fallback_i32.rs:87:29
|
||||
|
|
||||
LL | let f = || -> i32 { 1 };
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:103:21
|
||||
--> $DIR/default_numeric_fallback_i32.rs:101:21
|
||||
|
|
||||
LL | generic_arg(1);
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:106:32
|
||||
--> $DIR/default_numeric_fallback_i32.rs:104:32
|
||||
|
|
||||
LL | let x: _ = generic_arg(1);
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:124:28
|
||||
--> $DIR/default_numeric_fallback_i32.rs:122:28
|
||||
|
|
||||
LL | GenericStruct { x: 1 };
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:127:36
|
||||
--> $DIR/default_numeric_fallback_i32.rs:125:36
|
||||
|
|
||||
LL | let _ = GenericStruct { x: 1 };
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:145:24
|
||||
--> $DIR/default_numeric_fallback_i32.rs:143:24
|
||||
|
|
||||
LL | GenericEnum::X(1);
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:165:23
|
||||
--> $DIR/default_numeric_fallback_i32.rs:163:23
|
||||
|
|
||||
LL | s.generic_arg(1);
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:175:25
|
||||
--> $DIR/default_numeric_fallback_i32.rs:173:25
|
||||
|
|
||||
LL | inline!(let x = 22;);
|
||||
| ^^ help: consider adding suffix: `22_i32`
|
||||
|
|
||||
= note: this error originates in the macro `__inline_mac_fn_internal` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to 26 previous errors
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:215:29
|
||||
|
|
||||
LL | let data_i32 = vec![1, 2, 3];
|
||||
| ^ help: consider adding suffix: `1_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:215:32
|
||||
|
|
||||
LL | let data_i32 = vec![1, 2, 3];
|
||||
| ^ help: consider adding suffix: `2_i32`
|
||||
|
||||
error: default numeric fallback might occur
|
||||
--> $DIR/default_numeric_fallback_i32.rs:215:35
|
||||
|
|
||||
LL | let data_i32 = vec![1, 2, 3];
|
||||
| ^ help: consider adding suffix: `3_i32`
|
||||
|
||||
error: aborting due to 28 previous errors
|
||||
|
||||
|
@ -12,16 +12,49 @@ enum Opcode {
|
||||
Div = 3,
|
||||
}
|
||||
|
||||
struct Data {
|
||||
foo: &'static [u8],
|
||||
bar: &'static [u8],
|
||||
}
|
||||
|
||||
fn int_to_opcode(op: u8) -> Option<Opcode> {
|
||||
(op < 4).then(|| unsafe { std::mem::transmute(op) })
|
||||
}
|
||||
|
||||
fn f(op: u8, unrelated: u8) {
|
||||
fn f(op: u8, op2: Data, unrelated: u8) {
|
||||
true.then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(unrelated < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
|
||||
let _: Option<Opcode> = (op > 0 && op < 10).then(|| unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (op > 0 && op < 10 && unrelated == 0).then(|| unsafe { std::mem::transmute(op) });
|
||||
|
||||
// lint even when the transmutable goes through field/array accesses
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.foo[0] < 10).then(|| unsafe { std::mem::transmute(op2.foo[0]) });
|
||||
|
||||
// don't lint: wrong index used in the transmute
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[1]) });
|
||||
|
||||
// don't lint: no check for the transmutable in the condition
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op2.bar[0]) });
|
||||
|
||||
// don't lint: wrong variable
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op) });
|
||||
|
||||
// range contains checks
|
||||
let _: Option<Opcode> = (1..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = ((1..=3).contains(&op) || op == 4).then(|| unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (1..3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (1..).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (..3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
|
||||
// unrelated binding in contains
|
||||
let _: Option<Opcode> = (1..=3)
|
||||
.contains(&unrelated)
|
||||
.then_some(unsafe { std::mem::transmute(op) });
|
||||
}
|
||||
|
||||
unsafe fn f2(op: u8) {
|
||||
|
@ -12,16 +12,49 @@ enum Opcode {
|
||||
Div = 3,
|
||||
}
|
||||
|
||||
struct Data {
|
||||
foo: &'static [u8],
|
||||
bar: &'static [u8],
|
||||
}
|
||||
|
||||
fn int_to_opcode(op: u8) -> Option<Opcode> {
|
||||
(op < 4).then_some(unsafe { std::mem::transmute(op) })
|
||||
}
|
||||
|
||||
fn f(op: u8, unrelated: u8) {
|
||||
fn f(op: u8, op2: Data, unrelated: u8) {
|
||||
true.then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(unrelated < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
(op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
|
||||
let _: Option<Opcode> = (op > 0 && op < 10).then_some(unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (op > 0 && op < 10 && unrelated == 0).then_some(unsafe { std::mem::transmute(op) });
|
||||
|
||||
// lint even when the transmutable goes through field/array accesses
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[0]) });
|
||||
|
||||
// don't lint: wrong index used in the transmute
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[1]) });
|
||||
|
||||
// don't lint: no check for the transmutable in the condition
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op2.bar[0]) });
|
||||
|
||||
// don't lint: wrong variable
|
||||
let _: Option<Opcode> = (op2.foo[0] > 0 && op2.bar[1] < 10).then_some(unsafe { std::mem::transmute(op) });
|
||||
|
||||
// range contains checks
|
||||
let _: Option<Opcode> = (1..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = ((1..=3).contains(&op) || op == 4).then_some(unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (1..3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (1..).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (..3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
let _: Option<Opcode> = (..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
|
||||
// unrelated binding in contains
|
||||
let _: Option<Opcode> = (1..=3)
|
||||
.contains(&unrelated)
|
||||
.then_some(unsafe { std::mem::transmute(op) });
|
||||
}
|
||||
|
||||
unsafe fn f2(op: u8) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:16:33
|
||||
--> $DIR/eager_transmute.rs:21:33
|
||||
|
|
||||
LL | (op < 4).then_some(unsafe { std::mem::transmute(op) })
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -12,7 +12,7 @@ LL | (op < 4).then(|| unsafe { std::mem::transmute(op) })
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:22:33
|
||||
--> $DIR/eager_transmute.rs:27:33
|
||||
|
|
||||
LL | (op < 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -23,7 +23,7 @@ LL | (op < 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:23:33
|
||||
--> $DIR/eager_transmute.rs:28:33
|
||||
|
|
||||
LL | (op > 4).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -34,7 +34,7 @@ LL | (op > 4).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:24:34
|
||||
--> $DIR/eager_transmute.rs:29:34
|
||||
|
|
||||
LL | (op == 0).then_some(unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -45,7 +45,106 @@ LL | (op == 0).then(|| unsafe { std::mem::transmute::<_, Opcode>(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:28:24
|
||||
--> $DIR/eager_transmute.rs:31:68
|
||||
|
|
||||
LL | let _: Option<Opcode> = (op > 0 && op < 10).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (op > 0 && op < 10).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:32:86
|
||||
|
|
||||
LL | let _: Option<Opcode> = (op > 0 && op < 10 && unrelated == 0).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (op > 0 && op < 10 && unrelated == 0).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:35:84
|
||||
|
|
||||
LL | let _: Option<Opcode> = (op2.foo[0] > 0 && op2.foo[0] < 10).then_some(unsafe { std::mem::transmute(op2.foo[0]) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (op2.foo[0] > 0 && op2.foo[0] < 10).then(|| unsafe { std::mem::transmute(op2.foo[0]) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:47:70
|
||||
|
|
||||
LL | let _: Option<Opcode> = (1..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (1..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:48:83
|
||||
|
|
||||
LL | let _: Option<Opcode> = ((1..=3).contains(&op) || op == 4).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = ((1..=3).contains(&op) || op == 4).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:49:69
|
||||
|
|
||||
LL | let _: Option<Opcode> = (1..3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (1..3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:50:68
|
||||
|
|
||||
LL | let _: Option<Opcode> = (1..).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (1..).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:51:68
|
||||
|
|
||||
LL | let _: Option<Opcode> = (..3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (..3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:52:69
|
||||
|
|
||||
LL | let _: Option<Opcode> = (..=3).contains(&op).then_some(unsafe { std::mem::transmute(op) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `bool::then` to only transmute if the condition holds
|
||||
|
|
||||
LL | let _: Option<Opcode> = (..=3).contains(&op).then(|| unsafe { std::mem::transmute(op) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:61:24
|
||||
|
|
||||
LL | (op < 4).then_some(std::mem::transmute::<_, Opcode>(op));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -56,7 +155,7 @@ LL | (op < 4).then(|| std::mem::transmute::<_, Opcode>(op));
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:57:60
|
||||
--> $DIR/eager_transmute.rs:90:60
|
||||
|
|
||||
LL | let _: Option<NonZeroU8> = (v1 > 0).then_some(unsafe { std::mem::transmute(v1) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -67,7 +166,7 @@ LL | let _: Option<NonZeroU8> = (v1 > 0).then(|| unsafe { std::mem::transmut
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:63:86
|
||||
--> $DIR/eager_transmute.rs:96:86
|
||||
|
|
||||
LL | let _: Option<NonMaxU8> = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -78,7 +177,7 @@ LL | let _: Option<NonMaxU8> = (v2 < NonZeroU8::new(255).unwrap()).then(|| u
|
||||
| ~~~~ ++
|
||||
|
||||
error: this transmute is always evaluated eagerly, even if the condition is false
|
||||
--> $DIR/eager_transmute.rs:69:93
|
||||
--> $DIR/eager_transmute.rs:102:93
|
||||
|
|
||||
LL | let _: Option<NonZeroNonMaxU8> = (v2 < NonZeroU8::new(255).unwrap()).then_some(unsafe { std::mem::transmute(v2) });
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -88,5 +187,5 @@ help: consider using `bool::then` to only transmute if the condition holds
|
||||
LL | let _: Option<NonZeroNonMaxU8> = (v2 < NonZeroU8::new(255).unwrap()).then(|| unsafe { std::mem::transmute(v2) });
|
||||
| ~~~~ ++
|
||||
|
||||
error: aborting due to 8 previous errors
|
||||
error: aborting due to 17 previous errors
|
||||
|
||||
|
27
tests/ui/empty_enum_variants_with_brackets.fixed
Normal file
27
tests/ui/empty_enum_variants_with_brackets.fixed
Normal file
@ -0,0 +1,27 @@
|
||||
#![warn(clippy::empty_enum_variants_with_brackets)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub enum PublicTestEnum {
|
||||
NonEmptyBraces { x: i32, y: i32 }, // No error
|
||||
NonEmptyParentheses(i32, i32), // No error
|
||||
EmptyBraces, //~ ERROR: enum variant has empty brackets
|
||||
EmptyParentheses, //~ ERROR: enum variant has empty brackets
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
NonEmptyBraces { x: i32, y: i32 }, // No error
|
||||
NonEmptyParentheses(i32, i32), // No error
|
||||
EmptyBraces, //~ ERROR: enum variant has empty brackets
|
||||
EmptyParentheses, //~ ERROR: enum variant has empty brackets
|
||||
AnotherEnum, // No error
|
||||
}
|
||||
|
||||
enum TestEnumWithFeatures {
|
||||
NonEmptyBraces {
|
||||
#[cfg(feature = "thisisneverenabled")]
|
||||
x: i32,
|
||||
}, // No error
|
||||
NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32), // No error
|
||||
}
|
||||
|
||||
fn main() {}
|
27
tests/ui/empty_enum_variants_with_brackets.rs
Normal file
27
tests/ui/empty_enum_variants_with_brackets.rs
Normal file
@ -0,0 +1,27 @@
|
||||
#![warn(clippy::empty_enum_variants_with_brackets)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub enum PublicTestEnum {
|
||||
NonEmptyBraces { x: i32, y: i32 }, // No error
|
||||
NonEmptyParentheses(i32, i32), // No error
|
||||
EmptyBraces {}, //~ ERROR: enum variant has empty brackets
|
||||
EmptyParentheses(), //~ ERROR: enum variant has empty brackets
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
NonEmptyBraces { x: i32, y: i32 }, // No error
|
||||
NonEmptyParentheses(i32, i32), // No error
|
||||
EmptyBraces {}, //~ ERROR: enum variant has empty brackets
|
||||
EmptyParentheses(), //~ ERROR: enum variant has empty brackets
|
||||
AnotherEnum, // No error
|
||||
}
|
||||
|
||||
enum TestEnumWithFeatures {
|
||||
NonEmptyBraces {
|
||||
#[cfg(feature = "thisisneverenabled")]
|
||||
x: i32,
|
||||
}, // No error
|
||||
NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32), // No error
|
||||
}
|
||||
|
||||
fn main() {}
|
36
tests/ui/empty_enum_variants_with_brackets.stderr
Normal file
36
tests/ui/empty_enum_variants_with_brackets.stderr
Normal file
@ -0,0 +1,36 @@
|
||||
error: enum variant has empty brackets
|
||||
--> $DIR/empty_enum_variants_with_brackets.rs:7:16
|
||||
|
|
||||
LL | EmptyBraces {},
|
||||
| ^^^
|
||||
|
|
||||
= note: `-D clippy::empty-enum-variants-with-brackets` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::empty_enum_variants_with_brackets)]`
|
||||
= help: remove the brackets
|
||||
|
||||
error: enum variant has empty brackets
|
||||
--> $DIR/empty_enum_variants_with_brackets.rs:8:21
|
||||
|
|
||||
LL | EmptyParentheses(),
|
||||
| ^^
|
||||
|
|
||||
= help: remove the brackets
|
||||
|
||||
error: enum variant has empty brackets
|
||||
--> $DIR/empty_enum_variants_with_brackets.rs:14:16
|
||||
|
|
||||
LL | EmptyBraces {},
|
||||
| ^^^
|
||||
|
|
||||
= help: remove the brackets
|
||||
|
||||
error: enum variant has empty brackets
|
||||
--> $DIR/empty_enum_variants_with_brackets.rs:15:21
|
||||
|
|
||||
LL | EmptyParentheses(),
|
||||
| ^^
|
||||
|
|
||||
= help: remove the brackets
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
@ -6,7 +6,9 @@
|
||||
clippy::unnecessary_operation,
|
||||
clippy::op_ref,
|
||||
clippy::double_parens,
|
||||
clippy::uninlined_format_args
|
||||
clippy::uninlined_format_args,
|
||||
clippy::borrow_deref_ref,
|
||||
clippy::deref_addrof
|
||||
)]
|
||||
|
||||
use std::fmt::Write as _;
|
||||
@ -40,32 +42,45 @@ fn main() {
|
||||
let x = 0;
|
||||
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x + 1;
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
1 + x;
|
||||
x - ZERO; //no error, as we skip lookups (for now)
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
((ZERO)) | x; //no error, as we skip lookups (for now)
|
||||
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x / ONE; //no error, as we skip lookups (for now)
|
||||
|
||||
x / 2; //no false positive
|
||||
|
||||
x & NEG_ONE; //no error, as we skip lookups (for now)
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
let u: u8 = 0;
|
||||
u;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
1 << 0; // no error, this case is allowed, see issue 3430
|
||||
42;
|
||||
//~^ ERROR: this operation has no effect
|
||||
1;
|
||||
//~^ ERROR: this operation has no effect
|
||||
42;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
let mut a = A(String::new());
|
||||
let b = a << 0; // no error: non-integer
|
||||
@ -73,10 +88,15 @@ fn main() {
|
||||
1 * Meter; // no error: non-integer
|
||||
|
||||
2;
|
||||
//~^ ERROR: this operation has no effect
|
||||
-2;
|
||||
//~^ ERROR: this operation has no effect
|
||||
2 + x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
-2 + x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x + 1;
|
||||
//~^ ERROR: this operation has no effect
|
||||
(x + 1) % 3; // no error
|
||||
4 % 3; // no error
|
||||
4 % -3; // no error
|
||||
@ -85,38 +105,110 @@ fn main() {
|
||||
let a = 0;
|
||||
let b = true;
|
||||
(if b { 1 } else { 2 });
|
||||
//~^ ERROR: this operation has no effect
|
||||
(if b { 1 } else { 2 }) + if b { 3 } else { 4 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
(match a { 0 => 10, _ => 20 });
|
||||
//~^ ERROR: this operation has no effect
|
||||
(match a { 0 => 10, _ => 20 }) + match a { 0 => 30, _ => 40 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
(if b { 1 } else { 2 }) + match a { 0 => 30, _ => 40 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
(match a { 0 => 10, _ => 20 }) + if b { 3 } else { 4 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
(if b { 1 } else { 2 });
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
({ a }) + 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
({ a } * 2);
|
||||
//~^ ERROR: this operation has no effect
|
||||
(loop { let mut c = 0; if c == 10 { break c; } c += 1; }) + { a * 2 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
fn f(_: i32) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
f(a + { 8 * 5 });
|
||||
//~^ ERROR: this operation has no effect
|
||||
f(if b { 1 } else { 2 } + 3);
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
const _: i32 = { 2 * 4 } + 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
const _: i32 = { 1 + 2 * 3 } + 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
a as usize;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _ = a as usize;
|
||||
//~^ ERROR: this operation has no effect
|
||||
({ a } as usize);
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
2 * { a };
|
||||
//~^ ERROR: this operation has no effect
|
||||
(({ a } + 4));
|
||||
//~^ ERROR: this operation has no effect
|
||||
1;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
// Issue #9904
|
||||
let x = 0i32;
|
||||
let _: i32 = x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
|
||||
pub fn decide(a: bool, b: bool) -> u32 {
|
||||
(if a { 1 } else { 2 }) + if b { 3 } else { 5 }
|
||||
}
|
||||
|
||||
/// The following tests are from / for issue #12050
|
||||
/// In short, the lint didn't work for coerced references,
|
||||
/// e.g. let x = &0; let y = x + 0;
|
||||
/// because the suggested fix was `let y = x;` but
|
||||
/// it should have been `let y = *x;`
|
||||
fn issue_12050() {
|
||||
{
|
||||
let x = &0i32;
|
||||
let _: i32 = *x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = *x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
{
|
||||
let x = &&0i32;
|
||||
let _: i32 = **x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let x = &&0i32;
|
||||
let _: i32 = **x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
{
|
||||
// this is just silly
|
||||
let x = &&&0i32;
|
||||
let _: i32 = ***x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = ***x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let x = 0i32;
|
||||
let _: i32 = *&x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = **&&x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = *&*&x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = **&&*&x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
{
|
||||
// this is getting ridiculous, but we should still see the same
|
||||
// error message so let's just keep going
|
||||
let x = &0i32;
|
||||
let _: i32 = ***&&*&x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = ***&&*&x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@
|
||||
clippy::unnecessary_operation,
|
||||
clippy::op_ref,
|
||||
clippy::double_parens,
|
||||
clippy::uninlined_format_args
|
||||
clippy::uninlined_format_args,
|
||||
clippy::borrow_deref_ref,
|
||||
clippy::deref_addrof
|
||||
)]
|
||||
|
||||
use std::fmt::Write as _;
|
||||
@ -40,32 +42,45 @@ fn main() {
|
||||
let x = 0;
|
||||
|
||||
x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x + (1 - 1);
|
||||
//~^ ERROR: this operation has no effect
|
||||
x + 1;
|
||||
0 + x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
1 + x;
|
||||
x - ZERO; //no error, as we skip lookups (for now)
|
||||
x | (0);
|
||||
//~^ ERROR: this operation has no effect
|
||||
((ZERO)) | x; //no error, as we skip lookups (for now)
|
||||
|
||||
x * 1;
|
||||
//~^ ERROR: this operation has no effect
|
||||
1 * x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x / ONE; //no error, as we skip lookups (for now)
|
||||
|
||||
x / 2; //no false positive
|
||||
|
||||
x & NEG_ONE; //no error, as we skip lookups (for now)
|
||||
-1 & x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
let u: u8 = 0;
|
||||
u & 255;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
1 << 0; // no error, this case is allowed, see issue 3430
|
||||
42 << 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
1 >> 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
42 >> 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
&x >> 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x >> &0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
let mut a = A(String::new());
|
||||
let b = a << 0; // no error: non-integer
|
||||
@ -73,10 +88,15 @@ fn main() {
|
||||
1 * Meter; // no error: non-integer
|
||||
|
||||
2 % 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
-2 % 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
2 % -3 + x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
-2 % -3 + x;
|
||||
//~^ ERROR: this operation has no effect
|
||||
x + 1 % 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
(x + 1) % 3; // no error
|
||||
4 % 3; // no error
|
||||
4 % -3; // no error
|
||||
@ -85,38 +105,110 @@ fn main() {
|
||||
let a = 0;
|
||||
let b = true;
|
||||
0 + if b { 1 } else { 2 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + if b { 1 } else { 2 } + if b { 3 } else { 4 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + match a { 0 => 10, _ => 20 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + match a { 0 => 10, _ => 20 } + match a { 0 => 30, _ => 40 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + if b { 1 } else { 2 } + match a { 0 => 30, _ => 40 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + match a { 0 => 10, _ => 20 } + if b { 3 } else { 4 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
(if b { 1 } else { 2 }) + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
0 + { a } + 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + { a } * 2;
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + loop { let mut c = 0; if c == 10 { break c; } c += 1; } + { a * 2 };
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
fn f(_: i32) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
f(1 * a + { 8 * 5 });
|
||||
//~^ ERROR: this operation has no effect
|
||||
f(0 + if b { 1 } else { 2 } + 3);
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
const _: i32 = { 2 * 4 } + 0 + 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
const _: i32 = 0 + { 1 + 2 * 3 } + 3;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
0 + a as usize;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _ = 0 + a as usize;
|
||||
//~^ ERROR: this operation has no effect
|
||||
0 + { a } as usize;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
2 * (0 + { a });
|
||||
//~^ ERROR: this operation has no effect
|
||||
1 * ({ a } + 4);
|
||||
//~^ ERROR: this operation has no effect
|
||||
1 * 1;
|
||||
//~^ ERROR: this operation has no effect
|
||||
|
||||
// Issue #9904
|
||||
let x = 0i32;
|
||||
let _: i32 = &x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
|
||||
pub fn decide(a: bool, b: bool) -> u32 {
|
||||
0 + if a { 1 } else { 2 } + if b { 3 } else { 5 }
|
||||
}
|
||||
|
||||
/// The following tests are from / for issue #12050
|
||||
/// In short, the lint didn't work for coerced references,
|
||||
/// e.g. let x = &0; let y = x + 0;
|
||||
/// because the suggested fix was `let y = x;` but
|
||||
/// it should have been `let y = *x;`
|
||||
fn issue_12050() {
|
||||
{
|
||||
let x = &0i32;
|
||||
let _: i32 = *x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
{
|
||||
let x = &&0i32;
|
||||
let _: i32 = **x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let x = &&0i32;
|
||||
let _: i32 = *x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
{
|
||||
// this is just silly
|
||||
let x = &&&0i32;
|
||||
let _: i32 = ***x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = **x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let x = 0i32;
|
||||
let _: i32 = *&x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = **&&x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = *&*&x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = **&&*&x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
{
|
||||
// this is getting ridiculous, but we should still see the same
|
||||
// error message so let's just keep going
|
||||
let x = &0i32;
|
||||
let _: i32 = **&&*&x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
let _: i32 = **&&*&x + 0;
|
||||
//~^ ERROR: this operation has no effect
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:42:5
|
||||
--> $DIR/identity_op.rs:44:5
|
||||
|
|
||||
LL | x + 0;
|
||||
| ^^^^^ help: consider reducing it to: `x`
|
||||
@ -8,238 +8,310 @@ LL | x + 0;
|
||||
= help: to override `-D warnings` add `#[allow(clippy::identity_op)]`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:43:5
|
||||
--> $DIR/identity_op.rs:46:5
|
||||
|
|
||||
LL | x + (1 - 1);
|
||||
| ^^^^^^^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:45:5
|
||||
--> $DIR/identity_op.rs:49:5
|
||||
|
|
||||
LL | 0 + x;
|
||||
| ^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:48:5
|
||||
--> $DIR/identity_op.rs:53:5
|
||||
|
|
||||
LL | x | (0);
|
||||
| ^^^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:51:5
|
||||
--> $DIR/identity_op.rs:57:5
|
||||
|
|
||||
LL | x * 1;
|
||||
| ^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:52:5
|
||||
--> $DIR/identity_op.rs:59:5
|
||||
|
|
||||
LL | 1 * x;
|
||||
| ^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:58:5
|
||||
--> $DIR/identity_op.rs:66:5
|
||||
|
|
||||
LL | -1 & x;
|
||||
| ^^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:61:5
|
||||
--> $DIR/identity_op.rs:70:5
|
||||
|
|
||||
LL | u & 255;
|
||||
| ^^^^^^^ help: consider reducing it to: `u`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:64:5
|
||||
--> $DIR/identity_op.rs:74:5
|
||||
|
|
||||
LL | 42 << 0;
|
||||
| ^^^^^^^ help: consider reducing it to: `42`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:65:5
|
||||
--> $DIR/identity_op.rs:76:5
|
||||
|
|
||||
LL | 1 >> 0;
|
||||
| ^^^^^^ help: consider reducing it to: `1`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:66:5
|
||||
--> $DIR/identity_op.rs:78:5
|
||||
|
|
||||
LL | 42 >> 0;
|
||||
| ^^^^^^^ help: consider reducing it to: `42`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:67:5
|
||||
--> $DIR/identity_op.rs:80:5
|
||||
|
|
||||
LL | &x >> 0;
|
||||
| ^^^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:68:5
|
||||
--> $DIR/identity_op.rs:82:5
|
||||
|
|
||||
LL | x >> &0;
|
||||
| ^^^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:75:5
|
||||
--> $DIR/identity_op.rs:90:5
|
||||
|
|
||||
LL | 2 % 3;
|
||||
| ^^^^^ help: consider reducing it to: `2`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:76:5
|
||||
--> $DIR/identity_op.rs:92:5
|
||||
|
|
||||
LL | -2 % 3;
|
||||
| ^^^^^^ help: consider reducing it to: `-2`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:77:5
|
||||
--> $DIR/identity_op.rs:94:5
|
||||
|
|
||||
LL | 2 % -3 + x;
|
||||
| ^^^^^^ help: consider reducing it to: `2`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:78:5
|
||||
--> $DIR/identity_op.rs:96:5
|
||||
|
|
||||
LL | -2 % -3 + x;
|
||||
| ^^^^^^^ help: consider reducing it to: `-2`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:79:9
|
||||
--> $DIR/identity_op.rs:98:9
|
||||
|
|
||||
LL | x + 1 % 3;
|
||||
| ^^^^^ help: consider reducing it to: `1`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:87:5
|
||||
--> $DIR/identity_op.rs:107:5
|
||||
|
|
||||
LL | 0 + if b { 1 } else { 2 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:88:5
|
||||
--> $DIR/identity_op.rs:109:5
|
||||
|
|
||||
LL | 0 + if b { 1 } else { 2 } + if b { 3 } else { 4 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:89:5
|
||||
--> $DIR/identity_op.rs:111:5
|
||||
|
|
||||
LL | 0 + match a { 0 => 10, _ => 20 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(match a { 0 => 10, _ => 20 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:90:5
|
||||
--> $DIR/identity_op.rs:113:5
|
||||
|
|
||||
LL | 0 + match a { 0 => 10, _ => 20 } + match a { 0 => 30, _ => 40 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(match a { 0 => 10, _ => 20 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:91:5
|
||||
--> $DIR/identity_op.rs:115:5
|
||||
|
|
||||
LL | 0 + if b { 1 } else { 2 } + match a { 0 => 30, _ => 40 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:92:5
|
||||
--> $DIR/identity_op.rs:117:5
|
||||
|
|
||||
LL | 0 + match a { 0 => 10, _ => 20 } + if b { 3 } else { 4 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(match a { 0 => 10, _ => 20 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:93:5
|
||||
--> $DIR/identity_op.rs:119:5
|
||||
|
|
||||
LL | (if b { 1 } else { 2 }) + 0;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if b { 1 } else { 2 })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:95:5
|
||||
--> $DIR/identity_op.rs:122:5
|
||||
|
|
||||
LL | 0 + { a } + 3;
|
||||
| ^^^^^^^^^ help: consider reducing it to: `({ a })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:96:5
|
||||
--> $DIR/identity_op.rs:124:5
|
||||
|
|
||||
LL | 0 + { a } * 2;
|
||||
| ^^^^^^^^^^^^^ help: consider reducing it to: `({ a } * 2)`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:97:5
|
||||
--> $DIR/identity_op.rs:126:5
|
||||
|
|
||||
LL | 0 + loop { let mut c = 0; if c == 10 { break c; } c += 1; } + { a * 2 };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(loop { let mut c = 0; if c == 10 { break c; } c += 1; })`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:102:7
|
||||
--> $DIR/identity_op.rs:133:7
|
||||
|
|
||||
LL | f(1 * a + { 8 * 5 });
|
||||
| ^^^^^ help: consider reducing it to: `a`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:103:7
|
||||
--> $DIR/identity_op.rs:135:7
|
||||
|
|
||||
LL | f(0 + if b { 1 } else { 2 } + 3);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `if b { 1 } else { 2 }`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:104:20
|
||||
--> $DIR/identity_op.rs:138:20
|
||||
|
|
||||
LL | const _: i32 = { 2 * 4 } + 0 + 3;
|
||||
| ^^^^^^^^^^^^^ help: consider reducing it to: `{ 2 * 4 }`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:105:20
|
||||
--> $DIR/identity_op.rs:140:20
|
||||
|
|
||||
LL | const _: i32 = 0 + { 1 + 2 * 3 } + 3;
|
||||
| ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `{ 1 + 2 * 3 }`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:107:5
|
||||
--> $DIR/identity_op.rs:143:5
|
||||
|
|
||||
LL | 0 + a as usize;
|
||||
| ^^^^^^^^^^^^^^ help: consider reducing it to: `a as usize`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:108:13
|
||||
--> $DIR/identity_op.rs:145:13
|
||||
|
|
||||
LL | let _ = 0 + a as usize;
|
||||
| ^^^^^^^^^^^^^^ help: consider reducing it to: `a as usize`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:109:5
|
||||
--> $DIR/identity_op.rs:147:5
|
||||
|
|
||||
LL | 0 + { a } as usize;
|
||||
| ^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `({ a } as usize)`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:111:9
|
||||
--> $DIR/identity_op.rs:150:9
|
||||
|
|
||||
LL | 2 * (0 + { a });
|
||||
| ^^^^^^^^^^^ help: consider reducing it to: `{ a }`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:112:5
|
||||
--> $DIR/identity_op.rs:152:5
|
||||
|
|
||||
LL | 1 * ({ a } + 4);
|
||||
| ^^^^^^^^^^^^^^^ help: consider reducing it to: `(({ a } + 4))`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:113:5
|
||||
--> $DIR/identity_op.rs:154:5
|
||||
|
|
||||
LL | 1 * 1;
|
||||
| ^^^^^ help: consider reducing it to: `1`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:117:18
|
||||
--> $DIR/identity_op.rs:159:18
|
||||
|
|
||||
LL | let _: i32 = &x + 0;
|
||||
| ^^^^^^ help: consider reducing it to: `x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:121:5
|
||||
--> $DIR/identity_op.rs:164:5
|
||||
|
|
||||
LL | 0 + if a { 1 } else { 2 } + if b { 3 } else { 5 }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(if a { 1 } else { 2 })`
|
||||
|
||||
error: aborting due to 40 previous errors
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:175:22
|
||||
|
|
||||
LL | let _: i32 = *x + 0;
|
||||
| ^^^^^^ help: consider reducing it to: `*x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:177:22
|
||||
|
|
||||
LL | let _: i32 = x + 0;
|
||||
| ^^^^^ help: consider reducing it to: `*x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:182:22
|
||||
|
|
||||
LL | let _: i32 = **x + 0;
|
||||
| ^^^^^^^ help: consider reducing it to: `**x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:185:22
|
||||
|
|
||||
LL | let _: i32 = *x + 0;
|
||||
| ^^^^^^ help: consider reducing it to: `**x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:191:22
|
||||
|
|
||||
LL | let _: i32 = ***x + 0;
|
||||
| ^^^^^^^^ help: consider reducing it to: `***x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:193:22
|
||||
|
|
||||
LL | let _: i32 = **x + 0;
|
||||
| ^^^^^^^ help: consider reducing it to: `***x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:196:22
|
||||
|
|
||||
LL | let _: i32 = *&x + 0;
|
||||
| ^^^^^^^ help: consider reducing it to: `*&x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:198:22
|
||||
|
|
||||
LL | let _: i32 = **&&x + 0;
|
||||
| ^^^^^^^^^ help: consider reducing it to: `**&&x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:200:22
|
||||
|
|
||||
LL | let _: i32 = *&*&x + 0;
|
||||
| ^^^^^^^^^ help: consider reducing it to: `*&*&x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:202:22
|
||||
|
|
||||
LL | let _: i32 = **&&*&x + 0;
|
||||
| ^^^^^^^^^^^ help: consider reducing it to: `**&&*&x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:209:22
|
||||
|
|
||||
LL | let _: i32 = **&&*&x + 0;
|
||||
| ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x`
|
||||
|
||||
error: this operation has no effect
|
||||
--> $DIR/identity_op.rs:211:22
|
||||
|
|
||||
LL | let _: i32 = **&&*&x + 0;
|
||||
| ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x`
|
||||
|
||||
error: aborting due to 52 previous errors
|
||||
|
||||
|
@ -130,3 +130,8 @@ fn issue11394(b: bool, v: Result<(), ()>) -> Result<(), ()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn issue12103(x: u32) -> Option<u32> {
|
||||
// Should not issue an error in `const` context
|
||||
if x > 42 { Some(150) } else { None }
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
//@no-rustfix
|
||||
//@aux-build:proc_macros.rs
|
||||
#![warn(clippy::into_iter_without_iter)]
|
||||
extern crate proc_macros;
|
||||
|
||||
use std::iter::IntoIterator;
|
||||
|
||||
@ -111,6 +113,43 @@ fn into_iter(self) -> Self::IntoIter {
|
||||
}
|
||||
}
|
||||
|
||||
// Fine to lint, the impls comes from a local macro.
|
||||
pub struct Issue12037;
|
||||
macro_rules! generate_impl {
|
||||
() => {
|
||||
impl<'a> IntoIterator for &'a Issue12037 {
|
||||
type IntoIter = std::slice::Iter<'a, u8>;
|
||||
type Item = &'a u8;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
generate_impl!();
|
||||
|
||||
// Impl comes from an external crate
|
||||
proc_macros::external! {
|
||||
pub struct ImplWithForeignSpan;
|
||||
impl<'a> IntoIterator for &'a ImplWithForeignSpan {
|
||||
type IntoIter = std::slice::Iter<'a, u8>;
|
||||
type Item = &'a u8;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Allowed;
|
||||
#[allow(clippy::into_iter_without_iter)]
|
||||
impl<'a> IntoIterator for &'a Allowed {
|
||||
type IntoIter = std::slice::Iter<'a, u8>;
|
||||
type Item = &'a u8;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
||||
pub mod issue11635 {
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: `IntoIterator` implemented for a reference type without an `iter` method
|
||||
--> $DIR/into_iter_without_iter.rs:7:1
|
||||
--> $DIR/into_iter_without_iter.rs:9:1
|
||||
|
|
||||
LL | / impl<'a> IntoIterator for &'a S1 {
|
||||
LL | |
|
||||
@ -23,7 +23,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: `IntoIterator` implemented for a reference type without an `iter_mut` method
|
||||
--> $DIR/into_iter_without_iter.rs:15:1
|
||||
--> $DIR/into_iter_without_iter.rs:17:1
|
||||
|
|
||||
LL | / impl<'a> IntoIterator for &'a mut S1 {
|
||||
LL | |
|
||||
@ -45,7 +45,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: `IntoIterator` implemented for a reference type without an `iter` method
|
||||
--> $DIR/into_iter_without_iter.rs:25:1
|
||||
--> $DIR/into_iter_without_iter.rs:27:1
|
||||
|
|
||||
LL | / impl<'a, T> IntoIterator for &'a S2<T> {
|
||||
LL | |
|
||||
@ -67,7 +67,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: `IntoIterator` implemented for a reference type without an `iter_mut` method
|
||||
--> $DIR/into_iter_without_iter.rs:33:1
|
||||
--> $DIR/into_iter_without_iter.rs:35:1
|
||||
|
|
||||
LL | / impl<'a, T> IntoIterator for &'a mut S2<T> {
|
||||
LL | |
|
||||
@ -89,7 +89,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: `IntoIterator` implemented for a reference type without an `iter_mut` method
|
||||
--> $DIR/into_iter_without_iter.rs:84:1
|
||||
--> $DIR/into_iter_without_iter.rs:86:1
|
||||
|
|
||||
LL | / impl<'a, T> IntoIterator for &mut S4<'a, T> {
|
||||
LL | |
|
||||
@ -110,5 +110,31 @@ LL + }
|
||||
LL + }
|
||||
|
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
error: `IntoIterator` implemented for a reference type without an `iter` method
|
||||
--> $DIR/into_iter_without_iter.rs:120:9
|
||||
|
|
||||
LL | / impl<'a> IntoIterator for &'a Issue12037 {
|
||||
LL | | type IntoIter = std::slice::Iter<'a, u8>;
|
||||
LL | | type Item = &'a u8;
|
||||
LL | | fn into_iter(self) -> Self::IntoIter {
|
||||
LL | | todo!()
|
||||
LL | | }
|
||||
LL | | }
|
||||
| |_________^
|
||||
...
|
||||
LL | generate_impl!();
|
||||
| ---------------- in this macro invocation
|
||||
|
|
||||
= note: this error originates in the macro `generate_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
help: consider implementing `iter`
|
||||
|
|
||||
LL ~
|
||||
LL + impl Issue12037 {
|
||||
LL + fn iter(&self) -> std::slice::Iter<'a, u8> {
|
||||
LL + <&Self as IntoIterator>::into_iter(self)
|
||||
LL + }
|
||||
LL + }
|
||||
|
|
||||
|
||||
error: aborting due to 6 previous errors
|
||||
|
||||
|
@ -1,21 +1,72 @@
|
||||
#![warn(clippy::iter_filter_is_ok)]
|
||||
#![allow(
|
||||
clippy::map_identity,
|
||||
clippy::result_filter_map,
|
||||
clippy::needless_borrow,
|
||||
clippy::redundant_closure
|
||||
)]
|
||||
|
||||
fn main() {
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
.flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
.flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().flatten();
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
}
|
||||
|
||||
fn avoid_linting_when_filter_has_side_effects() {
|
||||
// Don't lint below
|
||||
let mut counter = 0;
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
|
||||
counter += 1;
|
||||
o.is_ok()
|
||||
});
|
||||
}
|
||||
|
||||
fn avoid_linting_when_commented() {
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
|
||||
// Roses are red,
|
||||
// Violets are blue,
|
||||
@ -24,3 +75,131 @@ fn main() {
|
||||
o.is_ok()
|
||||
});
|
||||
}
|
||||
|
||||
fn ice_12058() {
|
||||
// check that checking the parent node doesn't cause an ICE
|
||||
// by indexing the parameters of a closure without parameters
|
||||
Some(1).or_else(|| {
|
||||
vec![Ok(1), Err(())].into_iter().filter(|z| *z != Ok(2));
|
||||
None
|
||||
});
|
||||
}
|
||||
|
||||
fn avoid_linting_map() {
|
||||
// should not lint
|
||||
let _ = vec![Ok(1), Err(())]
|
||||
.into_iter()
|
||||
.filter(|o| o.is_ok())
|
||||
.map(|o| o.unwrap());
|
||||
|
||||
// should not lint
|
||||
let _ = vec![Ok(1), Err(())].into_iter().filter(|o| o.is_ok()).map(|o| o);
|
||||
}
|
||||
|
||||
fn avoid_false_positive_due_to_is_ok_and_iterator_impl() {
|
||||
#[derive(Default, Clone)]
|
||||
struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn is_ok(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Foo {
|
||||
type Item = Foo;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(Foo::default())
|
||||
}
|
||||
}
|
||||
|
||||
let data = vec![Foo::default()];
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(Foo::is_ok);
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(|f| f.is_ok());
|
||||
}
|
||||
|
||||
fn avoid_false_positive_due_to_is_ok_and_into_iterator_impl() {
|
||||
#[derive(Default, Clone)]
|
||||
struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn is_ok(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
let data = vec![Foo::default()];
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(Foo::is_ok);
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(|f| f.is_ok());
|
||||
}
|
||||
|
||||
fn avoid_fp_for_trivial() {
|
||||
let _ = vec![Ok(1), Err(()), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| (Err(()) as Result<i32, ()>).is_ok());
|
||||
}
|
||||
|
||||
fn avoid_false_positive_due_to_method_name() {
|
||||
fn is_ok(x: &Result<i32, i32>) -> bool {
|
||||
x.is_ok()
|
||||
}
|
||||
|
||||
vec![Ok(1), Err(2), Ok(3)].into_iter().filter(is_ok);
|
||||
// should not lint
|
||||
}
|
||||
|
||||
fn avoid_fp_due_to_trait_type() {
|
||||
struct Foo {
|
||||
bar: i32,
|
||||
}
|
||||
impl Foo {
|
||||
fn is_ok(obj: &Result<i32, i32>) -> bool {
|
||||
obj.is_ok()
|
||||
}
|
||||
}
|
||||
vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Foo::is_ok);
|
||||
// should not lint
|
||||
}
|
||||
|
||||
fn avoid_fp_with_call_to_outside_var() {
|
||||
let outside: Result<i32, ()> = Ok(1);
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| outside.is_ok());
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| Result::is_ok(&outside));
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| std::result::Result::is_ok(&outside));
|
||||
}
|
||||
|
||||
fn avoid_fp_with_call_to_outside_var_mix_match_types() {
|
||||
let outside = Some(1);
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| outside.is_some());
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| Option::is_some(&outside));
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| std::option::Option::is_some(&outside));
|
||||
}
|
||||
|
@ -1,21 +1,72 @@
|
||||
#![warn(clippy::iter_filter_is_ok)]
|
||||
#![allow(
|
||||
clippy::map_identity,
|
||||
clippy::result_filter_map,
|
||||
clippy::needless_borrow,
|
||||
clippy::redundant_closure
|
||||
)]
|
||||
|
||||
fn main() {
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Result::is_ok);
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| a.is_ok());
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Result::is_ok);
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| a.is_ok());
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { o.is_ok() });
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| { o.is_ok() });
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|&a| a.is_ok());
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|&a| a.is_ok());
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|&o| { o.is_ok() });
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
.filter(std::result::Result::is_ok);
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
.filter(|a| std::result::Result::is_ok(a));
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|a| { std::result::Result::is_ok(a) });
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
|
||||
{
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|ref a| a.is_ok());
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)].into_iter().filter(|ref a| a.is_ok());
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
|
||||
#[rustfmt::skip]
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|ref o| { o.is_ok() });
|
||||
//~^ HELP: consider using `flatten` instead
|
||||
}
|
||||
}
|
||||
|
||||
fn avoid_linting_when_filter_has_side_effects() {
|
||||
// Don't lint below
|
||||
let mut counter = 0;
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
|
||||
counter += 1;
|
||||
o.is_ok()
|
||||
});
|
||||
}
|
||||
|
||||
fn avoid_linting_when_commented() {
|
||||
let _ = vec![Ok(1), Err(2)].into_iter().filter(|o| {
|
||||
// Roses are red,
|
||||
// Violets are blue,
|
||||
@ -24,3 +75,131 @@ fn main() {
|
||||
o.is_ok()
|
||||
});
|
||||
}
|
||||
|
||||
fn ice_12058() {
|
||||
// check that checking the parent node doesn't cause an ICE
|
||||
// by indexing the parameters of a closure without parameters
|
||||
Some(1).or_else(|| {
|
||||
vec![Ok(1), Err(())].into_iter().filter(|z| *z != Ok(2));
|
||||
None
|
||||
});
|
||||
}
|
||||
|
||||
fn avoid_linting_map() {
|
||||
// should not lint
|
||||
let _ = vec![Ok(1), Err(())]
|
||||
.into_iter()
|
||||
.filter(|o| o.is_ok())
|
||||
.map(|o| o.unwrap());
|
||||
|
||||
// should not lint
|
||||
let _ = vec![Ok(1), Err(())].into_iter().filter(|o| o.is_ok()).map(|o| o);
|
||||
}
|
||||
|
||||
fn avoid_false_positive_due_to_is_ok_and_iterator_impl() {
|
||||
#[derive(Default, Clone)]
|
||||
struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn is_ok(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Foo {
|
||||
type Item = Foo;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(Foo::default())
|
||||
}
|
||||
}
|
||||
|
||||
let data = vec![Foo::default()];
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(Foo::is_ok);
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(|f| f.is_ok());
|
||||
}
|
||||
|
||||
fn avoid_false_positive_due_to_is_ok_and_into_iterator_impl() {
|
||||
#[derive(Default, Clone)]
|
||||
struct Foo {}
|
||||
|
||||
impl Foo {
|
||||
fn is_ok(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
let data = vec![Foo::default()];
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(Foo::is_ok);
|
||||
// should not lint
|
||||
let _ = data.clone().into_iter().filter(|f| f.is_ok());
|
||||
}
|
||||
|
||||
fn avoid_fp_for_trivial() {
|
||||
let _ = vec![Ok(1), Err(()), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| (Err(()) as Result<i32, ()>).is_ok());
|
||||
}
|
||||
|
||||
fn avoid_false_positive_due_to_method_name() {
|
||||
fn is_ok(x: &Result<i32, i32>) -> bool {
|
||||
x.is_ok()
|
||||
}
|
||||
|
||||
vec![Ok(1), Err(2), Ok(3)].into_iter().filter(is_ok);
|
||||
// should not lint
|
||||
}
|
||||
|
||||
fn avoid_fp_due_to_trait_type() {
|
||||
struct Foo {
|
||||
bar: i32,
|
||||
}
|
||||
impl Foo {
|
||||
fn is_ok(obj: &Result<i32, i32>) -> bool {
|
||||
obj.is_ok()
|
||||
}
|
||||
}
|
||||
vec![Ok(1), Err(2), Ok(3)].into_iter().filter(Foo::is_ok);
|
||||
// should not lint
|
||||
}
|
||||
|
||||
fn avoid_fp_with_call_to_outside_var() {
|
||||
let outside: Result<i32, ()> = Ok(1);
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| outside.is_ok());
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| Result::is_ok(&outside));
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| std::result::Result::is_ok(&outside));
|
||||
}
|
||||
|
||||
fn avoid_fp_with_call_to_outside_var_mix_match_types() {
|
||||
let outside = Some(1);
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| outside.is_some());
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| Option::is_some(&outside));
|
||||
|
||||
let _ = vec![Ok(1), Err(2), Ok(3)]
|
||||
.into_iter()
|
||||
// should not lint
|
||||
.filter(|o| std::option::Option::is_some(&outside));
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user