Merge commit '9d6f41691ed9dbfaec2a2df2661c42451f2fe0d3' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2024-03-21 22:20:40 +01:00
parent bc0965e2ff
commit 0e62b18435
187 changed files with 4185 additions and 1050 deletions

View File

@ -26,6 +26,12 @@ env:
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
concurrency:
# For a given workflow, if we push to the same PR, cancel all previous builds on that PR.
# If the push is not attached to a PR, we will cancel all builds on the same branch.
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
cancel-in-progress: true
jobs:
base:
# NOTE: If you modify this job, make sure you copy the changes to clippy_bors.yml
@ -33,10 +39,6 @@ jobs:
steps:
# Setup
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
with:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v4

View File

@ -12,6 +12,11 @@ env:
NO_FMT_TEST: 1
CARGO_INCREMENTAL: 0
concurrency:
# For a given workflow, if we push to the same branch, cancel all previous builds on that branch.
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
cancel-in-progress: true
defaults:
run:
shell: bash
@ -21,10 +26,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
with:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v4
with:
@ -67,10 +68,6 @@ jobs:
# NOTE: If you modify this job, make sure you copy the changes to clippy.yml
steps:
# Setup
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
with:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v4
@ -131,10 +128,6 @@ jobs:
steps:
# Setup
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
with:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v4
@ -155,10 +148,6 @@ jobs:
steps:
# Setup
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
with:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v4
@ -211,10 +200,6 @@ jobs:
steps:
# Setup
- uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master
with:
github_token: "${{ secrets.github_token }}"
- name: Checkout
uses: actions/checkout@v4

View File

