Merge commit '26ac6aab023393c94edf42f38f6ad31196009643'

This commit is contained in:
Philipp Krones 2024-01-11 17:27:03 +01:00
parent beeaee9785
commit aa220c7ee7
157 changed files with 5167 additions and 820 deletions

View File

@ -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

View File

@ -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

View File

@ -24,7 +24,7 @@ jobs:
steps:
# Setup
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
# Run
- name: Build

View File

@ -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'

View File

@ -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

View File

@ -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 -->

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
```

View 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
```

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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.

View 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();

View File

@ -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 }

View File

@ -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,
}

View File

@ -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"
);
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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();

View File

@ -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 {

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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()

View File

@ -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`
}

View File

@ -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 {

View File

@ -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

View File

@ -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,
),
}
}

View 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,
);
}

View File

@ -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;

View File

@ -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]) => {

View 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,
);
}
}

View File

@ -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)));

View File

@ -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}")"#);

View File

@ -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(

View 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,
);
}
}

View File

@ -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

View File

@ -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,
);
}

View File

@ -74,6 +74,7 @@ pub(super) fn get_hint_if_single_char_arg(
match ch {
"'" => "\\'",
r"\" => "\\\\",
"\\\"" => "\"", // no need to escape `"` in `'"'`
_ => ch,
}
);

View File

@ -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`

View File

@ -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

View File

@ -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,
}

View File

@ -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.

View File

@ -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);
}

View File

@ -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(

View 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",
);
}
}
}
}

View File

@ -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),

View File

@ -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

View 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);
}

View File

@ -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

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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() {

View File

@ -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
);

View File

@ -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?");
}
}

View File

@ -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());

View File

@ -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(..) => (),

View File

@ -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);

View File

@ -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()
})
}

View File

@ -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);

View File

@ -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"]

View File

@ -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

View File

@ -0,0 +1 @@
pub-underscore-fields-behavior = "AllPubFields"

View File

@ -0,0 +1 @@
pub-underscore-fields-behavior = "PublicallyExported"

View File

@ -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

View File

@ -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

View 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>,
}
}

View File

@ -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

View File

@ -17,7 +17,7 @@ fn main() {
with_span!(
span
fn coverting() {
fn converting() {
let x = 0u32 as u64;
}
);

View File

@ -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
}
}

View File

@ -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

View File

@ -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`
}

View File

@ -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`
}

View File

@ -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

View File

@ -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() {

View File

@ -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.
}

View File

@ -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

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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

View 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() {}

View 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() {}

View 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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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 }
}

View File

@ -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 {

View File

@ -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

View File

@ -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));
}

View File

@ -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