@ -6,11 +6,65 @@ document.
## Unreleased / Beta / In Rust Nightly
[a859e5cc...master](https://github.com/rust-lang/rust-clippy/compare/a859e5cc...master)
[66c29b97...master](https://github.com/rust-lang/rust-clippy/compare/66c29b97...master)
## Rust 1.77
Current stable, released 2024-03-18
[View all 93 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-12-16T18%3A20%3A00Z..2024-01-25T18%3A15%3A56Z+base%3Amaster)
### New Lints
* [`suspicious_open_options`]
[#11608](https://github.com/rust-lang/rust-clippy/pull/11608)
* [`option_as_ref_cloned`]
[#12051](https://github.com/rust-lang/rust-clippy/pull/12051)
* [`thread_local_initializer_can_be_made_const`]
[#12026](https://github.com/rust-lang/rust-clippy/pull/12026)
* [`str_split_at_newline`]
[#11987](https://github.com/rust-lang/rust-clippy/pull/11987)
* [`empty_enum_variants_with_brackets`]
[#12047](https://github.com/rust-lang/rust-clippy/pull/12047)
* [`manual_is_variant_and`]
[#11865](https://github.com/rust-lang/rust-clippy/pull/11865)
* [`pub_underscore_fields`]
[#10283](https://github.com/rust-lang/rust-clippy/pull/10283)
* [`eager_transmute`]
[#11981](https://github.com/rust-lang/rust-clippy/pull/11981)
* [`iter_filter_is_some`]
[#12004](https://github.com/rust-lang/rust/pull/12004)
* [`iter_filter_is_ok`]
[#12004](https://github.com/rust-lang/rust/pull/12004)
* [`result_filter_map`]
[#11869](https://github.com/rust-lang/rust-clippy/pull/11869)
* [`unconditional_recursion`]
[#11938](https://github.com/rust-lang/rust-clippy/pull/11938)
### Enhancements
* [`multiple_crate_versions`]: Added the [`allowed-duplicate-crates`] configuration to allow specific crates
[#12179](https://github.com/rust-lang/rust-clippy/pull/12179)
* [`single_call_fn`]: No longer ignores `#[allow]` attributes
[#12183](https://github.com/rust-lang/rust-clippy/pull/12183)
* [`read_zero_byte_vec`]: Updated the heuristics used for linting
[#11766](https://github.com/rust-lang/rust-clippy/pull/11766)
### ICE Fixes
* [`unit_arg`]: No longer crashes when checking for const in nested bodies
[#11977](https://github.com/rust-lang/rust-clippy/pull/11977)
* [`indexing_slicing`]: No longer crashes when the array index exceeds `usize`
[#12266](https://github.com/rust-lang/rust-clippy/pull/12266)
### Others
* Warnings about invalid fields inside `clippy.toml` files now include suggestions for existing fields
[#12180](https://github.com/rust-lang/rust-clippy/pull/12180)
## Rust 1.76
Current stable, released 2024-02-08
Released 2024-02-08
[View all 85 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-11-02T20%3A23%3A40Z..2023-12-16T13%3A11%3A08Z+base%3Amaster)
@ -5110,6 +5164,7 @@ Released 2018-09-13
[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def
@ -5156,6 +5211,7 @@ Released 2018-09-13
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
[`duplicated_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
[`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
@ -5279,6 +5335,7 @@ Released 2018-09-13
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
[`integer_division_remainder_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division_remainder_used
[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
[`into_iter_without_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_without_iter
@ -5376,6 +5433,7 @@ Released 2018-09-13
[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
[`manual_try_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold
[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
[`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default
[`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some
[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
@ -5813,6 +5871,7 @@ Released 2018-09-13
[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero
[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr
[`zero_repeat_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_repeat_side_effects
[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
[`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space
[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset

View File

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.78"
version = "0.1.79"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"
@ -24,7 +24,7 @@ path = "src/driver.rs"
clippy_config = { path = "clippy_config" }
clippy_lints = { path = "clippy_lints" }
rustc_tools_util = "0.3.0"
tempfile = { version = "3.2", optional = true }
tempfile = { version = "3.3", optional = true }
termize = "0.1"
color-print = "0.3.4"
anstream = "0.6.0"
@ -32,18 +32,18 @@ anstream = "0.6.0"
[dev-dependencies]
ui_test = "0.22.2"
tester = "0.9"
regex = "1.5"
regex = "1.5.5"
toml = "0.7.3"
walkdir = "2.3"
# This is used by the `collect-metadata` alias.
filetime = "0.2"
filetime = "0.2.9"
itertools = "0.12"
# UI test dependencies
clippy_utils = { path = "clippy_utils" }
if_chain = "1.0"
quote = "1.0"
serde = { version = "1.0.125", features = ["derive"] }
quote = "1.0.25"
serde = { version = "1.0.145", features = ["derive"] }
syn = { version = "2.0", features = ["full"] }
futures = "0.3"
parking_lot = "0.12"

View File

@ -52,7 +52,7 @@ if expr.span.from_expansion() {
### `Span.ctxt` method
The `span`'s context, given by the method [`ctxt`] and returning [SpanContext],
The `span`'s context, given by the method [`ctxt`] and returning [SyntaxContext],
represents if the span is from a macro expansion and, if it is, which
macro call expanded this span.
@ -155,4 +155,4 @@ if in_external_macro(cx.sess(), foo_span) {
[`from_expansion`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
[`in_external_macro`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html
[Span]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
[SpanContext]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/hygiene/struct.SyntaxContext.html
[SyntaxContext]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/hygiene/struct.SyntaxContext.html

View File

@ -602,6 +602,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
**Affected lints:**
* [`almost_complete_range`](https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range)
* [`approx_constant`](https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant)
* [`assigning_clones`](https://rust-lang.github.io/rust-clippy/master/index.html#assigning_clones)
* [`borrow_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr)
* [`cast_abs_to_unsigned`](https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned)
* [`checked_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions)

View File

@ -1,7 +1,10 @@
avoid-breaking-exported-api = false
# use the various `span_lint_*` methods instead, which also add a link to the docs
disallowed-methods = [
"rustc_lint::context::LintContext::span_lint",
"rustc_middle::ty::context::TyCtxt::node_span_lint"
]
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"
[[disallowed-methods]]
path = "rustc_middle::ty::context::TyCtxt::node_span_lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead"

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.78"
version = "0.1.79"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -262,7 +262,7 @@ define_Conf! {
///
/// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true),
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS.
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES.
///
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = ""]

View File

@ -23,6 +23,7 @@ msrv_aliases! {
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,63,0 { ASSIGNING_CLONES }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }
1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY }

View File

@ -689,6 +689,8 @@ fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
let mut seen_lints = HashSet::new();
let mut res: String = GENERATED_FILE_COMMENT.into();
res.push_str("#![allow(clippy::duplicated_attributes)]\n");
for lint in lints {
if seen_lints.insert(&lint.new_name) {
writeln!(res, "#![allow({})]", lint.new_name).unwrap();

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.78"
version = "0.1.79"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View File

@ -1,3 +1,4 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::HirNode;
use clippy_utils::sugg::Sugg;
@ -6,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Instance, Mutability};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::symbol::sym;
use rustc_span::ExpnKind;
@ -49,7 +50,19 @@ declare_clippy_lint! {
perf,
"assigning the result of cloning may be inefficient"
}
declare_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
pub struct AssigningClones {
msrv: Msrv,
}
impl AssigningClones {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
impl<'tcx> LateLintPass<'tcx> for AssigningClones {
fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx hir::Expr<'_>) {
@ -68,10 +81,12 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
return;
};
if is_ok_to_suggest(cx, lhs, &call) {
if is_ok_to_suggest(cx, lhs, &call, &self.msrv) {
suggest(cx, assign_expr, lhs, &call);
}
}
extract_msrv_attr!(LateContext);
}
// Try to resolve the call to `Clone::clone` or `ToOwned::to_owned`.
@ -135,7 +150,13 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<
// Return true if we find that the called method has a custom implementation and isn't derived or
// provided by default by the corresponding trait.
fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>) -> bool {
fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>, msrv: &Msrv) -> bool {
// For calls to .to_owned we suggest using .clone_into(), which was only stablilized in 1.63.
// If the current MSRV is below that, don't suggest the lint.
if !msrv.meets(msrvs::ASSIGNING_CLONES) && matches!(call.target, TargetTrait::ToOwned) {
return false;
}
// If the left-hand side is a local variable, it might be uninitialized at this point.
// In that case we do not want to suggest the lint.
if let Some(local) = path_to_local(lhs) {

View File

@ -0,0 +1,64 @@
use super::DUPLICATED_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::{Attribute, MetaItem};
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::EarlyContext;
use rustc_span::{sym, Span};
use std::collections::hash_map::Entry;
fn emit_if_duplicated(
cx: &EarlyContext<'_>,
attr: &MetaItem,
attr_paths: &mut FxHashMap<String, Span>,
complete_path: String,
) {
match attr_paths.entry(complete_path) {
Entry::Vacant(v) => {
v.insert(attr.span);
},
Entry::Occupied(o) => {
span_lint_and_then(cx, DUPLICATED_ATTRIBUTES, attr.span, "duplicated attribute", |diag| {
diag.span_note(*o.get(), "first defined here");
diag.span_help(attr.span, "remove this attribute");
});
},
}
}
fn check_duplicated_attr(
cx: &EarlyContext<'_>,
attr: &MetaItem,
attr_paths: &mut FxHashMap<String, Span>,
parent: &mut Vec<String>,
) {
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
return;
}
if let Some(value) = attr.value_str() {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}={value}", parent.join(":")));
} else if let Some(sub_attrs) = attr.meta_item_list() {
parent.push(name.as_str().to_string());
for sub_attr in sub_attrs {
if let Some(meta) = sub_attr.meta_item() {
check_duplicated_attr(cx, meta, attr_paths, parent);
}
}
parent.pop();
} else {
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.join(":")));
}
}
pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
let mut attr_paths = FxHashMap::default();
for attr in attrs {
if let Some(meta) = attr.meta() {
check_duplicated_attr(cx, &meta, &mut attr_paths, &mut Vec::new());
}
}
}

View File

@ -4,6 +4,7 @@ mod allow_attributes_without_reason;
mod blanket_clippy_restriction_lints;
mod deprecated_cfg_attr;
mod deprecated_semver;
mod duplicated_attributes;
mod empty_line_after;
mod inline_always;
mod maybe_misused_cfg;
@ -16,7 +17,7 @@ mod useless_attribute;
mod utils;
use clippy_config::msrvs::Msrv;
use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
use rustc_ast::{Attribute, Crate, MetaItemKind, NestedMetaItem};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, impl_lint_pass};
@ -489,6 +490,32 @@ declare_clippy_lint! {
"item has both inner and outer attributes"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for attributes that appear two or more times.
///
/// ### Why is this bad?
/// Repeating an attribute on the same item (or globally on the same crate)
/// is unnecessary and doesn't have an effect.
///
/// ### Example
/// ```no_run
/// #[allow(dead_code)]
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
///
/// Use instead:
/// ```no_run
/// #[allow(dead_code)]
/// fn foo() {}
/// ```
#[clippy::version = "1.78.0"]
pub DUPLICATED_ATTRIBUTES,
suspicious,
"duplicated attribute"
}
declare_lint_pass!(Attributes => [
ALLOW_ATTRIBUTES_WITHOUT_REASON,
INLINE_ALWAYS,
@ -568,12 +595,18 @@ impl_lint_pass!(EarlyAttributes => [
DEPRECATED_CLIPPY_CFG_ATTR,
UNNECESSARY_CLIPPY_CFG,
MIXED_ATTRIBUTES_STYLE,
DUPLICATED_ATTRIBUTES,
]);
impl EarlyLintPass for EarlyAttributes {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
duplicated_attributes::check(cx, &krate.attrs);
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
empty_line_after::check(cx, item);
mixed_attributes_style::check(cx, item);
duplicated_attributes::check(cx, &item.attrs);
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {

View File

@ -1,12 +1,12 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::in_constant;
use clippy_utils::source::snippet_opt;
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::ty::is_isize_or_usize;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_middle::ty::{self, FloatTy, Ty, UintTy};
use super::{utils, CAST_LOSSLESS};
@ -16,6 +16,7 @@ pub(super) fn check(
cast_op: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
cast_to_hir: &rustc_hir::Ty<'_>,
msrv: &Msrv,
) {
if !should_lint(cx, expr, cast_from, cast_to, msrv) {
@ -24,11 +25,11 @@ pub(super) fn check(
// The suggestion is to use a function call, so if the original expression
// has parens on the outside, they are no longer needed.
let mut applicability = Applicability::MachineApplicable;
let mut app = Applicability::MachineApplicable;
let opt = snippet_opt(cx, cast_op.span.source_callsite());
let sugg = opt.as_ref().map_or_else(
|| {
applicability = Applicability::HasPlaceholders;
app = Applicability::HasPlaceholders;
".."
},
|snip| {
@ -40,10 +41,27 @@ pub(super) fn check(
},
);
let message = if cast_from.is_bool() {
format!("casting `{cast_from:}` to `{cast_to:}` is more cleanly stated with `{cast_to:}::from(_)`")
// Display the type alias instead of the aliased type. Fixes #11285
//
// FIXME: Once `lazy_type_alias` is stabilized(?) we should use `rustc_middle` types instead,
// this will allow us to display the right type with `cast_from` as well.
let cast_to_fmt = if let TyKind::Path(QPath::Resolved(None, path)) = cast_to_hir.kind
// It's a bit annoying but the turbofish is optional for types. A type in an `as` cast
// shouldn't have these if they're primitives, which are the only things we deal with.
//
// This could be removed for performance if this check is determined to have a pretty major
// effect.
&& path.segments.iter().all(|segment| segment.args.is_none())
{
snippet_with_applicability(cx, cast_to_hir.span, "..", &mut app)
} else {
format!("casting `{cast_from}` to `{cast_to}` may become silently lossy if you later change the type")
cast_to.to_string().into()
};
let message = if cast_from.is_bool() {
format!("casting `{cast_from}` to `{cast_to_fmt}` is more cleanly stated with `{cast_to_fmt}::from(_)`")
} else {
format!("casting `{cast_from}` to `{cast_to_fmt}` may become silently lossy if you later change the type")
};
span_lint_and_sugg(
@ -52,14 +70,17 @@ pub(super) fn check(
expr.span,
&message,
"try",
format!("{cast_to}::from({sugg})"),
applicability,
format!("{cast_to_fmt}::from({sugg})"),
app,
);
}
fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool {
// Do not suggest using From in consts/statics until it is valid to do so (see #2267).
if in_constant(cx, expr.hir_id) {
//
// If destination is u128, do not lint because source type cannot be larger
// If source is bool, still lint due to the lint message differing (refers to style)
if in_constant(cx, expr.hir_id) || (!cast_from.is_bool() && matches!(cast_to.kind(), ty::Uint(UintTy::U128))) {
return false;
}

View File

@ -791,7 +791,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_nan_to_int::check(cx, expr, cast_expr, cast_from, cast_to);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir, &self.msrv);
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
}

View File

@ -1,12 +1,14 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::macros::{macro_backtrace, MacroCall};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_hir::{Expr, ExprKind, HirId, Node};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass;
use rustc_span::sym;
use rustc_span::{sym, Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -31,31 +33,38 @@ declare_clippy_lint! {
"`dbg!` macro is intended as a debugging tool"
}
#[derive(Copy, Clone)]
#[derive(Clone)]
pub struct DbgMacro {
allow_dbg_in_tests: bool,
/// Tracks the `dbg!` macro callsites that are already checked.
checked_dbg_call_site: FxHashSet<Span>,
/// Tracks the previous `SyntaxContext`, to avoid walking the same context chain.
prev_ctxt: SyntaxContext,
}
impl_lint_pass!(DbgMacro => [DBG_MACRO]);
impl DbgMacro {
pub fn new(allow_dbg_in_tests: bool) -> Self {
DbgMacro { allow_dbg_in_tests }
DbgMacro {
allow_dbg_in_tests,
checked_dbg_call_site: FxHashSet::default(),
prev_ctxt: SyntaxContext::root(),
}
}
}
impl LateLintPass<'_> for DbgMacro {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
return;
};
if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
let cur_syntax_ctxt = expr.span.ctxt();
if cur_syntax_ctxt != self.prev_ctxt &&
let Some(macro_call) = first_dbg_macro_in_expansion(cx, expr.span) &&
!in_external_macro(cx.sess(), macro_call.span) &&
self.checked_dbg_call_site.insert(macro_call.span) &&
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
if self.allow_dbg_in_tests
&& (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id))
{
return;
}
!(self.allow_dbg_in_tests && is_in_test(cx, expr.hir_id))
{
let mut applicability = Applicability::MachineApplicable;
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
@ -101,6 +110,8 @@ impl LateLintPass<'_> for DbgMacro {
_ => return,
};
self.prev_ctxt = cur_syntax_ctxt;
span_lint_and_sugg(
cx,
DBG_MACRO,
@ -112,4 +123,16 @@ impl LateLintPass<'_> for DbgMacro {
);
}
}
fn check_crate_post(&mut self, _: &LateContext<'_>) {
self.checked_dbg_call_site = FxHashSet::default();
}
}
fn is_in_test(cx: &LateContext<'_>, hir_id: HirId) -> bool {
is_in_test_function(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id)
}
fn first_dbg_macro_in_expansion(cx: &LateContext<'_>, span: Span) -> Option<MacroCall> {
macro_backtrace(span).find(|mc| cx.tcx.is_diagnostic_item(sym::dbg_macro, mc.def_id))
}

View File

@ -54,6 +54,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::attrs::DEPRECATED_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_SEMVER_INFO,
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
@ -235,6 +236,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO,
crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
crate::int_plus_one::INT_PLUS_ONE_INFO,
crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO,
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
@ -310,6 +312,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
crate::match_result_ok::MATCH_RESULT_OK_INFO,
@ -353,6 +356,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::CLONE_ON_COPY_INFO,
crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
crate::methods::CONST_IS_EMPTY_INFO,
crate::methods::DRAIN_COLLECT_INFO,
crate::methods::ERR_EXPECT_INFO,
crate::methods::EXPECT_FUN_CALL_INFO,
@ -750,5 +754,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::write::WRITE_LITERAL_INFO,
crate::write::WRITE_WITH_NEWLINE_INFO,
crate::zero_div_zero::ZERO_DIVIDED_BY_ZERO_INFO,
crate::zero_repeat_side_effects::ZERO_REPEAT_SIDE_EFFECTS_INFO,
crate::zero_sized_map_values::ZERO_SIZED_MAP_VALUES_INFO,
];

View File

@ -8,7 +8,14 @@ use url::Url;
use crate::doc::DOC_MARKDOWN;
pub fn check(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span, code_level: isize) {
pub fn check(
cx: &LateContext<'_>,
valid_idents: &FxHashSet<String>,
text: &str,
span: Span,
code_level: isize,
blockquote_level: isize,
) {
for orig_word in text.split(|c: char| c.is_whitespace() || c == '\'') {
// Trim punctuation as in `some comment (see foo::bar).`
// ^^
@ -46,11 +53,11 @@ pub fn check(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str,
span.parent(),
);
check_word(cx, word, span, code_level);
check_word(cx, word, span, code_level, blockquote_level);
}
}
fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize) {
fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, blockquote_level: isize) {
/// Checks if a string is upper-camel-case, i.e., starts with an uppercase and
/// contains at least two uppercase letters (`Clippy` is ok) and one lower-case
/// letter (`NASA` is ok).
@ -97,7 +104,9 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize) {
}
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
if code_level > 0 || (has_underscore(word) && has_hyphen(word)) {
//
// We also assume that backticks are not necessary if inside a quote. (Issue #10262)
if code_level > 0 || blockquote_level > 0 || (has_underscore(word) && has_hyphen(word)) {
return;
}

View File

@ -7,14 +7,14 @@ use clippy_utils::{is_entrypoint_fn, method_chain_args};
use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
};
use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::Tag::{BlockQuote, CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{AnonConst, Expr};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
@ -538,7 +538,16 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
suspicious_doc_comments::check(cx, attrs);
let (fragments, _) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), true);
let (fragments, _) = attrs_to_doc_fragments(
attrs.iter().filter_map(|attr| {
if in_external_macro(cx.sess(), attr.span) {
None
} else {
Some((attr, None))
}
}),
true,
);
let mut doc = fragments.iter().fold(String::new(), |mut acc, fragment| {
add_doc_fragment(&mut acc, fragment);
acc
@ -602,6 +611,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut text_to_check: Vec<(CowStr<'_>, Range<usize>, isize)> = Vec::new();
let mut paragraph_range = 0..0;
let mut code_level = 0;
let mut blockquote_level = 0;
for (event, range) in events {
match event {
@ -610,8 +620,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
code_level += 1;
} else if tag.starts_with("</code") {
code_level -= 1;
} else if tag.starts_with("<blockquote") || tag.starts_with("<q") {
blockquote_level += 1;
} else if tag.starts_with("</blockquote") || tag.starts_with("</q") {
blockquote_level -= 1;
}
},
Start(BlockQuote) => blockquote_level += 1,
End(BlockQuote) => blockquote_level -= 1,
Start(CodeBlock(ref kind)) => {
in_code = true;
if let CodeBlockKind::Fenced(lang) = kind {
@ -663,7 +679,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
} else {
for (text, range, assoc_code_level) in text_to_check {
if let Some(span) = fragments.span(cx, range) {
markdown::check(cx, valid_idents, &text, span, assoc_code_level);
markdown::check(cx, valid_idents, &text, span, assoc_code_level, blockquote_level);
}
}
}

View File

@ -49,24 +49,22 @@ declare_clippy_lint! {
declare_lint_pass!(ElseIfWithoutElse => [ELSE_IF_WITHOUT_ELSE]);
impl EarlyLintPass for ElseIfWithoutElse {
fn check_expr(&mut self, cx: &EarlyContext<'_>, mut item: &Expr) {
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
if in_external_macro(cx.sess(), item.span) {
return;
}
while let ExprKind::If(_, _, Some(ref els)) = item.kind {
if let ExprKind::If(_, _, None) = els.kind {
span_lint_and_help(
cx,
ELSE_IF_WITHOUT_ELSE,
els.span,
"`if` expression with an `else if`, but without a final `else`",
None,
"add an `else` block here",
);
}
item = els;
if let ExprKind::If(_, _, Some(ref els)) = item.kind
&& let ExprKind::If(_, _, None) = els.kind
{
span_lint_and_help(
cx,
ELSE_IF_WITHOUT_ELSE,
els.span,
"`if` expression with an `else if`, but without a final `else`",
None,
"add an `else` block here",
);
}
}
}

View File

@ -358,7 +358,7 @@ struct InsertSearcher<'cx, 'tcx> {
can_use_entry: bool,
/// Whether this expression is the final expression in this code path. This may be a statement.
in_tail_pos: bool,
// Is this expression a single insert. A slightly better suggestion can be made in this case.
/// Is this expression a single insert. A slightly better suggestion can be made in this case.
is_single_insert: bool,
/// If the visitor has seen the map being used.
is_map_used: bool,
@ -431,6 +431,9 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
self.is_single_insert = false;
self.visit_expr(e);
}
if let Some(els) = &l.els {
self.visit_block(els);
}
},
StmtKind::Item(_) => {
self.allow_insert_closure &= !self.in_tail_pos;

View File

@ -250,7 +250,7 @@ declare_clippy_lint! {
///
/// ### Why is this bad?
/// A `Result` is at least as large as the `Err`-variant. While we
/// expect that variant to be seldomly used, the compiler needs to reserve
/// expect that variant to be seldom used, the compiler needs to reserve
/// and move that much memory every single time.
/// Furthermore, errors are often simply passed up the call-stack, making
/// use of the `?`-operator and its type-conversion mechanics. If the

View File

@ -0,0 +1,50 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::BinOpKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self};
use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of division (/) and remainder (%) operations
/// when performed on any integer types using the default Div and Rem trait implementations.
///
/// ### Why is this bad?
/// In cryptographic contexts, division can result in timing sidechannel vulnerabilities,
/// and needs to be replaced with constant-time code instead (e.g. Barrett reduction).
///
/// ### Example
/// ```no_run
/// let my_div = 10 / 2;
/// ```
/// Use instead:
/// ```no_run
/// let my_div = 10 >> 1;
/// ```
#[clippy::version = "1.78.0"]
pub INTEGER_DIVISION_REMAINDER_USED,
restriction,
"use of disallowed default division and remainder operations"
}
declare_lint_pass!(IntegerDivisionRemainderUsed => [INTEGER_DIVISION_REMAINDER_USED]);
impl LateLintPass<'_> for IntegerDivisionRemainderUsed {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind
&& let BinOpKind::Div | BinOpKind::Rem = op.node
&& let lhs_ty = cx.typeck_results().expr_ty(lhs)
&& let rhs_ty = cx.typeck_results().expr_ty(rhs)
&& let ty::Int(_) | ty::Uint(_) = lhs_ty.peel_refs().kind()
&& let ty::Int(_) | ty::Uint(_) = rhs_ty.peel_refs().kind()
{
span_lint(
cx,
INTEGER_DIVISION_REMAINDER_USED,
expr.span.source_callsite(),
&format!("use of {} has been disallowed in this context", op.node.as_str()),
);
}
}
}

View File

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::path_to_local_id;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
@ -122,9 +122,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"),
);
span_lint_and_then(
span_lint_hir_and_then(
cx,
USELESS_LET_IF_SEQ,
local.hir_id,
span,
"`if _ { .. } else { .. }` is an expression",
|diag| {

View File

@ -172,6 +172,7 @@ mod init_numbered_fields;
mod inline_fn_without_body;
mod instant_subtraction;
mod int_plus_one;
mod integer_division_remainder_used;
mod invalid_upcast_comparisons;
mod item_name_repetitions;
mod items_after_statements;
@ -211,6 +212,7 @@ mod manual_retain;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod manual_unwrap_or_default;
mod map_unit_fn;
mod match_result_ok;
mod matches;
@ -373,6 +375,7 @@ mod visibility;
mod wildcard_imports;
mod write;
mod zero_div_zero;
mod zero_repeat_side_effects;
mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints`
@ -1119,7 +1122,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
store.register_late_pass(|_| Box::new(assigning_clones::AssigningClones));
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(msrv())));
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -273,7 +273,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
}
return false; // no need to walk further *on the variable*
},
Res::Def(DefKind::Static{..} | DefKind::Const, ..) => {
Res::Def(DefKind::Static { .. } | DefKind::Const, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,

View File

@ -255,9 +255,7 @@ fn never_loop_expr<'tcx>(
InlineAsmOperand::Const { .. } | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => {
NeverLoopResult::Normal
},
InlineAsmOperand::Label { block } => {
never_loop_block(cx, block, local_labels, main_loop_id)
}
InlineAsmOperand::Label { block } => never_loop_block(cx, block, local_labels, main_loop_id),
})),
ExprKind::OffsetOf(_, _)
| ExprKind::Yield(_, _)

View File

@ -1,62 +1,41 @@
use super::UNUSED_ENUMERATE_INDEX;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::{pat_is_wild, sugg};
use clippy_utils::{match_def_path, pat_is_wild, sugg};
use rustc_hir::def::DefKind;
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
/// Checks for the `UNUSED_ENUMERATE_INDEX` lint.
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
let PatKind::Tuple([index, elem], _) = pat.kind else {
return;
};
let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind else {
return;
};
let ty = cx.typeck_results().expr_ty(arg);
if !pat_is_wild(cx, &index.kind, body) {
return;
///
/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`.
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) {
if let PatKind::Tuple([index, elem], _) = pat.kind
&& let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind
&& let ty = cx.typeck_results().expr_ty(arg)
&& pat_is_wild(cx, &index.kind, body)
&& let ty::Adt(base, _) = *ty.kind()
&& match_def_path(cx, base.did(), &clippy_utils::paths::CORE_ITER_ENUMERATE_STRUCT)
&& let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id)
&& match_def_path(cx, call_id, &clippy_utils::paths::CORE_ITER_ENUMERATE_METHOD)
{
span_lint_and_then(
cx,
UNUSED_ENUMERATE_INDEX,
arg.span,
"you seem to use `.enumerate()` and immediately discard the index",
|diag| {
let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter");
multispan_sugg(
diag,
"remove the `.enumerate()` call",
vec![
(pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()),
],
);
},
);
}
let name = match *ty.kind() {
ty::Adt(base, _substs) => cx.tcx.def_path_str(base.did()),
_ => return,
};
if name != "std::iter::Enumerate" && name != "core::iter::Enumerate" {
return;
}
let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) else {
return;
};
let call_name = cx.tcx.def_path_str(call_id);
if call_name != "std::iter::Iterator::enumerate" && call_name != "core::iter::Iterator::enumerate" {
return;
}
span_lint_and_then(
cx,
UNUSED_ENUMERATE_INDEX,
arg.span,
"you seem to use `.enumerate()` and immediately discard the index",
|diag| {
let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter");
multispan_sugg(
diag,
"remove the `.enumerate()` call",
vec![
(pat.span, snippet(cx, elem.span, "..").into_owned()),
(arg.span, base_iter.to_string()),
],
);
},
);
}

View File

@ -101,7 +101,7 @@ impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
Res::Local(hir_id) => {
self.ids.insert(hir_id);
},
Res::Def(DefKind::Static{..}, def_id) => {
Res::Def(DefKind::Static { .. }, def_id) => {
let mutable = self.cx.tcx.is_mutable_static(def_id);
self.def_ids.insert(def_id, mutable);
},

View File

@ -2,7 +2,7 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq};
use clippy_utils::{match_def_path, paths, SpanlessEq};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
@ -69,17 +69,16 @@ impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]);
impl<'tcx> LateLintPass<'tcx> for ManualRetain {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let Some(parent_expr) = get_parent_expr(cx, expr)
&& let Assign(left_expr, collect_expr, _) = &parent_expr.kind
if let Assign(left_expr, collect_expr, _) = &expr.kind
&& let hir::ExprKind::MethodCall(seg, ..) = &collect_expr.kind
&& seg.args.is_none()
&& let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
{
check_into_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_to_owned(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_into_iter(cx, left_expr, target_expr, expr.span, &self.msrv);
check_iter(cx, left_expr, target_expr, expr.span, &self.msrv);
check_to_owned(cx, left_expr, target_expr, expr.span, &self.msrv);
}
}

View File

@ -0,0 +1,181 @@
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_default_equivalent;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
declare_clippy_lint! {
/// ### What it does
/// Checks if a `match` or `if let` expression can be simplified using
/// `.unwrap_or_default()`.
///
/// ### Why is this bad?
/// It can be done in one call with `.unwrap_or_default()`.
///
/// ### Example
/// ```no_run
/// let x: Option<String> = Some(String::new());
/// let y: String = match x {
/// Some(v) => v,
/// None => String::new(),
/// };
///
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = if let Some(v) = x {
/// v
/// } else {
/// Vec::new()
/// };
/// ```
/// Use instead:
/// ```no_run
/// let x: Option<String> = Some(String::new());
/// let y: String = x.unwrap_or_default();
///
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = x.unwrap_or_default();
/// ```
#[clippy::version = "1.78.0"]
pub MANUAL_UNWRAP_OR_DEFAULT,
suspicious,
"check if a `match` or `if let` can be simplified with `unwrap_or_default`"
}
declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]);
fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
{
let mut bindings = Vec::new();
pat.each_binding(|_, id, _, _| bindings.push(id));
if let &[id] = bindings.as_slice() {
Some(id)
} else {
None
}
} else {
None
}
}
fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if let PatKind::Path(QPath::Resolved(_, path)) = arm.pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id)
{
Some(arm.body)
} else if let PatKind::Wild = arm.pat.kind {
// We consider that the `Some` check will filter it out if it's not right.
Some(arm.body)
} else {
None
}
}
fn get_some_and_none_bodies<'tcx>(
cx: &LateContext<'tcx>,
arm1: &'tcx Arm<'tcx>,
arm2: &'tcx Arm<'tcx>,
) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> {
if let Some(binding_id) = get_some(cx, arm1.pat)
&& let Some(body_none) = get_none(cx, arm2)
{
Some(((arm1.body, binding_id), body_none))
} else if let Some(binding_id) = get_some(cx, arm2.pat)
&& let Some(body_none) = get_none(cx, arm1)
{
Some(((arm2.body, binding_id), body_none))
} else {
None
}
}
fn handle_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
let ExprKind::Match(match_expr, [arm1, arm2], MatchSource::Normal | MatchSource::ForLoopDesugar) = expr.kind else {
return false;
};
// We don't want conditions on the arms to simplify things.
if arm1.guard.is_none()
&& arm2.guard.is_none()
// We check that the returned type implements the `Default` trait.
&& let match_ty = cx.typeck_results().expr_ty(expr)
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
&& implements_trait(cx, match_ty, default_trait_id, &[])
// We now get the bodies for both the `Some` and `None` arms.
&& let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2)
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
&& let ExprKind::Path(QPath::Resolved(_, path)) = body_some.peel_blocks().kind
&& let Res::Local(local_id) = path.res
&& local_id == binding_id
// We now check the `None` arm is calling a method equivalent to `Default::default`.
&& let body_none = body_none.peel_blocks()
&& is_default_equivalent(cx, body_none)
&& let Some(match_expr_snippet) = snippet_opt(cx, match_expr.span)
{
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
"match can be simplified with `.unwrap_or_default()`",
"replace it with",
format!("{match_expr_snippet}.unwrap_or_default()"),
Applicability::MachineApplicable,
);
}
true
}
fn handle_if_let<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::If(cond, if_block, Some(else_expr)) = expr.kind
&& let ExprKind::Let(let_) = cond.kind
&& let ExprKind::Block(_, _) = else_expr.kind
// We check that the returned type implements the `Default` trait.
&& let match_ty = cx.typeck_results().expr_ty(expr)
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
&& implements_trait(cx, match_ty, default_trait_id, &[])
&& let Some(binding_id) = get_some(cx, let_.pat)
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
&& let ExprKind::Path(QPath::Resolved(_, path)) = if_block.peel_blocks().kind
&& let Res::Local(local_id) = path.res
&& local_id == binding_id
// We now check the `None` arm is calling a method equivalent to `Default::default`.
&& let body_else = else_expr.peel_blocks()
&& is_default_equivalent(cx, body_else)
&& let Some(if_let_expr_snippet) = snippet_opt(cx, let_.init.span)
{
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
"if let can be simplified with `.unwrap_or_default()`",
"replace it with",
format!("{if_let_expr_snippet}.unwrap_or_default()"),
Applicability::MachineApplicable,
);
}
}
impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if expr.span.from_expansion() {
return;
}
if !handle_match(cx, expr) {
handle_if_let(cx, expr);
}
}
}

View File

@ -55,23 +55,15 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
};
let ty = cx.typeck_results().expr_ty(ex);
if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
check_single_pattern(cx, ex, arms, expr, els);
check_opt_like(cx, ex, arms, expr, ty, els);
if (*ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id)) &&
(check_single_pattern(arms) || check_opt_like(cx, arms, ty)) {
report_single_pattern(cx, ex, arms, expr, els);
}
}
}
fn check_single_pattern(
cx: &LateContext<'_>,
ex: &Expr<'_>,
arms: &[Arm<'_>],
expr: &Expr<'_>,
els: Option<&Expr<'_>>,
) {
if is_wild(arms[1].pat) {
report_single_pattern(cx, ex, arms, expr, els);
}
fn check_single_pattern(arms: &[Arm<'_>]) -> bool {
is_wild(arms[1].pat)
}
fn report_single_pattern(
@ -140,19 +132,10 @@ fn report_single_pattern(
span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app);
}
fn check_opt_like<'a>(
cx: &LateContext<'a>,
ex: &Expr<'_>,
arms: &[Arm<'_>],
expr: &Expr<'_>,
ty: Ty<'a>,
els: Option<&Expr<'_>>,
) {
fn check_opt_like<'a>(cx: &LateContext<'a>, arms: &[Arm<'_>], ty: Ty<'a>) -> bool {
// We don't want to lint if the second arm contains an enum which could
// have more variants in the future.
if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) {
report_single_pattern(cx, ex, arms, expr, els);
}
form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat)
}
/// Returns `true` if all of the types in the pattern are enums which we know

View File

@ -91,7 +91,7 @@ pub(super) fn check<'tcx>(
},
hir::ExprKind::Path(ref p) => matches!(
cx.qpath_res(p, arg.hir_id),
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static{..}, _)
hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static { .. }, _)
),
_ => false,
}

View File

@ -0,0 +1,49 @@
use clippy_utils::consts::constant_is_empty;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{find_binding_init, path_to_local};
use rustc_hir::{Expr, HirId};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::sym;
use super::CONST_IS_EMPTY;
/// Expression whose initialization depend on a constant conditioned by a `#[cfg(…)]` directive will
/// not trigger the lint.
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
return;
}
let init_expr = expr_or_init(cx, receiver);
if !receiver.span.eq_ctxt(init_expr.span) {
return;
}
if let Some(init_is_empty) = constant_is_empty(cx, init_expr) {
span_lint(
cx,
CONST_IS_EMPTY,
expr.span,
&format!("this expression always evaluates to {init_is_empty:?}"),
);
}
}
fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool {
cx.tcx
.hir()
.parent_id_iter(id)
.any(|id| cx.tcx.hir().attrs(id).iter().any(|attr| attr.has_name(sym::cfg)))
}
/// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization
/// value depends on a `#[cfg(…)]` directive.
fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
while let Some(init) = path_to_local(expr)
.and_then(|id| find_binding_init(cx, id))
.filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
.filter(|init| !is_under_cfg(cx, init.hir_id))
{
expr = init;
}
expr
}

View File

@ -1,10 +1,10 @@
use super::utils::derefs_to_slice;
use crate::methods::iter_nth_zero;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::get_type_diagnostic_name;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use rustc_span::Span;
use super::ITER_NTH;
@ -12,28 +12,33 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
iter_recv: &'tcx hir::Expr<'tcx>,
nth_recv: &hir::Expr<'_>,
nth_arg: &hir::Expr<'_>,
is_mut: bool,
) {
let mut_str = if is_mut { "_mut" } else { "" };
let caller_type = if derefs_to_slice(cx, iter_recv, cx.typeck_results().expr_ty(iter_recv)).is_some() {
"slice"
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::Vec) {
"`Vec`"
} else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(iter_recv), sym::VecDeque) {
"`VecDeque`"
} else {
iter_nth_zero::check(cx, expr, nth_recv, nth_arg);
return; // caller is not a type that we want to lint
iter_method: &str,
iter_span: Span,
nth_span: Span,
) -> bool {
let caller_type = match get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(iter_recv).peel_refs()) {
Some(sym::Vec) => "`Vec`",
Some(sym::VecDeque) => "`VecDeque`",
_ if cx.typeck_results().expr_ty_adjusted(iter_recv).peel_refs().is_slice() => "slice",
// caller is not a type that we want to lint
_ => return false,
};
span_lint_and_help(
span_lint_and_then(
cx,
ITER_NTH,
expr.span,
&format!("called `.iter{mut_str}().nth()` on a {caller_type}"),
None,
&format!("calling `.get{mut_str}()` is both faster and more readable"),
&format!("called `.{iter_method}().nth()` on a {caller_type}"),
|diag| {
let get_method = if iter_method == "iter_mut" { "get_mut" } else { "get" };
diag.span_suggestion_verbose(
iter_span.to(nth_span),
format!("`{get_method}` is equivalent but more concise"),
get_method,
Applicability::MachineApplicable,
);
},
);
true
}

View File

@ -86,8 +86,11 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_
}
}
},
hir::ExprKind::Call(call, [_]) => {
if let hir::ExprKind::Path(qpath) = call.kind {
hir::ExprKind::Call(call, args) => {
if let hir::ExprKind::Path(qpath) = call.kind
&& let [arg] = args
&& ident_eq(name, arg)
{
handle_path(cx, call, &qpath, e, recv);
}
},
@ -118,7 +121,9 @@ fn handle_path(
if let ty::Adt(_, args) = cx.typeck_results().expr_ty(recv).kind()
&& let args = args.as_slice()
&& let Some(ty) = args.iter().find_map(|generic_arg| generic_arg.as_type())
&& ty.is_ref()
&& let ty::Ref(_, ty, Mutability::Not) = ty.kind()
&& let ty::FnDef(_, lst) = cx.typeck_results().expr_ty(arg).kind()
&& lst.iter().all(|l| l.as_type() == Some(*ty))
{
lint_path(cx, e.span, recv.span, is_copy(cx, ty.peel_refs()));
}

View File

@ -36,6 +36,7 @@ mod inefficient_to_string;
mod inspect_for_each;
mod into_iter_on_ref;
mod is_digit_ascii_radix;
mod is_empty;
mod iter_cloned_collect;
mod iter_count;
mod iter_filter;
@ -118,6 +119,7 @@ mod unnecessary_literal_unwrap;
mod unnecessary_result_map_or_else;
mod unnecessary_sort_by;
mod unnecessary_to_owned;
mod unused_enumerate_index;
mod unwrap_expect_used;
mod useless_asref;
mod utils;
@ -1235,12 +1237,11 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.iter().nth()` (and the related
/// `.iter_mut().nth()`) on standard library types with *O*(1) element access.
/// Checks for usage of `.iter().nth()`/`.iter_mut().nth()` on standard library types that have
/// equivalent `.get()`/`.get_mut()` methods.
///
/// ### Why is this bad?
/// `.get()` and `.get_mut()` are more efficient and more
/// readable.
/// `.get()` and `.get_mut()` are equivalent but more concise.
///
/// ### Example
/// ```no_run
@ -1256,7 +1257,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "pre 1.29.0"]
pub ITER_NTH,
perf,
style,
"using `.iter().nth()` on a standard library type with O(1) element access"
}
@ -2848,7 +2849,7 @@ declare_clippy_lint! {
/// the file is created from scratch, or ensure `truncate` is
/// called so that the truncation behaviour is explicit. `truncate(true)`
/// will ensure the file is entirely overwritten with new data, whereas
/// `truncate(false)` will explicitely keep the default behavior.
/// `truncate(false)` will explicitly keep the default behavior.
///
/// ### Example
/// ```rust,no_run
@ -2862,7 +2863,7 @@ declare_clippy_lint! {
///
/// OpenOptions::new().create(true).truncate(true);
/// ```
#[clippy::version = "1.75.0"]
#[clippy::version = "1.77.0"]
pub SUSPICIOUS_OPEN_OPTIONS,
suspicious,
"suspicious combination of options for opening a file"
@ -3182,8 +3183,8 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
///
/// Checks an argument of `seek` method of `Seek` trait
/// and if it start seek from `SeekFrom::Current(0)`, suggests `stream_position` instead.
/// Checks if the `seek` method of the `Seek` trait is called with `SeekFrom::Current(0)`,
/// and if it is, suggests using `stream_position` instead.
///
/// ### Why is this bad?
///
@ -3590,7 +3591,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.73.0"]
pub READONLY_WRITE_LOCK,
nursery,
perf,
"acquiring a write lock when a read lock would work"
}
@ -3816,7 +3817,7 @@ declare_clippy_lint! {
/// ```no_run
/// let _ = std::iter::empty::<Result<i32, ()>>().flatten();
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub RESULT_FILTER_MAP,
complexity,
"filtering `Result` for `Ok` then force-unwrapping, which can be one type-safe operation"
@ -3825,7 +3826,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.filter(Option::is_some)` that may be replaced with a `.flatten()` call.
/// This lint will require additional changes to the follow-up calls as it appects the type.
/// This lint will require additional changes to the follow-up calls as it affects the type.
///
/// ### Why is this bad?
/// This pattern is often followed by manual unwrapping of the `Option`. The simplification
@ -3842,7 +3843,7 @@ declare_clippy_lint! {
/// // example code which does not raise clippy warning
/// vec![Some(1)].into_iter().flatten();
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub ITER_FILTER_IS_SOME,
pedantic,
"filtering an iterator over `Option`s for `Some` can be achieved with `flatten`"
@ -3851,7 +3852,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.filter(Result::is_ok)` that may be replaced with a `.flatten()` call.
/// This lint will require additional changes to the follow-up calls as it appects the type.
/// This lint will require additional changes to the follow-up calls as it affects the type.
///
/// ### Why is this bad?
/// This pattern is often followed by manual unwrapping of `Result`. The simplification
@ -3868,7 +3869,7 @@ declare_clippy_lint! {
/// // example code which does not raise clippy warning
/// vec![Ok::<i32, String>(1)].into_iter().flatten();
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub ITER_FILTER_IS_OK,
pedantic,
"filtering an iterator over `Result`s for `Ok` can be achieved with `flatten`"
@ -3895,7 +3896,7 @@ declare_clippy_lint! {
/// option.is_some_and(|a| a > 10);
/// result.is_ok_and(|a| a > 10);
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.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)`"
@ -3925,7 +3926,7 @@ declare_clippy_lint! {
/// `"\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"]
#[clippy::version = "1.77.0"]
pub STR_SPLIT_AT_NEWLINE,
pedantic,
"splitting a trimmed string at hard-coded newlines"
@ -4044,6 +4045,31 @@ declare_clippy_lint! {
"calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`"
}
declare_clippy_lint! {
/// ### What it does
/// It identifies calls to `.is_empty()` on constant values.
///
/// ### Why is this bad?
/// String literals and constant values are known at compile time. Checking if they
/// are empty will always return the same value. This might not be the intention of
/// the expression.
///
/// ### Example
/// ```no_run
/// let value = "";
/// if value.is_empty() {
/// println!("the string is empty");
/// }
/// ```
/// Use instead:
/// ```no_run
/// println!("the string is empty");
/// ```
#[clippy::version = "1.78.0"]
pub CONST_IS_EMPTY,
suspicious,
"is_empty() called on strings known at compile time"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@ -4092,6 +4118,7 @@ impl_lint_pass!(Methods => [
CLONE_ON_COPY,
CLONE_ON_REF_PTR,
COLLAPSIBLE_STR_REPLACE,
CONST_IS_EMPTY,
ITER_OVEREAGER_CLONED,
CLONED_INSTEAD_OF_COPIED,
FLAT_MAP_OPTION,
@ -4403,6 +4430,7 @@ impl Methods {
zst_offset::check(cx, expr, recv);
},
("all", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
if let Some(("cloned", recv2, [], _, _)) = method_call(recv) {
iter_overeager_cloned::check(
cx,
@ -4421,23 +4449,26 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
}
},
("any", [arg]) => match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
Some(("chars", recv, _, _, _))
if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
},
_ => {},
("any", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
match method_call(recv) {
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
Some(("chars", recv, _, _, _))
if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
},
_ => {},
}
},
("arg", [arg]) => {
suspicious_command_arg_space::check(cx, recv, arg, span);
@ -4445,7 +4476,7 @@ impl Methods {
("as_deref" | "as_deref_mut", []) => {
needless_option_as_deref::check(cx, expr, recv, name);
},
("as_bytes" | "is_empty", []) => {
("as_bytes", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
@ -4570,14 +4601,17 @@ impl Methods {
}
},
("filter_map", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
filter_map_bool_then::check(cx, expr, arg, call_span);
filter_map_identity::check(cx, expr, arg, span);
},
("find_map", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
},
("flat_map", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
flat_map_identity::check(cx, expr, arg, span);
flat_map_option::check(cx, expr, arg, span);
},
@ -4599,17 +4633,20 @@ impl Methods {
manual_try_fold::check(cx, expr, init, acc, call_span, &self.msrv);
unnecessary_fold::check(cx, expr, init, acc, span);
},
("for_each", [arg]) => match method_call(recv) {
Some(("inspect", _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
_ => {},
("for_each", [arg]) => {
unused_enumerate_index::check(cx, expr, recv, arg);
match method_call(recv) {
Some(("inspect", _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(
cx,
expr,
recv,
recv2,
iter_overeager_cloned::Op::NeedlessMove(arg),
false,
),
_ => {},
}
},
("get", [arg]) => {
get_first::check(cx, expr, recv, arg);
@ -4619,6 +4656,12 @@ impl Methods {
("hash", [arg]) => {
unit_hash::check(cx, expr, recv, arg);
},
("is_empty", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
is_empty::check(cx, expr, recv);
},
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),
@ -4650,6 +4693,7 @@ impl Methods {
},
(name @ ("map" | "map_err"), [m_arg]) => {
if name == "map" {
unused_enumerate_index::check(cx, expr, recv, m_arg);
map_clone::check(cx, expr, recv, m_arg, &self.msrv);
match method_call(recv) {
Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => {
@ -4723,8 +4767,11 @@ impl Methods {
iter_overeager_cloned::Op::LaterCloned,
false,
),
Some(("iter", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
Some(("iter_mut", recv2, [], _, _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
Some((iter_method @ ("iter" | "iter_mut"), iter_recv, [], iter_span, _)) => {
if !iter_nth::check(cx, expr, iter_recv, iter_method, iter_span, span) {
iter_nth_zero::check(cx, expr, recv, n_arg);
}
},
_ => iter_nth_zero::check(cx, expr, recv, n_arg),
},
("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),

View File

@ -25,6 +25,7 @@ pub(super) fn check<'tcx>(
&& param1 == param2.as_str()
{
span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
return;
}
if SpanlessEq::new(cx).eq_expr(arg1, arg2) {

View File

@ -0,0 +1,135 @@
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_hir_and_then};
use clippy_utils::paths::{CORE_ITER_ENUMERATE_METHOD, CORE_ITER_ENUMERATE_STRUCT};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::{expr_or_init, is_trait_method, match_def_path, pat_is_wild};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::AdtDef;
use rustc_span::{sym, Span};
use crate::loops::UNUSED_ENUMERATE_INDEX;
/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops.
///
/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is
/// checked:
/// ```ignore
/// for (_, x) in some_iter.enumerate() {
/// // Index is ignored
/// }
/// ```
///
/// This `check` function checks for chained method calls constructs where we can detect that the
/// index is unused. Currently, this checks only for the following patterns:
/// ```ignore
/// some_iter.enumerate().map_function(|(_, x)| ..)
/// let x = some_iter.enumerate();
/// x.map_function(|(_, x)| ..)
/// ```
/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or
/// `map`.
///
/// # Preconditions
/// This function must be called not on the `enumerate` call expression itself, but on any of the
/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and
/// that the method call is one of the `std::iter::Iterator` trait.
///
/// * `call_expr`: The map function call expression
/// * `recv`: The receiver of the call
/// * `closure_arg`: The argument to the map function call containing the closure/function to apply
pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) {
let recv_ty = cx.typeck_results().expr_ty(recv);
if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did)
// If we call a method on a `std::iter::Enumerate` instance
&& match_def_path(cx, recv_ty_defid, &CORE_ITER_ENUMERATE_STRUCT)
// If we are calling a method of the `Iterator` trait
&& is_trait_method(cx, call_expr, sym::Iterator)
// And the map argument is a closure
&& let ExprKind::Closure(closure) = closure_arg.kind
&& let closure_body = cx.tcx.hir().body(closure.body)
// And that closure has one argument ...
&& let [closure_param] = closure_body.params
// .. which is a tuple of 2 elements
&& let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind
// And that the first element (the index) is either `_` or unused in the body
&& pat_is_wild(cx, &index.kind, closure_body)
// Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the
// first example below, `expr_or_init` would return `recv`.
// ```
// iter.enumerate().map(|(_, x)| x)
// ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate`
//
// let binding = iter.enumerate();
// ^^^^^^^^^^^^^^^^ `recv_init_expr`
// binding.map(|(_, x)| x)
// ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate`
// ```
&& let recv_init_expr = expr_or_init(cx, recv)
// Make sure the initializer is a method call. It may be that the `Enumerate` comes from something
// that we cannot control.
// This would for instance happen with:
// ```
// external_lib::some_function_returning_enumerate().map(|(_, x)| x)
// ```
&& let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind
&& let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id)
// Make sure the method call is `std::iter::Iterator::enumerate`.
&& match_def_path(cx, enumerate_defid, &CORE_ITER_ENUMERATE_METHOD)
{
// Check if the tuple type was explicit. It may be the type system _needs_ the type of the element
// that would be explicited in the closure.
let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) {
// We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`.
// Fallback to `..` if we fail getting either snippet.
Some(ty_span) => snippet_opt(cx, elem.span)
.and_then(|binding_name| snippet_opt(cx, ty_span).map(|ty_name| format!("{binding_name}: {ty_name}")))
.unwrap_or_else(|| "..".to_string()),
// Otherwise, we have no explicit type. We can replace with the binding name of the element.
None => snippet(cx, elem.span, "..").into_owned(),
};
// Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we
// can get from the `MethodCall`.
span_lint_hir_and_then(
cx,
UNUSED_ENUMERATE_INDEX,
recv_init_expr.hir_id,
enumerate_span,
"you seem to use `.enumerate()` and immediately discard the index",
|diag| {
multispan_sugg_with_applicability(
diag,
"remove the `.enumerate()` call",
Applicability::MachineApplicable,
vec![
(closure_param.span, new_closure_param),
(
enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()),
String::new(),
),
],
);
},
);
}
}
/// Find the span of the explicit type of the element.
///
/// # Returns
/// If the tuple argument:
/// * Has no explicit type, returns `None`
/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None`
/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for
/// the element type.
fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option<Span> {
if let [tuple_ty] = fn_decl.inputs
&& let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind
&& !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer)
{
Some(elem_ty.span)
} else {
None
}
}

View File

@ -8,6 +8,7 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_from_proc_macro;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
@ -32,6 +33,13 @@ declare_clippy_lint! {
"detects missing documentation for private members"
}
macro_rules! note_prev_span_then_ret {
($prev_span:expr, $span:expr) => {{
$prev_span = Some($span);
return;
}};
}
pub struct MissingDoc {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
@ -39,6 +47,8 @@ pub struct MissingDoc {
/// Stack of whether #[doc(hidden)] is set
/// at each level which has lint attributes.
doc_hidden_stack: Vec<bool>,
/// Used to keep tracking of the previous item, field or variants etc, to get the search span.
prev_span: Option<Span>,
}
impl Default for MissingDoc {
@ -54,6 +64,7 @@ impl MissingDoc {
Self {
crate_items_only,
doc_hidden_stack: vec![false],
prev_span: None,
}
}
@ -108,7 +119,8 @@ impl MissingDoc {
let has_doc = attrs
.iter()
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()))
|| matches!(self.search_span(sp), Some(span) if span_to_snippet_contains_docs(cx, span));
if !has_doc {
span_lint(
@ -119,6 +131,32 @@ impl MissingDoc {
);
}
}
/// Return a span to search for doc comments manually.
///
/// # Example
/// ```ignore
/// fn foo() { ... }
/// ^^^^^^^^^^^^^^^^ prev_span
/// ↑
/// | search_span |
/// ↓
/// fn bar() { ... }
/// ^^^^^^^^^^^^^^^^ cur_span
/// ```
fn search_span(&self, cur_span: Span) -> Option<Span> {
let prev_span = self.prev_span?;
let start_pos = if prev_span.contains(cur_span) {
// In case when the prev_span is an entire struct, or enum,
// and the current span is a field, or variant, we need to search from
// the starting pos of the previous span.
prev_span.lo()
} else {
prev_span.hi()
};
let search_span = cur_span.with_lo(start_pos).with_hi(cur_span.lo());
Some(search_span)
}
}
impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
@ -138,6 +176,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
self.check_missing_docs_attrs(cx, CRATE_DEF_ID, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate");
}
fn check_crate_post(&mut self, _: &LateContext<'tcx>) {
self.prev_span = None;
}
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
match it.kind {
hir::ItemKind::Fn(..) => {
@ -145,7 +187,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if it.ident.name == sym::main {
let at_root = cx.tcx.local_parent(it.owner_id.def_id) == CRATE_DEF_ID;
if at_root {
return;
note_prev_span_then_ret!(self.prev_span, it.span);
}
}
},
@ -164,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::GlobalAsm(..)
| hir::ItemKind::Impl { .. }
| hir::ItemKind::Use(..) => return,
| hir::ItemKind::Use(..) => note_prev_span_then_ret!(self.prev_span, it.span),
};
let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id());
@ -173,6 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, it) {
self.check_missing_docs_attrs(cx, it.owner_id.def_id, attrs, it.span, article, desc);
}
self.prev_span = Some(it.span);
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
@ -182,16 +225,17 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, trait_item) {
self.check_missing_docs_attrs(cx, trait_item.owner_id.def_id, attrs, trait_item.span, article, desc);
}
self.prev_span = Some(trait_item.span);
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
// If the method is an impl for a trait, don't doc.
if let Some(cid) = cx.tcx.associated_item(impl_item.owner_id).impl_container(cx.tcx) {
if cx.tcx.impl_trait_ref(cid).is_some() {
return;
note_prev_span_then_ret!(self.prev_span, impl_item.span);
}
} else {
return;
note_prev_span_then_ret!(self.prev_span, impl_item.span);
}
let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id());
@ -199,6 +243,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, impl_item) {
self.check_missing_docs_attrs(cx, impl_item.owner_id.def_id, attrs, impl_item.span, article, desc);
}
self.prev_span = Some(impl_item.span);
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
@ -208,6 +253,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
}
}
self.prev_span = Some(sf.span);
}
fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
@ -215,5 +261,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
if !is_from_proc_macro(cx, v) {
self.check_missing_docs_attrs(cx, v.def_id, attrs, v.span, "a", "variant");
}
self.prev_span = Some(v.span);
}
}
fn span_to_snippet_contains_docs(cx: &LateContext<'_>, search_span: Span) -> bool {
let Some(snippet) = snippet_opt(cx, search_span) else {
return false;
};
snippet.lines().rev().any(|line| line.trim().starts_with("///"))
}

View File

@ -109,7 +109,14 @@ fn collect_unsafe_exprs<'tcx>(
ExprKind::Path(QPath::Resolved(
_,
hir::Path {
res: Res::Def(DefKind::Static{mutability:Mutability::Mut, ..}, _),
res:
Res::Def(
DefKind::Static {
mutability: Mutability::Mut,
..
},
_,
),
..
},
)) => {
@ -149,7 +156,13 @@ fn collect_unsafe_exprs<'tcx>(
ExprKind::Path(QPath::Resolved(
_,
hir::Path {
res: Res::Def(DefKind::Static{mutability:Mutability::Mut, ..}, _),
res: Res::Def(
DefKind::Static {
mutability: Mutability::Mut,
..
},
_
),
..
}
))

View File

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::{span_lint, span_lint_hir};
use clippy_utils::higher;
use rustc_hir as hir;
use rustc_hir::intravisit;
@ -35,9 +35,34 @@ impl<'tcx> LateLintPass<'tcx> for MutMut {
}
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_>) {
use rustc_hir::intravisit::Visitor;
if in_external_macro(cx.sess(), ty.span) {
return;
}
MutVisitor { cx }.visit_ty(ty);
if let hir::TyKind::Ref(
_,
hir::MutTy {
ty: pty,
mutbl: hir::Mutability::Mut,
},
) = ty.kind
{
if let hir::TyKind::Ref(
_,
hir::MutTy {
mutbl: hir::Mutability::Mut,
..
},
) = pty.kind
{
span_lint(
cx,
MUT_MUT,
ty.span,
"generally you want to avoid `&mut &mut _` if possible",
);
}
}
}
}
@ -62,17 +87,19 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
intravisit::walk_expr(self, body);
} else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind {
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind {
span_lint(
span_lint_hir(
self.cx,
MUT_MUT,
expr.hir_id,
expr.span,
"generally you want to avoid `&mut &mut _` if possible",
);
} else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() {
if ty.peel_refs().is_sized(self.cx.tcx, self.cx.param_env) {
span_lint(
span_lint_hir(
self.cx,
MUT_MUT,
expr.hir_id,
expr.span,
"this expression mutably borrows a mutable reference. Consider reborrowing",
);
@ -80,37 +107,4 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
}
}
}
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
if in_external_macro(self.cx.sess(), ty.span) {
return;
}
if let hir::TyKind::Ref(
_,
hir::MutTy {
ty: pty,
mutbl: hir::Mutability::Mut,
},
) = ty.kind
{
if let hir::TyKind::Ref(
_,
hir::MutTy {
mutbl: hir::Mutability::Mut,
..
},
) = pty.kind
{
span_lint(
self.cx,
MUT_MUT,
ty.span,
"generally you want to avoid `&mut &mut _` if possible",
);
}
}
intravisit::walk_ty(self, ty);
}
}

View File

@ -290,14 +290,21 @@ impl NonCopyConst {
promoted: None,
};
let param_env = cx.tcx.param_env(def_id).with_reveal_all_normalized(cx.tcx);
let result = cx.tcx.const_eval_global_id_for_typeck(param_env, cid, rustc_span::DUMMY_SP);
let result = cx
.tcx
.const_eval_global_id_for_typeck(param_env, cid, rustc_span::DUMMY_SP);
self.is_value_unfrozen_raw(cx, result, ty)
}
fn is_value_unfrozen_expr<'tcx>(&self, cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
let args = cx.typeck_results().node_args(hir_id);
let result = Self::const_eval_resolve(cx.tcx, cx.param_env, ty::UnevaluatedConst::new(def_id, args), rustc_span::DUMMY_SP);
let result = Self::const_eval_resolve(
cx.tcx,
cx.param_env,
ty::UnevaluatedConst::new(def_id, args),
rustc_span::DUMMY_SP,
);
self.is_value_unfrozen_raw(cx, result, ty)
}

View File

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::get_enclosing_block;
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
use clippy_utils::source::snippet;
@ -77,36 +77,52 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
if let Some(expr) = visitor.read_zero_expr {
let applicability = Applicability::MaybeIncorrect;
match vec_init_kind {
VecInitKind::WithConstCapacity(len) => {
span_lint_and_sugg(
cx,
READ_ZERO_BYTE_VEC,
expr.span,
"reading zero byte data to `Vec`",
"try",
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
applicability,
);
},
VecInitKind::WithConstCapacity(len) => span_lint_hir_and_then(
cx,
READ_ZERO_BYTE_VEC,
expr.hir_id,
expr.span,
"reading zero byte data to `Vec`",
|diag| {
diag.span_suggestion(
expr.span,
"try",
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
applicability,
);
},
),
VecInitKind::WithExprCapacity(hir_id) => {
let e = cx.tcx.hir().expect_expr(hir_id);
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
READ_ZERO_BYTE_VEC,
expr.hir_id,
expr.span,
"reading zero byte data to `Vec`",
"try",
format!(
"{}.resize({}, 0); {}",
ident.as_str(),
snippet(cx, e.span, ".."),
snippet(cx, expr.span, "..")
),
applicability,
|diag| {
diag.span_suggestion(
expr.span,
"try",
format!(
"{}.resize({}, 0); {}",
ident.as_str(),
snippet(cx, e.span, ".."),
snippet(cx, expr.span, "..")
),
applicability,
);
},
);
},
_ => {
span_lint(cx, READ_ZERO_BYTE_VEC, expr.span, "reading zero byte data to `Vec`");
span_lint_hir(
cx,
READ_ZERO_BYTE_VEC,
expr.hir_id,
expr.span,
"reading zero byte data to `Vec`",
);
},
}
}

View File

@ -1,5 +1,5 @@
use crate::rustc_lint::LintContext;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir};
use clippy_utils::get_parent_expr;
use clippy_utils::sugg::Sugg;
use hir::Param;
@ -273,9 +273,10 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
&& ident == path.segments[0].ident
&& count_closure_usage(cx, block, path) == 1
{
span_lint(
span_lint_hir(
cx,
REDUNDANT_CLOSURE_CALL,
second.hir_id,
second.span,
"closure called just once immediately after it was declared",
);

View File

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
@ -380,7 +380,7 @@ fn check_final_expr<'tcx>(
return;
}
emit_return_lint(cx, ret_span, semi_spans, &replacement);
emit_return_lint(cx, ret_span, semi_spans, &replacement, expr.hir_id);
},
ExprKind::If(_, then, else_clause_opt) => {
check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone());
@ -415,18 +415,31 @@ fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool {
contains_if(expr, false)
}
fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec<Span>, replacement: &RetReplacement<'_>) {
fn emit_return_lint(
cx: &LateContext<'_>,
ret_span: Span,
semi_spans: Vec<Span>,
replacement: &RetReplacement<'_>,
at: HirId,
) {
if ret_span.from_expansion() {
return;
}
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
let suggestions = std::iter::once((ret_span, replacement.to_string()))
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
.collect();
span_lint_hir_and_then(
cx,
NEEDLESS_RETURN,
at,
ret_span,
"unneeded `return` statement",
|diag| {
let suggestions = std::iter::once((ret_span, replacement.to_string()))
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
.collect();
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
});
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
},
);
}
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
@ -452,8 +465,8 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
// Go backwards while encountering whitespace and extend the given Span to that point.
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
let ws = [' ', '\t', '\n'];
if let Some(non_ws_pos) = prev_source.rfind(|c| !ws.contains(&c)) {
let ws = [b' ', b'\t', b'\n'];
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
let len = prev_source.len() - non_ws_pos - 1;
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
}

View File

@ -109,6 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
sym::core => (STD_INSTEAD_OF_CORE, "std", "core"),
sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"),
_ => {
self.prev_span = first_segment.ident.span;
return;
},
},
@ -116,6 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
if cx.tcx.crate_name(def_id.krate) == sym::core {
(ALLOC_INSTEAD_OF_CORE, "alloc", "core")
} else {
self.prev_span = first_segment.ident.span;
return;
}
},

View File

@ -37,7 +37,7 @@ declare_clippy_lint! {
/// static BUF: String = const { String::new() };
/// }
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
perf,
"suggest using `const` in `thread_local!` macro"

View File

@ -514,7 +514,7 @@ declare_clippy_lint! {
/// ^^^^ ^^ `bool::then` only executes the closure if the condition is true!
/// }
/// ```
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub EAGER_TRANSMUTE,
correctness,
"eager evaluation of `transmute`"

View File

@ -392,6 +392,10 @@ impl<'tcx> LateLintPass<'tcx> for Types {
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &hir::FieldDef<'tcx>) {
if field.span.from_expansion() {
return;
}
let is_exported = cx.effective_visibilities.is_exported(field.def_id);
self.check_ty(

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{expr_or_init, get_trait_def_id, path_def_id};
use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id};
use rustc_ast::BinOpKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
@ -19,11 +19,11 @@ use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
declare_clippy_lint! {
/// ### What it does
/// Checks that there isn't an infinite recursion in `PartialEq` trait
/// implementation.
/// Checks that there isn't an infinite recursion in trait
/// implementations.
///
/// ### Why is this bad?
/// This is a hard to find infinite recursion which will crashing any code
/// This is a hard to find infinite recursion that will crash any code.
/// using it.
///
/// ### Example
@ -42,7 +42,7 @@ declare_clippy_lint! {
/// Use instead:
///
/// In such cases, either use `#[derive(PartialEq)]` or don't implement it.
#[clippy::version = "1.76.0"]
#[clippy::version = "1.77.0"]
pub UNCONDITIONAL_RECURSION,
suspicious,
"detect unconditional recursion in some traits implementation"
@ -201,7 +201,6 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
}
}
#[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
@ -224,7 +223,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
&& 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"])
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_def_id)
{
let is_bad = match expr.kind {
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
@ -291,7 +290,6 @@ where
self.map
}
#[allow(clippy::unnecessary_def_path)]
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.found_default_call {
return;
@ -303,7 +301,7 @@ where
&& 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.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id)
{
self.found_default_call = true;
span_error(self.cx, self.method_span, expr);
@ -312,10 +310,9 @@ where
}
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 Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
{
let impls = cx.tcx.trait_impls_of(default_trait_id);
for (ty, impl_def_ids) in impls.non_blanket_impls() {
@ -394,6 +391,34 @@ impl UnconditionalRecursion {
}
}
fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, expr: &Expr<'_>) {
let Some(sig) = cx
.typeck_results()
.liberated_fn_sigs()
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
else {
return;
};
// Check if we are calling `Into::into` where the node args match with our `From::from` signature:
// From::from signature: fn(S1) -> S2
// <S1 as Into<S2>>::into(s1), node_args=[S1, S2]
// If they do match, then it must mean that it is the blanket impl,
// which calls back into our `From::from` again (`Into` is not specializable).
// rustc's unconditional_recursion already catches calling `From::from` directly
if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr)
&& let [s1, s2] = **node_args
&& let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type())
&& let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id)
&& cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)
&& get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From)
&& s1 == sig.inputs()[0]
&& s2 == sig.output()
{
span_error(cx, method_span, expr);
}
}
impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
fn check_fn(
&mut self,
@ -410,10 +435,11 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
// Doesn't have a conditional return.
&& !has_conditional_return(body, expr)
{
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);
match name.name {
sym::eq | sym::ne => check_partial_eq(cx, method_span, method_def_id, name, expr),
sym::to_string => check_to_string(cx, method_span, method_def_id, name, expr),
sym::from => check_from(cx, method_span, method_def_id, expr),
_ => {},
}
self.check_default_new(cx, decl, body, method_span, method_def_id);
}

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
use hir::{ExprKind, PatKind};
use hir::{ExprKind, HirId, PatKind};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -135,22 +135,22 @@ fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
&& is_ok_wild_or_dotdot_pattern(cx, pat)
&& let Some(op) = should_lint(cx, init) =>
{
emit_lint(cx, cond.span, op, &[pat.span]);
emit_lint(cx, cond.span, cond.hir_id, op, &[pat.span]);
},
// we will capture only the case where the match is Ok( ) or Err( )
// prefer to match the minimum possible, and expand later if needed
// to avoid false positives on something as used as this
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
emit_lint(cx, expr.span, op, &[arm1.pat.span]);
emit_lint(cx, expr.span, expr.hir_id, op, &[arm1.pat.span]);
}
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
emit_lint(cx, expr.span, op, &[arm2.pat.span]);
emit_lint(cx, expr.span, expr.hir_id, op, &[arm2.pat.span]);
}
},
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
_ if let Some(op) = should_lint(cx, expr) => {
emit_lint(cx, expr.span, op, &[]);
emit_lint(cx, expr.span, expr.hir_id, op, &[]);
},
_ => {},
};
@ -279,7 +279,7 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
}
}
fn emit_lint(cx: &LateContext<'_>, span: Span, op: IoOp, wild_cards: &[Span]) {
fn emit_lint(cx: &LateContext<'_>, span: Span, at: HirId, op: IoOp, wild_cards: &[Span]) {
let (msg, help) = match op {
IoOp::AsyncRead(false) => (
"read amount is not handled",
@ -301,7 +301,7 @@ fn emit_lint(cx: &LateContext<'_>, span: Span, op: IoOp, wild_cards: &[Span]) {
IoOp::SyncWrite(true) | IoOp::AsyncWrite(true) => ("written amount is not handled", None),
};
span_lint_and_then(cx, UNUSED_IO_AMOUNT, span, msg, |diag| {
span_lint_hir_and_then(cx, UNUSED_IO_AMOUNT, at, span, msg, |diag| {
if let Some(help_str) = help {
diag.help(help_str);
}

View File

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, peel_ref_operators};
use rustc_ast::Mutability;
@ -79,13 +79,15 @@ impl<'tcx> LateLintPass<'tcx> for UnusedPeekable {
}
if !vis.found_peek_call {
span_lint_and_help(
span_lint_hir_and_then(
cx,
UNUSED_PEEKABLE,
local.hir_id,
ident.span,
"`peek` never called on `Peekable` iterator",
None,
"consider removing the call to `peekable`",
|diag| {
diag.help("consider removing the call to `peekable`");
},
);
}
}

View File

@ -8,11 +8,12 @@ use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{walk_inf, walk_ty, Visitor};
use rustc_hir::{
self as hir, Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind,
HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
self as hir, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl,
ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty as MiddleTy;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -95,10 +96,9 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
let stack_item = if let ItemKind::Impl(Impl { self_ty, generics, .. }) = item.kind
&& let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind
&& let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args
&& parameters.as_ref().map_or(true, |params| {
params.parenthesized == GenericArgsParentheses::No
&& !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
})
&& parameters
.as_ref()
.map_or(true, |params| params.parenthesized == GenericArgsParentheses::No)
&& !item.span.from_expansion()
&& !is_from_proc_macro(cx, item)
// expensive, should be last check
@ -226,7 +226,12 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
} else {
hir_ty_to_ty(cx.tcx, hir_ty)
}
&& same_type_and_consts(ty, cx.tcx.type_of(impl_id).instantiate_identity())
&& let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity()
&& same_type_and_consts(ty, impl_ty)
// Ensure the type we encounter and the one from the impl have the same lifetime parameters. It may be that
// the lifetime parameters of `ty` are ellided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`, in
// which case we must still trigger the lint.
&& (has_no_lifetime(ty) || same_lifetimes(ty, impl_ty))
{
span_lint(cx, hir_ty.span);
}
@ -318,3 +323,37 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
span_lint(cx, span);
}
}
/// Returns `true` if types `a` and `b` have the same lifetime parameters, otherwise returns
/// `false`.
///
/// This function does not check that types `a` and `b` are the same types.
fn same_lifetimes<'tcx>(a: MiddleTy<'tcx>, b: MiddleTy<'tcx>) -> bool {
use rustc_middle::ty::{Adt, GenericArgKind};
match (&a.kind(), &b.kind()) {
(&Adt(_, args_a), &Adt(_, args_b)) => {
args_a
.iter()
.zip(args_b.iter())
.all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) {
// TODO: Handle inferred lifetimes
(GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b,
(GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b),
_ => true,
})
},
_ => a == b,
}
}
/// Returns `true` if `ty` has no lifetime parameter, otherwise returns `false`.
fn has_no_lifetime(ty: MiddleTy<'_>) -> bool {
use rustc_middle::ty::{Adt, GenericArgKind};
match ty.kind() {
&Adt(_, args) => !args
.iter()
// TODO: Handle inferred lifetimes
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(..))),
_ => true,
}
}

View File

@ -925,7 +925,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
&& let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr))
&& match_type(self.cx, expr_ty, &paths::LINT)
{
if let hir::def::Res::Def(DefKind::Static(..), _) = path.res {
if let hir::def::Res::Def(DefKind::Static { .. }, _) = path.res {
let lint_name = last_path_segment(qpath).ident.name;
self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
} else if let Some(local) = get_parent_local(self.cx, expr) {

View File

@ -223,7 +223,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
None
}
},
Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path(
Res::Def(DefKind::Static { .. }, def_id) => read_mir_alloc_def_path(
cx,
cx.tcx.eval_static_initializer(def_id).ok()?.inner(),
cx.tcx.type_of(def_id).instantiate_identity(),

View File

@ -213,7 +213,7 @@ fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
// Allow skipping imports containing user configured segments,
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
// segment matching need to be exact instead of using 'contains', in case user unintentionaly put
// segment matching need to be exact instead of using 'contains', in case user unintentionally put
// a single character in the config thus skipping most of the warnings.
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
}

View File

@ -0,0 +1,154 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::VecArgs;
use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ConstKind, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for array or vec initializations which call a function or method,
/// but which have a repeat count of zero.
///
/// ### Why is this bad?
/// Such an initialization, despite having a repeat length of 0, will still call the inner function.
/// This may not be obvious and as such there may be unintended side effects in code.
///
/// ### Example
/// ```no_run
/// fn side_effect() -> i32 {
/// println!("side effect");
/// 10
/// }
/// let a = [side_effect(); 0];
/// ```
/// Use instead:
/// ```no_run
/// fn side_effect() -> i32 {
/// println!("side effect");
/// 10
/// }
/// side_effect();
/// let a: [i32; 0] = [];
/// ```
#[clippy::version = "1.75.0"]
pub ZERO_REPEAT_SIDE_EFFECTS,
suspicious,
"usage of zero-sized initializations of arrays or vecs causing side effects"
}
declare_lint_pass!(ZeroRepeatSideEffects => [ZERO_REPEAT_SIDE_EFFECTS]);
impl LateLintPass<'_> for ZeroRepeatSideEffects {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
if let Some(args) = VecArgs::hir(cx, expr)
&& let VecArgs::Repeat(inner_expr, len) = args
&& let ExprKind::Lit(l) = len.kind
&& let LitKind::Int(i, _) = l.node
&& i.0 == 0
{
inner_check(cx, expr, inner_expr, true);
} else if let ExprKind::Repeat(inner_expr, _) = expr.kind
&& let ty::Array(_, cst) = cx.typeck_results().expr_ty(expr).kind()
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
&& element_count == 0
{
inner_check(cx, expr, inner_expr, false);
}
}
}
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
// check if expr is a call or has a call inside it
if for_each_expr(inner_expr, |x| {
if let ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _) = x.kind {
std::ops::ControlFlow::Break(())
} else {
std::ops::ControlFlow::Continue(())
}
})
.is_some()
{
let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id);
let return_type = cx.typeck_results().expr_ty(expr);
if let Node::Local(l) = parent_hir_node {
array_span_lint(
cx,
l.span,
inner_expr.span,
l.pat.span,
Some(return_type),
is_vec,
false,
);
} else if let Node::Expr(x) = parent_hir_node
&& let ExprKind::Assign(l, _, _) = x.kind
{
array_span_lint(cx, x.span, inner_expr.span, l.span, Some(return_type), is_vec, true);
} else {
span_lint_and_sugg(
cx,
ZERO_REPEAT_SIDE_EFFECTS,
expr.span.source_callsite(),
"function or method calls as the initial value in zero-sized array initializers may cause side effects",
"consider using",
format!(
"{{ {}; {}[] as {return_type} }}",
snippet(cx, inner_expr.span.source_callsite(), ".."),
if is_vec { "vec!" } else { "" },
),
Applicability::Unspecified,
);
}
}
}
fn array_span_lint(
cx: &LateContext<'_>,
expr_span: Span,
func_call_span: Span,
variable_name_span: Span,
expr_ty: Option<Ty<'_>>,
is_vec: bool,
is_assign: bool,
) {
let has_ty = expr_ty.is_some();
span_lint_and_sugg(
cx,
ZERO_REPEAT_SIDE_EFFECTS,
expr_span.source_callsite(),
"function or method calls as the initial value in zero-sized array initializers may cause side effects",
"consider using",
format!(
"{}; {}{}{} = {}[]{}{}",
snippet(cx, func_call_span.source_callsite(), ".."),
if has_ty && !is_assign { "let " } else { "" },
snippet(cx, variable_name_span.source_callsite(), ".."),
if let Some(ty) = expr_ty
&& !is_assign
{
format!(": {ty}")
} else {
String::new()
},
if is_vec { "vec!" } else { "" },
if let Some(ty) = expr_ty
&& is_assign
{
format!(" as {ty}")
} else {
String::new()
},
if is_assign { "" } else { ";" }
),
Applicability::Unspecified,
);
}

View File

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.78"
version = "0.1.79"
edition = "2021"
publish = false

View File

@ -10,8 +10,10 @@ use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item
use rustc_lexer::tokenize;
use rustc_lint::LateContext;
use rustc_middle::mir::interpret::{alloc_range, Scalar};
use rustc_middle::mir::ConstValue;
use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, IntTy, List, ScalarInt, Ty, TyCtxt, UintTy};
use rustc_middle::{bug, mir, span_bug};
use rustc_span::def_id::DefId;
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::SyntaxContext;
use rustc_target::abi::Size;
@ -307,6 +309,12 @@ impl ConstantSource {
}
}
/// Attempts to check whether the expression is a constant representing an empty slice, str, array,
/// etc…
pub fn constant_is_empty(lcx: &LateContext<'_>, e: &Expr<'_>) -> Option<bool> {
ConstEvalLateContext::new(lcx, lcx.typeck_results()).expr_is_empty(e)
}
/// Attempts to evaluate the expression as a constant.
pub fn constant<'tcx>(
lcx: &LateContext<'tcx>,
@ -406,7 +414,13 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value),
ExprKind::DropTemps(e) => self.expr(e),
ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)),
ExprKind::Path(ref qpath) => {
self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| {
let result = mir_to_const(this.lcx, result)?;
this.source = ConstantSource::Constant;
Some(result)
})
},
ExprKind::Block(block, _) => self.block(block),
ExprKind::Lit(lit) => {
if is_direct_expn_of(e.span, "cfg").is_some() {
@ -472,6 +486,49 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
}
}
/// Simple constant folding to determine if an expression is an empty slice, str, array, …
/// `None` will be returned if the constness cannot be determined, or if the resolution
/// leaves the local crate.
pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option<bool> {
match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr_is_empty(self.lcx.tcx.hir().body(body).value),
ExprKind::DropTemps(e) => self.expr_is_empty(e),
ExprKind::Path(ref qpath) => {
if !self
.typeck_results
.qpath_res(qpath, e.hir_id)
.opt_def_id()
.is_some_and(DefId::is_local)
{
return None;
}
self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| {
mir_is_empty(this.lcx, result)
})
},
ExprKind::Lit(lit) => {
if is_direct_expn_of(e.span, "cfg").is_some() {
None
} else {
match &lit.node {
LitKind::Str(is, _) => Some(is.is_empty()),
LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => Some(s.is_empty()),
_ => None,
}
}
},
ExprKind::Array(vec) => self.multi(vec).map(|v| v.is_empty()),
ExprKind::Repeat(..) => {
if let ty::Array(_, n) = self.typeck_results.expr_ty(e).kind() {
Some(n.try_eval_target_usize(self.lcx.tcx, self.lcx.param_env)? == 0)
} else {
span_bug!(e.span, "typeck error");
}
},
_ => None,
}
}
#[expect(clippy::cast_possible_wrap)]
fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tcx>> {
use self::Constant::{Bool, Int};
@ -519,8 +576,11 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
}
/// Lookup a possibly constant expression from an `ExprKind::Path`.
fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant<'tcx>> {
/// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it.
fn fetch_path_and_apply<T, F>(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>, f: F) -> Option<T>
where
F: FnOnce(&mut Self, rustc_middle::mir::Const<'tcx>) -> Option<T>,
{
let res = self.typeck_results.qpath_res(qpath, id);
match res {
Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
@ -553,9 +613,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
.const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, args), qpath.span())
.ok()
.map(|val| rustc_middle::mir::Const::from_value(val, ty))?;
let result = mir_to_const(self.lcx, result)?;
self.source = ConstantSource::Constant;
Some(result)
f(self, result)
},
_ => None,
}
@ -746,7 +804,6 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
}
pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) -> Option<Constant<'tcx>> {
use rustc_middle::mir::ConstValue;
let mir::Const::Val(val, _) = result else {
// We only work on evaluated consts.
return None;
@ -794,6 +851,42 @@ pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) ->
}
}
fn mir_is_empty<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) -> Option<bool> {
let mir::Const::Val(val, _) = result else {
// We only work on evaluated consts.
return None;
};
match (val, result.ty().kind()) {
(_, ty::Ref(_, inner_ty, _)) => match inner_ty.kind() {
ty::Str | ty::Slice(_) => {
if let ConstValue::Indirect { alloc_id, offset } = val {
// Get the length from the slice, using the same formula as
// [`ConstValue::try_get_slice_bytes_for_diagnostics`].
let a = lcx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
let ptr_size = lcx.tcx.data_layout.pointer_size;
if a.size() < offset + 2 * ptr_size {
// (partially) dangling reference
return None;
}
let len = a
.read_scalar(&lcx.tcx, alloc_range(offset + ptr_size, ptr_size), false)
.ok()?
.to_target_usize(&lcx.tcx)
.ok()?;
Some(len == 0)
} else {
None
}
},
ty::Array(_, len) => Some(len.try_to_target_usize(lcx.tcx)? == 0),
_ => None,
},
(ConstValue::Indirect { .. }, ty::Array(_, len)) => Some(len.try_to_target_usize(lcx.tcx)? == 0),
(ConstValue::ZeroSized, _) => Some(true),
_ => None,
}
}
fn field_of_struct<'tcx>(
adt_def: ty::AdtDef<'tcx>,
lcx: &LateContext<'tcx>,

View File

@ -36,6 +36,20 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) {
/// Usually it's nicer to provide more context for lint messages.
/// Be sure the output is understandable when you use this method.
///
/// NOTE: Lint emissions are always bound to a node in the HIR, which is used to determine
/// the lint level.
/// For the `span_lint` function, the node that was passed into the `LintPass::check_*` function is
/// used.
///
/// If you're emitting the lint at the span of a different node than the one provided by the
/// `LintPass::check_*` function, consider using [`span_lint_hir`] instead.
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir`] instead.
///
/// # Example
///
/// ```ignore
@ -61,6 +75,20 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
///
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// NOTE: Lint emissions are always bound to a node in the HIR, which is used to determine
/// the lint level.
/// For the `span_lint_and_help` function, the node that was passed into the `LintPass::check_*`
/// function is used.
///
/// If you're emitting the lint at the span of a different node than the one provided by the
/// `LintPass::check_*` function, consider using [`span_lint_hir_and_then`] instead.
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
///
/// # Example
///
/// ```text
@ -99,6 +127,20 @@ pub fn span_lint_and_help<T: LintContext>(
///
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// NOTE: Lint emissions are always bound to a node in the HIR, which is used to determine
/// the lint level.
/// For the `span_lint_and_note` function, the node that was passed into the `LintPass::check_*`
/// function is used.
///
/// If you're emitting the lint at the span of a different node than the one provided by the
/// `LintPass::check_*` function, consider using [`span_lint_hir_and_then`] instead.
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
///
/// # Example
///
/// ```text
@ -139,6 +181,20 @@ pub fn span_lint_and_note<T: LintContext>(
///
/// If you need to customize your lint output a lot, use this function.
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// NOTE: Lint emissions are always bound to a node in the HIR, which is used to determine
/// the lint level.
/// For the `span_lint_and_then` function, the node that was passed into the `LintPass::check_*`
/// function is used.
///
/// If you're emitting the lint at the span of a different node than the one provided by the
/// `LintPass::check_*` function, consider using [`span_lint_hir_and_then`] instead.
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
pub fn span_lint_and_then<C, S, F>(cx: &C, lint: &'static Lint, sp: S, msg: &str, f: F)
where
C: LintContext,
@ -152,6 +208,30 @@ where
});
}
/// Like [`span_lint`], but emits the lint at the node identified by the given `HirId`.
///
/// This is in contrast to [`span_lint`], which always emits the lint at the node that was last
/// passed to the `LintPass::check_*` function.
///
/// The `HirId` is used for checking lint level attributes and to fulfill lint expectations defined
/// via the `#[expect]` attribute.
///
/// For example:
/// ```ignore
/// fn f() { /* <node_1> */
///
/// #[allow(clippy::some_lint)]
/// let _x = /* <expr_1> */;
/// }
/// ```
/// If `some_lint` does its analysis in `LintPass::check_fn` (at `<node_1>`) and emits a lint at
/// `<expr_1>` using [`span_lint`], then allowing the lint at `<expr_1>` as attempted in the snippet
/// will not work!
/// Even though that is where the warning points at, which would be confusing to users.
///
/// Instead, use this function and also pass the `HirId` of `<expr_1>`, which will let
/// the compiler check lint level attributes at the place of the expression and
/// the `#[allow]` will work.
pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
#[expect(clippy::disallowed_methods)]
cx.tcx.node_span_lint(lint, hir_id, sp, msg.to_string(), |diag| {
@ -159,6 +239,30 @@ pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, s
});
}
/// Like [`span_lint_and_then`], but emits the lint at the node identified by the given `HirId`.
///
/// This is in contrast to [`span_lint_and_then`], which always emits the lint at the node that was
/// last passed to the `LintPass::check_*` function.
///
/// The `HirId` is used for checking lint level attributes and to fulfill lint expectations defined
/// via the `#[expect]` attribute.
///
/// For example:
/// ```ignore
/// fn f() { /* <node_1> */
///
/// #[allow(clippy::some_lint)]
/// let _x = /* <expr_1> */;
/// }
/// ```
/// If `some_lint` does its analysis in `LintPass::check_fn` (at `<node_1>`) and emits a lint at
/// `<expr_1>` using [`span_lint`], then allowing the lint at `<expr_1>` as attempted in the snippet
/// will not work!
/// Even though that is where the warning points at, which would be confusing to users.
///
/// Instead, use this function and also pass the `HirId` of `<expr_1>`, which will let
/// the compiler check lint level attributes at the place of the expression and
/// the `#[allow]` will work.
pub fn span_lint_hir_and_then(
cx: &LateContext<'_>,
lint: &'static Lint,
@ -182,6 +286,20 @@ pub fn span_lint_hir_and_then(
///
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// NOTE: Lint emissions are always bound to a node in the HIR, which is used to determine
/// the lint level.
/// For the `span_lint_and_sugg` function, the node that was passed into the `LintPass::check_*`
/// function is used.
///
/// If you're emitting the lint at the span of a different node than the one provided by the
/// `LintPass::check_*` function, consider using [`span_lint_hir_and_then`] instead.
/// This is needed for `#[allow]` and `#[expect]` attributes to work on the node
/// highlighted in the displayed warning.
///
/// If you're unsure which function you should use, you can test if the `#[allow]` attribute works
/// where you would expect it to.
/// If it doesn't, you likely need to use [`span_lint_hir_and_then`] instead.
///
/// # Example
///
/// ```text

View File

@ -2246,8 +2246,21 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
/// Returns the `DefId` of the callee if the given expression is a function or method call.
pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
fn_def_id_with_node_args(cx, expr).map(|(did, _)| did)
}
/// Returns the `DefId` of the callee if the given expression is a function or method call,
/// as well as its node args.
pub fn fn_def_id_with_node_args<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'_>,
) -> Option<(DefId, rustc_ty::GenericArgsRef<'tcx>)> {
let typeck = cx.typeck_results();
match &expr.kind {
ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
ExprKind::MethodCall(..) => Some((
typeck.type_dependent_def_id(expr.hir_id)?,
typeck.node_args(expr.hir_id),
)),
ExprKind::Call(
Expr {
kind: ExprKind::Path(qpath),
@ -2259,9 +2272,9 @@ pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
// Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or
// deref to fn pointers, dyn Fn, impl Fn - #8850
if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
cx.typeck_results().qpath_res(qpath, *path_hir_id)
typeck.qpath_res(qpath, *path_hir_id)
{
Some(id)
Some((id, typeck.node_args(*path_hir_id)))
} else {
None
}

View File

@ -19,6 +19,8 @@ pub const BTREESET_ITER: [&str; 6] = ["alloc", "collections", "btree", "set", "B
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
pub const CORE_ITER_CLONED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "cloned"];
pub const CORE_ITER_COPIED: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "copied"];
pub const CORE_ITER_ENUMERATE_METHOD: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "enumerate"];
pub const CORE_ITER_ENUMERATE_STRUCT: [&str; 5] = ["core", "iter", "adapters", "enumerate", "Enumerate"];
pub const CORE_ITER_FILTER: [&str; 6] = ["core", "iter", "traits", "iterator", "Iterator", "filter"];
pub const CORE_RESULT_OK_METHOD: [&str; 4] = ["core", "result", "Result", "ok"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["alloc", "ffi", "c_str", "CString", "as_c_str"];

View File

@ -1,6 +1,6 @@
[package]
name = "declare_clippy_lint"
version = "0.1.78"
version = "0.1.79"
edition = "2021"
publish = false

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2024-03-07"
channel = "nightly-2024-03-21"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View File

@ -4,6 +4,7 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint`
LL | cx.span_lint(lint, span, msg, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml)
= note: `-D clippy::disallowed-methods` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]`
@ -12,6 +13,8 @@ error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_
|
LL | tcx.node_span_lint(lint, hir_id, span, msg, |_| {});
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml)
error: aborting due to 2 previous errors

View File

@ -0,0 +1,38 @@
//@compile-flags: --test
#![warn(clippy::dbg_macro)]
#![allow(clippy::unnecessary_operation, clippy::no_effect)]
fn foo(n: u32) -> u32 {
if let Some(n) = n.checked_sub(4) { n } else { n }
}
fn factorial(n: u32) -> u32 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
fn main() {
42;
foo(3) + factorial(4);
(1, 2, 3, 4, 5);
}
#[test]
pub fn issue8481() {
dbg!(1);
}
#[cfg(test)]
fn foo2() {
dbg!(1);
}
#[cfg(test)]
mod mod1 {
fn func() {
dbg!(1);
}
}

View File

@ -1,6 +1,7 @@
//@compile-flags: --test
#![warn(clippy::dbg_macro)]
//@no-rustfix
#![allow(clippy::unnecessary_operation, clippy::no_effect)]
fn foo(n: u32) -> u32 {
if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
}
@ -15,9 +16,7 @@ fn factorial(n: u32) -> u32 {
fn main() {
dbg!(42);
dbg!(dbg!(dbg!(42)));
foo(3) + dbg!(factorial(4));
dbg!(1, 2, dbg!(3, 4));
dbg!(1, 2, 3, 4, 5);
}

View File

@ -1,5 +1,5 @@
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:5:22
--> tests/ui-toml/dbg_macro/dbg_macro.rs:6:22
|
LL | if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
| ^^^^^^^^^^^^^^^^^^^^^^
@ -12,7 +12,7 @@ LL | if let Some(n) = n.checked_sub(4) { n } else { n }
| ~~~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:9:8
--> tests/ui-toml/dbg_macro/dbg_macro.rs:10:8
|
LL | if dbg!(n <= 1) {
| ^^^^^^^^^^^^
@ -23,7 +23,7 @@ LL | if n <= 1 {
| ~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:10:9
--> tests/ui-toml/dbg_macro/dbg_macro.rs:11:9
|
LL | dbg!(1)
| ^^^^^^^
@ -34,7 +34,7 @@ LL | 1
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:12:9
--> tests/ui-toml/dbg_macro/dbg_macro.rs:13:9
|
LL | dbg!(n * factorial(n - 1))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -45,7 +45,7 @@ LL | n * factorial(n - 1)
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:17:5
--> tests/ui-toml/dbg_macro/dbg_macro.rs:18:5
|
LL | dbg!(42);
| ^^^^^^^^
@ -55,17 +55,6 @@ help: remove the invocation before committing it to a version control system
LL | 42;
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:18:5
|
LL | dbg!(dbg!(dbg!(42)));
| ^^^^^^^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | dbg!(dbg!(42));
| ~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:19:14
|
@ -80,17 +69,6 @@ LL | foo(3) + factorial(4);
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:20:5
|
LL | dbg!(1, 2, dbg!(3, 4));
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | (1, 2, dbg!(3, 4));
| ~~~~~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui-toml/dbg_macro/dbg_macro.rs:21:5
|
LL | dbg!(1, 2, 3, 4, 5);
| ^^^^^^^^^^^^^^^^^^^
|
@ -99,5 +77,5 @@ help: remove the invocation before committing it to a version control system
LL | (1, 2, 3, 4, 5);
| ~~~~~~~~~~~~~~~
error: aborting due to 9 previous errors
error: aborting due to 7 previous errors

View File

@ -1,7 +1,7 @@
//@aux-build:proc_macros.rs
#![feature(lint_reasons)]
#![deny(clippy::allow_attributes_without_reason)]
#![allow(unfulfilled_lint_expectations)]
#![allow(unfulfilled_lint_expectations, clippy::duplicated_attributes)]
extern crate proc_macros;
use proc_macros::{external, with_span};

View File

@ -1,8 +1,8 @@
error: `allow` attribute without specifying a reason
--> tests/ui/allow_attributes_without_reason.rs:4:1
|
LL | #![allow(unfulfilled_lint_expectations)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | #![allow(unfulfilled_lint_expectations, clippy::duplicated_attributes)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a reason at the end with `, reason = ".."`
note: the lint level is defined here

View File

@ -128,6 +128,19 @@ fn ignore_generic_clone<T: Clone>(a: &mut T, b: &T) {
*a = b.clone();
}
#[clippy::msrv = "1.62"]
fn msrv_1_62(mut a: String, b: String, c: &str) {
a.clone_from(&b);
// Should not be linted, as clone_into wasn't stabilized until 1.63
a = c.to_owned();
}
#[clippy::msrv = "1.63"]
fn msrv_1_63(mut a: String, b: String, c: &str) {
a.clone_from(&b);
c.clone_into(&mut a);
}
macro_rules! clone_inside {
($a:expr, $b: expr) => {
$a = $b.clone();

View File

@ -128,6 +128,19 @@ fn ignore_generic_clone<T: Clone>(a: &mut T, b: &T) {
*a = b.clone();
}
#[clippy::msrv = "1.62"]
fn msrv_1_62(mut a: String, b: String, c: &str) {
a = b.clone();
// Should not be linted, as clone_into wasn't stabilized until 1.63
a = c.to_owned();
}
#[clippy::msrv = "1.63"]
fn msrv_1_63(mut a: String, b: String, c: &str) {
a = b.clone();
a = c.to_owned();
}
macro_rules! clone_inside {
($a:expr, $b: expr) => {
$a = $b.clone();

View File

@ -67,41 +67,59 @@ error: assigning the result of `Clone::clone()` may be inefficient
LL | a = b.clone();
| ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:133:5
|
LL | a = b.clone();
| ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:140:5
|
LL | a = b.clone();
| ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:145:5
--> tests/ui/assigning_clones.rs:141:5
|
LL | a = c.to_owned();
| ^^^^^^^^^^^^^^^^ help: use `clone_into()`: `c.clone_into(&mut a)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:158:5
|
LL | *mut_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(mut_string)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:149:5
--> tests/ui/assigning_clones.rs:162:5
|
LL | mut_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut mut_string)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:170:5
--> tests/ui/assigning_clones.rs:183:5
|
LL | **mut_box_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:174:5
--> tests/ui/assigning_clones.rs:187:5
|
LL | **mut_box_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:178:5
--> tests/ui/assigning_clones.rs:191:5
|
LL | *mut_thing = ToOwned::to_owned(ref_str);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, mut_thing)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:182:5
--> tests/ui/assigning_clones.rs:195:5
|
LL | mut_thing = ToOwned::to_owned(ref_str);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, &mut mut_thing)`
error: aborting due to 17 previous errors
error: aborting due to 20 previous errors

View File

@ -11,8 +11,8 @@ use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::token::Star;
use syn::{
parse_macro_input, parse_quote, FnArg, ImplItem, ItemFn, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType,
Signature, TraitItem, Type,
parse_macro_input, parse_quote, FnArg, ImplItem, ItemFn, ItemImpl, ItemStruct, ItemTrait, Lifetime, Pat, PatIdent,
PatType, Signature, TraitItem, Type, Visibility,
};
#[proc_macro_attribute]
@ -101,9 +101,7 @@ pub fn fake_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut item = parse_macro_input!(item as ItemFn);
let span = item.block.brace_token.span;
if item.sig.asyncness.is_some() {
item.sig.asyncness = None;
}
item.sig.asyncness = None;
let crate_name = quote! { fake_crate };
let block = item.block;
@ -128,7 +126,7 @@ pub fn fake_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
#[proc_macro_attribute]
pub fn fake_desugar_await(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut async_fn = syn::parse_macro_input!(input as syn::ItemFn);
let mut async_fn = parse_macro_input!(input as syn::ItemFn);
for stmt in &mut async_fn.block.stmts {
if let syn::Stmt::Expr(syn::Expr::Match(syn::ExprMatch { expr: scrutinee, .. }), _) = stmt {
@ -145,3 +143,36 @@ pub fn fake_desugar_await(_args: TokenStream, input: TokenStream) -> TokenStream
quote!(#async_fn).into()
}
#[proc_macro_attribute]
pub fn rewrite_struct(_args: TokenStream, input: TokenStream) -> TokenStream {
let mut item_struct = parse_macro_input!(input as syn::ItemStruct);
// remove struct attributes including doc comments.
item_struct.attrs = vec![];
if let Visibility::Public(token) = item_struct.vis {
// set vis to `pub(crate)` to trigger `missing_docs_in_private_items` lint.
let new_vis: Visibility = syn::parse_quote_spanned!(token.span() => pub(crate));
item_struct.vis = new_vis;
}
if let syn::Fields::Named(fields) = &mut item_struct.fields {
for field in &mut fields.named {
// remove all attributes from fields as well.
field.attrs = vec![];
}
}
quote!(#item_struct).into()
}
#[proc_macro_attribute]
pub fn with_empty_docs(_attr: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as syn::Item);
let attrs: Vec<syn::Attribute> = vec![];
let doc_comment = "";
quote! {
#(#attrs)*
#[doc = #doc_comment]
#item
}
.into()
}

View File

@ -1,4 +1,5 @@
#![warn(clippy::await_holding_lock)]
#![allow(clippy::readonly_write_lock)]
// When adding or modifying a test, please do the same for parking_lot::Mutex.
mod std_mutex {

View File

@ -1,12 +1,12 @@
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:9:13
--> tests/ui/await_holding_lock.rs:10:13
|
LL | let guard = x.lock().unwrap();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:11:15
--> tests/ui/await_holding_lock.rs:12:15
|
LL | baz().await
| ^^^^^
@ -14,40 +14,40 @@ LL | baz().await
= help: to override `-D warnings` add `#[allow(clippy::await_holding_lock)]`
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:25:13
--> tests/ui/await_holding_lock.rs:26:13
|
LL | let guard = x.read().unwrap();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:27:15
--> tests/ui/await_holding_lock.rs:28:15
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:31:13
--> tests/ui/await_holding_lock.rs:32:13
|
LL | let mut guard = x.write().unwrap();
| ^^^^^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:33:15
--> tests/ui/await_holding_lock.rs:34:15
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:53:13
--> tests/ui/await_holding_lock.rs:54:13
|
LL | let guard = x.lock().unwrap();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:56:28
--> tests/ui/await_holding_lock.rs:57:28
|
LL | let second = baz().await;
| ^^^^^
@ -56,79 +56,79 @@ LL | let third = baz().await;
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:67:17
--> tests/ui/await_holding_lock.rs:68:17
|
LL | let guard = x.lock().unwrap();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:69:19
--> tests/ui/await_holding_lock.rs:70:19
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:80:17
--> tests/ui/await_holding_lock.rs:81:17
|
LL | let guard = x.lock().unwrap();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:82:19
--> tests/ui/await_holding_lock.rs:83:19
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:93:13
--> tests/ui/await_holding_lock.rs:94:13
|
LL | let guard = x.lock();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:95:15
--> tests/ui/await_holding_lock.rs:96:15
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:109:13
--> tests/ui/await_holding_lock.rs:110:13
|
LL | let guard = x.read();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:111:15
--> tests/ui/await_holding_lock.rs:112:15
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:115:13
--> tests/ui/await_holding_lock.rs:116:13
|
LL | let mut guard = x.write();
| ^^^^^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:117:15
--> tests/ui/await_holding_lock.rs:118:15
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:137:13
--> tests/ui/await_holding_lock.rs:138:13
|
LL | let guard = x.lock();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:140:28
--> tests/ui/await_holding_lock.rs:141:28
|
LL | let second = baz().await;
| ^^^^^
@ -137,40 +137,40 @@ LL | let third = baz().await;
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:151:17
--> tests/ui/await_holding_lock.rs:152:17
|
LL | let guard = x.lock();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:153:19
--> tests/ui/await_holding_lock.rs:154:19
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:164:17
--> tests/ui/await_holding_lock.rs:165:17
|
LL | let guard = x.lock();
| ^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:166:19
--> tests/ui/await_holding_lock.rs:167:19
|
LL | baz().await
| ^^^^^
error: this `MutexGuard` is held across an `await` point
--> tests/ui/await_holding_lock.rs:185:9
--> tests/ui/await_holding_lock.rs:186:9
|
LL | let mut guard = x.lock().unwrap();
| ^^^^^^^^^
|
= help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await
note: these are all the `await` points this lock is held through
--> tests/ui/await_holding_lock.rs:189:11
--> tests/ui/await_holding_lock.rs:190:11
|
LL | baz().await;
| ^^^^^

View File

@ -1,4 +1,4 @@
#![allow(unused, clippy::assertions_on_constants)]
#![allow(unused, clippy::assertions_on_constants, clippy::const_is_empty)]
#![warn(clippy::bool_assert_comparison)]
use std::ops::Not;

View File

@ -1,4 +1,4 @@
#![allow(unused, clippy::assertions_on_constants)]
#![allow(unused, clippy::assertions_on_constants, clippy::const_is_empty)]
#![warn(clippy::bool_assert_comparison)]
use std::ops::Not;

View File

@ -1,6 +1,8 @@
#![allow(dead_code)]
#![warn(clippy::cast_lossless)]
type U8 = u8;
fn main() {
// Test clippy::cast_lossless with casts to integer types
let _ = u8::from(true);
@ -19,6 +21,8 @@ fn main() {
// Test with an expression wrapped in parens
let _ = u16::from(true | false);
let _ = U8::from(true);
}
// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const,

View File

@ -1,6 +1,8 @@
#![allow(dead_code)]
#![warn(clippy::cast_lossless)]
type U8 = u8;
fn main() {
// Test clippy::cast_lossless with casts to integer types
let _ = true as u8;
@ -19,6 +21,8 @@ fn main() {
// Test with an expression wrapped in parens
let _ = (true | false) as u16;
let _ = true as U8;
}
// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const,

View File

@ -1,5 +1,5 @@
error: casting `bool` to `u8` is more cleanly stated with `u8::from(_)`
--> tests/ui/cast_lossless_bool.rs:6:13
--> tests/ui/cast_lossless_bool.rs:8:13
|
LL | let _ = true as u8;
| ^^^^^^^^^^ help: try: `u8::from(true)`
@ -8,82 +8,88 @@ LL | let _ = true as u8;
= help: to override `-D warnings` add `#[allow(clippy::cast_lossless)]`
error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)`
--> tests/ui/cast_lossless_bool.rs:7:13
--> tests/ui/cast_lossless_bool.rs:9:13
|
LL | let _ = true as u16;
| ^^^^^^^^^^^ help: try: `u16::from(true)`
error: casting `bool` to `u32` is more cleanly stated with `u32::from(_)`
--> tests/ui/cast_lossless_bool.rs:8:13
--> tests/ui/cast_lossless_bool.rs:10:13
|
LL | let _ = true as u32;
| ^^^^^^^^^^^ help: try: `u32::from(true)`
error: casting `bool` to `u64` is more cleanly stated with `u64::from(_)`
--> tests/ui/cast_lossless_bool.rs:9:13
--> tests/ui/cast_lossless_bool.rs:11:13
|
LL | let _ = true as u64;
| ^^^^^^^^^^^ help: try: `u64::from(true)`
error: casting `bool` to `u128` is more cleanly stated with `u128::from(_)`
--> tests/ui/cast_lossless_bool.rs:10:13
--> tests/ui/cast_lossless_bool.rs:12:13
|
LL | let _ = true as u128;
| ^^^^^^^^^^^^ help: try: `u128::from(true)`
error: casting `bool` to `usize` is more cleanly stated with `usize::from(_)`
--> tests/ui/cast_lossless_bool.rs:11:13
--> tests/ui/cast_lossless_bool.rs:13:13
|
LL | let _ = true as usize;
| ^^^^^^^^^^^^^ help: try: `usize::from(true)`
error: casting `bool` to `i8` is more cleanly stated with `i8::from(_)`
--> tests/ui/cast_lossless_bool.rs:13:13
--> tests/ui/cast_lossless_bool.rs:15:13
|
LL | let _ = true as i8;
| ^^^^^^^^^^ help: try: `i8::from(true)`
error: casting `bool` to `i16` is more cleanly stated with `i16::from(_)`
--> tests/ui/cast_lossless_bool.rs:14:13
--> tests/ui/cast_lossless_bool.rs:16:13
|
LL | let _ = true as i16;
| ^^^^^^^^^^^ help: try: `i16::from(true)`
error: casting `bool` to `i32` is more cleanly stated with `i32::from(_)`
--> tests/ui/cast_lossless_bool.rs:15:13
--> tests/ui/cast_lossless_bool.rs:17:13
|
LL | let _ = true as i32;
| ^^^^^^^^^^^ help: try: `i32::from(true)`
error: casting `bool` to `i64` is more cleanly stated with `i64::from(_)`
--> tests/ui/cast_lossless_bool.rs:16:13
--> tests/ui/cast_lossless_bool.rs:18:13
|
LL | let _ = true as i64;
| ^^^^^^^^^^^ help: try: `i64::from(true)`
error: casting `bool` to `i128` is more cleanly stated with `i128::from(_)`
--> tests/ui/cast_lossless_bool.rs:17:13
--> tests/ui/cast_lossless_bool.rs:19:13
|
LL | let _ = true as i128;
| ^^^^^^^^^^^^ help: try: `i128::from(true)`
error: casting `bool` to `isize` is more cleanly stated with `isize::from(_)`
--> tests/ui/cast_lossless_bool.rs:18:13
--> tests/ui/cast_lossless_bool.rs:20:13
|
LL | let _ = true as isize;
| ^^^^^^^^^^^^^ help: try: `isize::from(true)`
error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)`
--> tests/ui/cast_lossless_bool.rs:21:13
--> tests/ui/cast_lossless_bool.rs:23:13
|
LL | let _ = (true | false) as u16;
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::from(true | false)`
error: casting `bool` to `U8` is more cleanly stated with `U8::from(_)`
--> tests/ui/cast_lossless_bool.rs:25:13
|
LL | let _ = true as U8;
| ^^^^^^^^^^ help: try: `U8::from(true)`
error: casting `bool` to `u8` is more cleanly stated with `u8::from(_)`
--> tests/ui/cast_lossless_bool.rs:49:13
--> tests/ui/cast_lossless_bool.rs:53:13
|
LL | let _ = true as u8;
| ^^^^^^^^^^ help: try: `u8::from(true)`
error: aborting due to 14 previous errors
error: aborting due to 15 previous errors

View File

@ -1,11 +1,16 @@
#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)]
#![warn(clippy::cast_lossless)]
type F32 = f32;
type F64 = f64;
fn main() {
// Test clippy::cast_lossless with casts to floating-point types
let x0 = 1i8;
let _ = f32::from(x0);
let _ = f64::from(x0);
let _ = F32::from(x0);
let _ = F64::from(x0);
let x1 = 1u8;
let _ = f32::from(x1);
let _ = f64::from(x1);

View File

@ -1,11 +1,16 @@
#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)]
#![warn(clippy::cast_lossless)]
type F32 = f32;
type F64 = f64;
fn main() {
// Test clippy::cast_lossless with casts to floating-point types
let x0 = 1i8;
let _ = x0 as f32;
let _ = x0 as f64;
let _ = x0 as F32;
let _ = x0 as F64;
let x1 = 1u8;
let _ = x1 as f32;
let _ = x1 as f64;

View File

@ -1,5 +1,5 @@
error: casting `i8` to `f32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:7:13
--> tests/ui/cast_lossless_float.rs:10:13
|
LL | let _ = x0 as f32;
| ^^^^^^^^^ help: try: `f32::from(x0)`
@ -8,64 +8,76 @@ LL | let _ = x0 as f32;
= help: to override `-D warnings` add `#[allow(clippy::cast_lossless)]`
error: casting `i8` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:8:13
--> tests/ui/cast_lossless_float.rs:11:13
|
LL | let _ = x0 as f64;
| ^^^^^^^^^ help: try: `f64::from(x0)`
error: casting `i8` to `F32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:12:13
|
LL | let _ = x0 as F32;
| ^^^^^^^^^ help: try: `F32::from(x0)`
error: casting `i8` to `F64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:13:13
|
LL | let _ = x0 as F64;
| ^^^^^^^^^ help: try: `F64::from(x0)`
error: casting `u8` to `f32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:10:13
--> tests/ui/cast_lossless_float.rs:15:13
|
LL | let _ = x1 as f32;
| ^^^^^^^^^ help: try: `f32::from(x1)`
error: casting `u8` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:11:13
--> tests/ui/cast_lossless_float.rs:16:13
|
LL | let _ = x1 as f64;
| ^^^^^^^^^ help: try: `f64::from(x1)`
error: casting `i16` to `f32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:13:13
--> tests/ui/cast_lossless_float.rs:18:13
|
LL | let _ = x2 as f32;
| ^^^^^^^^^ help: try: `f32::from(x2)`
error: casting `i16` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:14:13
--> tests/ui/cast_lossless_float.rs:19:13
|
LL | let _ = x2 as f64;
| ^^^^^^^^^ help: try: `f64::from(x2)`
error: casting `u16` to `f32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:16:13
--> tests/ui/cast_lossless_float.rs:21:13
|
LL | let _ = x3 as f32;
| ^^^^^^^^^ help: try: `f32::from(x3)`
error: casting `u16` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:17:13
--> tests/ui/cast_lossless_float.rs:22:13
|
LL | let _ = x3 as f64;
| ^^^^^^^^^ help: try: `f64::from(x3)`
error: casting `i32` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:19:13
--> tests/ui/cast_lossless_float.rs:24:13
|
LL | let _ = x4 as f64;
| ^^^^^^^^^ help: try: `f64::from(x4)`
error: casting `u32` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:21:13
--> tests/ui/cast_lossless_float.rs:26:13
|
LL | let _ = x5 as f64;
| ^^^^^^^^^ help: try: `f64::from(x5)`
error: casting `f32` to `f64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_float.rs:24:13
--> tests/ui/cast_lossless_float.rs:29:13
|
LL | let _ = 1.0f32 as f64;
| ^^^^^^^^^^^^^ help: try: `f64::from(1.0f32)`
error: aborting due to 11 previous errors
error: aborting due to 13 previous errors

View File

@ -1,6 +1,9 @@
#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)]
#![warn(clippy::cast_lossless)]
type I64 = i64;
type U128 = u128;
fn main() {
// Test clippy::cast_lossless with casts to integer types
let _ = i16::from(1i8);
@ -24,6 +27,13 @@ fn main() {
// Test with an expression wrapped in parens
let _ = u16::from(1u8 + 1u8);
let _ = I64::from(1i8);
// Do not lint if destination type is u128
// see https://github.com/rust-lang/rust-clippy/issues/12492
let _ = 1u8 as u128;
let _ = 1u8 as U128;
}
// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const,

View File

@ -1,6 +1,9 @@
#![allow(clippy::no_effect, clippy::unnecessary_operation, dead_code)]
#![warn(clippy::cast_lossless)]
type I64 = i64;
type U128 = u128;
fn main() {
// Test clippy::cast_lossless with casts to integer types
let _ = 1i8 as i16;
@ -24,6 +27,13 @@ fn main() {
// Test with an expression wrapped in parens
let _ = (1u8 + 1u8) as u16;
let _ = 1i8 as I64;
// Do not lint if destination type is u128
// see https://github.com/rust-lang/rust-clippy/issues/12492
let _ = 1u8 as u128;
let _ = 1u8 as U128;
}
// The lint would suggest using `f64::from(input)` here but the `XX::from` function is not const,

View File

@ -1,5 +1,5 @@
error: casting `i8` to `i16` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:6:13
--> tests/ui/cast_lossless_integer.rs:9:13
|
LL | let _ = 1i8 as i16;
| ^^^^^^^^^^ help: try: `i16::from(1i8)`
@ -8,124 +8,130 @@ LL | let _ = 1i8 as i16;
= help: to override `-D warnings` add `#[allow(clippy::cast_lossless)]`
error: casting `i8` to `i32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:7:13
--> tests/ui/cast_lossless_integer.rs:10:13
|
LL | let _ = 1i8 as i32;
| ^^^^^^^^^^ help: try: `i32::from(1i8)`
error: casting `i8` to `i64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:8:13
--> tests/ui/cast_lossless_integer.rs:11:13
|
LL | let _ = 1i8 as i64;
| ^^^^^^^^^^ help: try: `i64::from(1i8)`
error: casting `u8` to `i16` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:9:13
--> tests/ui/cast_lossless_integer.rs:12:13
|
LL | let _ = 1u8 as i16;
| ^^^^^^^^^^ help: try: `i16::from(1u8)`
error: casting `u8` to `i32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:10:13
--> tests/ui/cast_lossless_integer.rs:13:13
|
LL | let _ = 1u8 as i32;
| ^^^^^^^^^^ help: try: `i32::from(1u8)`
error: casting `u8` to `i64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:11:13
--> tests/ui/cast_lossless_integer.rs:14:13
|
LL | let _ = 1u8 as i64;
| ^^^^^^^^^^ help: try: `i64::from(1u8)`
error: casting `u8` to `u16` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:12:13
--> tests/ui/cast_lossless_integer.rs:15:13
|
LL | let _ = 1u8 as u16;
| ^^^^^^^^^^ help: try: `u16::from(1u8)`
error: casting `u8` to `u32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:13:13
--> tests/ui/cast_lossless_integer.rs:16:13
|
LL | let _ = 1u8 as u32;
| ^^^^^^^^^^ help: try: `u32::from(1u8)`
error: casting `u8` to `u64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:14:13
--> tests/ui/cast_lossless_integer.rs:17:13
|
LL | let _ = 1u8 as u64;
| ^^^^^^^^^^ help: try: `u64::from(1u8)`
error: casting `i16` to `i32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:15:13
--> tests/ui/cast_lossless_integer.rs:18:13
|
LL | let _ = 1i16 as i32;
| ^^^^^^^^^^^ help: try: `i32::from(1i16)`
error: casting `i16` to `i64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:16:13
--> tests/ui/cast_lossless_integer.rs:19:13
|
LL | let _ = 1i16 as i64;
| ^^^^^^^^^^^ help: try: `i64::from(1i16)`
error: casting `u16` to `i32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:17:13
--> tests/ui/cast_lossless_integer.rs:20:13
|
LL | let _ = 1u16 as i32;
| ^^^^^^^^^^^ help: try: `i32::from(1u16)`
error: casting `u16` to `i64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:18:13
--> tests/ui/cast_lossless_integer.rs:21:13
|
LL | let _ = 1u16 as i64;
| ^^^^^^^^^^^ help: try: `i64::from(1u16)`
error: casting `u16` to `u32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:19:13
--> tests/ui/cast_lossless_integer.rs:22:13
|
LL | let _ = 1u16 as u32;
| ^^^^^^^^^^^ help: try: `u32::from(1u16)`
error: casting `u16` to `u64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:20:13
--> tests/ui/cast_lossless_integer.rs:23:13
|
LL | let _ = 1u16 as u64;
| ^^^^^^^^^^^ help: try: `u64::from(1u16)`
error: casting `i32` to `i64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:21:13
--> tests/ui/cast_lossless_integer.rs:24:13
|
LL | let _ = 1i32 as i64;
| ^^^^^^^^^^^ help: try: `i64::from(1i32)`
error: casting `u32` to `i64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:22:13
--> tests/ui/cast_lossless_integer.rs:25:13
|
LL | let _ = 1u32 as i64;
| ^^^^^^^^^^^ help: try: `i64::from(1u32)`
error: casting `u32` to `u64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:23:13
--> tests/ui/cast_lossless_integer.rs:26:13
|
LL | let _ = 1u32 as u64;
| ^^^^^^^^^^^ help: try: `u64::from(1u32)`
error: casting `u8` to `u16` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:26:13
--> tests/ui/cast_lossless_integer.rs:29:13
|
LL | let _ = (1u8 + 1u8) as u16;
| ^^^^^^^^^^^^^^^^^^ help: try: `u16::from(1u8 + 1u8)`
error: casting `i8` to `I64` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:31:13
|
LL | let _ = 1i8 as I64;
| ^^^^^^^^^^ help: try: `I64::from(1i8)`
error: casting `i8` to `i32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:60:13
--> tests/ui/cast_lossless_integer.rs:70:13
|
LL | let _ = sign_cast!(x, u8, i8) as i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::from(sign_cast!(x, u8, i8))`
error: casting `i8` to `i32` may become silently lossy if you later change the type
--> tests/ui/cast_lossless_integer.rs:61:13
--> tests/ui/cast_lossless_integer.rs:71:13
|
LL | let _ = (sign_cast!(x, u8, i8) + 1) as i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `i32::from(sign_cast!(x, u8, i8) + 1)`
error: aborting due to 21 previous errors
error: aborting due to 22 previous errors

174
tests/ui/const_is_empty.rs Normal file
View File

@ -0,0 +1,174 @@
#![feature(inline_const)]
#![warn(clippy::const_is_empty)]
#![allow(clippy::needless_late_init, unused_must_use)]
fn test_literal() {
if "".is_empty() {
//~^ERROR: this expression always evaluates to true
}
if "foobar".is_empty() {
//~^ERROR: this expression always evaluates to false
}
}
fn test_byte_literal() {
if b"".is_empty() {
//~^ERROR: this expression always evaluates to true
}
if b"foobar".is_empty() {
//~^ERROR: this expression always evaluates to false
}
}
fn test_no_mut() {
let mut empty = "";
if empty.is_empty() {
// No lint because it is mutable
}
}
fn test_propagated() {
let empty = "";
let non_empty = "foobar";
let empty2 = empty;
let non_empty2 = non_empty;
if empty2.is_empty() {
//~^ERROR: this expression always evaluates to true
}
if non_empty2.is_empty() {
//~^ERROR: this expression always evaluates to false
}
}
const EMPTY_STR: &str = "";
const NON_EMPTY_STR: &str = "foo";
const EMPTY_BSTR: &[u8] = b"";
const NON_EMPTY_BSTR: &[u8] = b"foo";
const EMPTY_U8_SLICE: &[u8] = &[];
const NON_EMPTY_U8_SLICE: &[u8] = &[1, 2];
const EMPTY_SLICE: &[u32] = &[];
const NON_EMPTY_SLICE: &[u32] = &[1, 2];
const NON_EMPTY_SLICE_REPEAT: &[u32] = &[1; 2];
const EMPTY_ARRAY: [u32; 0] = [];
const EMPTY_ARRAY_REPEAT: [u32; 0] = [1; 0];
const NON_EMPTY_ARRAY: [u32; 2] = [1, 2];
const NON_EMPTY_ARRAY_REPEAT: [u32; 2] = [1; 2];
const EMPTY_REF_ARRAY: &[u32; 0] = &[];
const NON_EMPTY_REF_ARRAY: &[u32; 3] = &[1, 2, 3];
fn test_from_const() {
let _ = EMPTY_STR.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_STR.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_BSTR.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_BSTR.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = EMPTY_ARRAY_REPEAT.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = EMPTY_U8_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_U8_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = NON_EMPTY_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = NON_EMPTY_ARRAY_REPEAT.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_REF_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_REF_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = NON_EMPTY_SLICE_REPEAT.is_empty();
//~^ ERROR: this expression always evaluates to false
}
fn main() {
let value = "foobar";
let _ = value.is_empty();
//~^ ERROR: this expression always evaluates to false
let x = value;
let _ = x.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = "".is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = b"".is_empty();
//~^ ERROR: this expression always evaluates to true
}
fn str_from_arg(var: &str) {
var.is_empty();
// Do not lint, we know nothiny about var
}
fn update_str() {
let mut value = "duck";
value = "penguin";
let _ = value.is_empty();
// Do not lint since value is mutable
}
fn macros() {
// Content from Macro
let file = include_str!("const_is_empty.rs");
let _ = file.is_empty();
// No lint because initializer comes from a macro result
let var = env!("PATH");
let _ = var.is_empty();
// No lint because initializer comes from a macro result
}
fn conditional_value() {
let value;
if true {
value = "hey";
} else {
value = "hej";
}
let _ = value.is_empty();
// Do not lint, current constant folding is too simple to detect this
}
fn cfg_conditioned() {
#[cfg(test)]
let val = "";
#[cfg(not(test))]
let val = "foo";
let _ = val.is_empty();
// Do not lint, value depend on a #[cfg(…)] directive
}
fn not_cfg_conditioned() {
let val = "";
#[cfg(not(target_os = "inexistent"))]
let _ = val.is_empty();
//~^ ERROR: this expression always evaluates to true
}
const fn const_rand() -> &'static str {
"17"
}
fn const_expressions() {
let _ = const { if true { "1" } else { "2" } }.is_empty();
// Do not lint, we do not recurse into boolean expressions
let _ = const_rand().is_empty();
// Do not lint, we do not recurse into functions
}
fn constant_from_external_crate() {
let _ = std::env::consts::EXE_EXTENSION.is_empty();
// Do not lint, `exe_ext` comes from the `std` crate
}

View File

@ -0,0 +1,161 @@
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:6:8
|
LL | if "".is_empty() {
| ^^^^^^^^^^^^^
|
= note: `-D clippy::const-is-empty` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::const_is_empty)]`
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:9:8
|
LL | if "foobar".is_empty() {
| ^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:15:8
|
LL | if b"".is_empty() {
| ^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:18:8
|
LL | if b"foobar".is_empty() {
| ^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:35:8
|
LL | if empty2.is_empty() {
| ^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:38:8
|
LL | if non_empty2.is_empty() {
| ^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:60:13
|
LL | let _ = EMPTY_STR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:62:13
|
LL | let _ = NON_EMPTY_STR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:64:13
|
LL | let _ = EMPTY_BSTR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:66:13
|
LL | let _ = NON_EMPTY_BSTR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:68:13
|
LL | let _ = EMPTY_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:70:13
|
LL | let _ = EMPTY_ARRAY_REPEAT.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:72:13
|
LL | let _ = EMPTY_U8_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:74:13
|
LL | let _ = NON_EMPTY_U8_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:76:13
|
LL | let _ = NON_EMPTY_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:78:13
|
LL | let _ = NON_EMPTY_ARRAY_REPEAT.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:80:13
|
LL | let _ = EMPTY_REF_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:82:13
|
LL | let _ = NON_EMPTY_REF_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:84:13
|
LL | let _ = EMPTY_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:86:13
|
LL | let _ = NON_EMPTY_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:88:13
|
LL | let _ = NON_EMPTY_SLICE_REPEAT.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:94:13
|
LL | let _ = value.is_empty();
| ^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:97:13
|
LL | let _ = x.is_empty();
| ^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:99:13
|
LL | let _ = "".is_empty();
| ^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:101:13
|
LL | let _ = b"".is_empty();
| ^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:155:13
|
LL | let _ = val.is_empty();
| ^^^^^^^^^^^^^^
error: aborting due to 26 previous errors

View File

@ -0,0 +1,7 @@
#![warn(clippy::needless_return)]
fn main() {
if (true) {
// anything一些中文
}
}

View File

@ -0,0 +1,8 @@
#![warn(clippy::needless_return)]
fn main() {
if (true) {
// anything一些中文
return;
}
}

View File

@ -0,0 +1,19 @@
error: unneeded `return` statement
--> tests/ui/crashes/ice-12491.rs:5:24
|
LL | // anything一些中文
| ____________________________^
LL | | return;
| |______________^
|
= note: `-D clippy::needless-return` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::needless_return)]`
help: remove `return`
|
LL - // anything一些中文
LL - return;
LL + // anything一些中文
|
error: aborting due to 1 previous error

View File

@ -0,0 +1,111 @@
#![warn(clippy::dbg_macro)]
#![allow(clippy::unnecessary_operation, clippy::no_effect)]
fn foo(n: u32) -> u32 {
if let Some(n) = n.checked_sub(4) { n } else { n }
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
fn bar(_: ()) {}
fn factorial(n: u32) -> u32 {
if n <= 1 {
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
1
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
} else {
n * factorial(n - 1)
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
}
fn main() {
42;
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
foo(3) + factorial(4);
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
(1, 2, 3, 4, 5);
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
fn issue9914() {
macro_rules! foo {
($x:expr) => {
$x;
};
}
macro_rules! foo2 {
($x:expr) => {
$x;
};
}
macro_rules! expand_to_dbg {
() => {
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
};
}
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
#[allow(clippy::let_unit_value)]
let _ = ();
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
bar(());
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
foo!(());
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
foo2!(foo!(()));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
expand_to_dbg!();
}
mod issue7274 {
trait Thing<'b> {
fn foo(&self);
}
macro_rules! define_thing {
($thing:ident, $body:expr) => {
impl<'a> Thing<'a> for $thing {
fn foo<'b>(&self) {
$body
}
}
};
}
struct MyThing;
define_thing!(MyThing, {
2;
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
});
}
#[test]
pub fn issue8481() {
1;
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
#[cfg(test)]
fn foo2() {
1;
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
#[cfg(test)]
mod mod1 {
fn func() {
1;
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
}
mod issue12131 {
fn dbg_in_print(s: &str) {
println!("dbg: {:?}", s);
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
print!("{}", s);
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
}

View File

@ -1,9 +1,5 @@
//@no-rustfix
#![warn(clippy::dbg_macro)]
#[path = "auxiliary/submodule.rs"]
mod submodule;
#![allow(clippy::unnecessary_operation, clippy::no_effect)]
fn foo(n: u32) -> u32 {
if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
@ -25,12 +21,8 @@ fn factorial(n: u32) -> u32 {
fn main() {
dbg!(42);
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
dbg!(dbg!(dbg!(42)));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
foo(3) + dbg!(factorial(4));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
dbg!(1, 2, dbg!(3, 4));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
dbg!(1, 2, 3, 4, 5);
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
@ -49,6 +41,7 @@ fn issue9914() {
macro_rules! expand_to_dbg {
() => {
dbg!();
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
};
}
@ -107,3 +100,12 @@ mod mod1 {
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
}
mod issue12131 {
fn dbg_in_print(s: &str) {
println!("dbg: {:?}", dbg!(s));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
print!("{}", dbg!(s));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}
}

View File

@ -1,30 +1,18 @@
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/auxiliary/submodule.rs:2:5
|
LL | dbg!();
| ^^^^^^^
|
= note: `-D clippy::dbg-macro` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::dbg_macro)]`
help: remove the invocation before committing it to a version control system
|
LL - dbg!();
LL +
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:9:22
--> tests/ui/dbg_macro/dbg_macro.rs:5:22
|
LL | if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::dbg-macro` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::dbg_macro)]`
help: remove the invocation before committing it to a version control system
|
LL | if let Some(n) = n.checked_sub(4) { n } else { n }
| ~~~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:15:8
--> tests/ui/dbg_macro/dbg_macro.rs:11:8
|
LL | if dbg!(n <= 1) {
| ^^^^^^^^^^^^
@ -35,7 +23,7 @@ LL | if n <= 1 {
| ~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:17:9
--> tests/ui/dbg_macro/dbg_macro.rs:13:9
|
LL | dbg!(1)
| ^^^^^^^
@ -46,7 +34,7 @@ LL | 1
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:20:9
--> tests/ui/dbg_macro/dbg_macro.rs:16:9
|
LL | dbg!(n * factorial(n - 1))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -57,7 +45,7 @@ LL | n * factorial(n - 1)
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:26:5
--> tests/ui/dbg_macro/dbg_macro.rs:22:5
|
LL | dbg!(42);
| ^^^^^^^^
@ -68,18 +56,7 @@ LL | 42;
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:28:5
|
LL | dbg!(dbg!(dbg!(42)));
| ^^^^^^^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | dbg!(dbg!(42));
| ~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:30:14
--> tests/ui/dbg_macro/dbg_macro.rs:24:14
|
LL | foo(3) + dbg!(factorial(4));
| ^^^^^^^^^^^^^^^^^^
@ -90,18 +67,7 @@ LL | foo(3) + factorial(4);
| ~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:32:5
|
LL | dbg!(1, 2, dbg!(3, 4));
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | (1, 2, dbg!(3, 4));
| ~~~~~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:34:5
--> tests/ui/dbg_macro/dbg_macro.rs:26:5
|
LL | dbg!(1, 2, 3, 4, 5);
| ^^^^^^^^^^^^^^^^^^^
@ -112,7 +78,7 @@ LL | (1, 2, 3, 4, 5);
| ~~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:55:5
--> tests/ui/dbg_macro/dbg_macro.rs:48:5
|
LL | dbg!();
| ^^^^^^^
@ -124,7 +90,7 @@ LL +
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:58:13
--> tests/ui/dbg_macro/dbg_macro.rs:51:13
|
LL | let _ = dbg!();
| ^^^^^^
@ -135,7 +101,7 @@ LL | let _ = ();
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:60:9
--> tests/ui/dbg_macro/dbg_macro.rs:53:9
|
LL | bar(dbg!());
| ^^^^^^
@ -146,7 +112,7 @@ LL | bar(());
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:62:10
--> tests/ui/dbg_macro/dbg_macro.rs:55:10
|
LL | foo!(dbg!());
| ^^^^^^
@ -157,7 +123,7 @@ LL | foo!(());
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:64:16
--> tests/ui/dbg_macro/dbg_macro.rs:57:16
|
LL | foo2!(foo!(dbg!()));
| ^^^^^^
@ -168,7 +134,23 @@ LL | foo2!(foo!(()));
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:86:9
--> tests/ui/dbg_macro/dbg_macro.rs:43:13
|
LL | dbg!();
| ^^^^^^^
...
LL | expand_to_dbg!();
| ---------------- in this macro invocation
|
= note: this error originates in the macro `expand_to_dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: remove the invocation before committing it to a version control system
|
LL - dbg!();
LL +
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:79:9
|
LL | dbg!(2);
| ^^^^^^^
@ -179,7 +161,7 @@ LL | 2;
| ~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:93:5
--> tests/ui/dbg_macro/dbg_macro.rs:86:5
|
LL | dbg!(1);
| ^^^^^^^
@ -190,7 +172,7 @@ LL | 1;
| ~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:99:5
--> tests/ui/dbg_macro/dbg_macro.rs:92:5
|
LL | dbg!(1);
| ^^^^^^^
@ -201,7 +183,7 @@ LL | 1;
| ~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:106:9
--> tests/ui/dbg_macro/dbg_macro.rs:99:9
|
LL | dbg!(1);
| ^^^^^^^
@ -211,5 +193,27 @@ help: remove the invocation before committing it to a version control system
LL | 1;
| ~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:106:31
|
LL | println!("dbg: {:?}", dbg!(s));
| ^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | println!("dbg: {:?}", s);
| ~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro.rs:108:22
|
LL | print!("{}", dbg!(s));
| ^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | print!("{}", s);
| ~
error: aborting due to 19 previous errors

View File

@ -0,0 +1,12 @@
//@no-rustfix
#![warn(clippy::dbg_macro)]
#[path = "auxiliary/submodule.rs"]
mod submodule;
fn main() {
dbg!(dbg!(dbg!(42)));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
dbg!(1, 2, dbg!(3, 4));
//~^ ERROR: the `dbg!` macro is intended as a debugging tool
}

View File

@ -0,0 +1,71 @@
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/auxiliary/submodule.rs:2:5
|
LL | dbg!();
| ^^^^^^^
|
= note: `-D clippy::dbg-macro` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::dbg_macro)]`
help: remove the invocation before committing it to a version control system
|
LL - dbg!();
LL +
|
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro_unfixable.rs:8:5
|
LL | dbg!(dbg!(dbg!(42)));
| ^^^^^^^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | dbg!(dbg!(42));
| ~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro_unfixable.rs:8:10
|
LL | dbg!(dbg!(dbg!(42)));
| ^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | dbg!(dbg!(42));
| ~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro_unfixable.rs:8:15
|
LL | dbg!(dbg!(dbg!(42)));
| ^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | dbg!(dbg!(42));
| ~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro_unfixable.rs:10:5
|
LL | dbg!(1, 2, dbg!(3, 4));
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | (1, 2, dbg!(3, 4));
| ~~~~~~~~~~~~~~~~~~
error: the `dbg!` macro is intended as a debugging tool
--> tests/ui/dbg_macro/dbg_macro_unfixable.rs:10:16
|
LL | dbg!(1, 2, dbg!(3, 4));
| ^^^^^^^^^^
|
help: remove the invocation before committing it to a version control system
|
LL | dbg!(1, 2, (3, 4));
| ~~~~~~
error: aborting due to 6 previous errors

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