Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2024-10-18 13:25:37 +02:00
commit 224d1e323a
No known key found for this signature in database
GPG Key ID: 1CA0DF2AF59D68A5
186 changed files with 4085 additions and 1775 deletions

2
.github/deploy.sh vendored
View File

@ -8,8 +8,8 @@ rm -rf out/master/ || exit 0
echo "Making the docs for master"
mkdir out/master/
cp util/gh-pages/index.html out/master
cp util/gh-pages/theme.js out/master
cp util/gh-pages/script.js out/master
cp util/gh-pages/lints.json out/master
cp util/gh-pages/style.css out/master
if [[ -n $TAG_NAME ]]; then

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ out
# gh pages docs
util/gh-pages/lints.json
util/gh-pages/index.html
# rustfmt backups
*.rs.bk

View File

@ -6,11 +6,46 @@ document.
## Unreleased / Beta / In Rust Nightly
[b794b8e0...master](https://github.com/rust-lang/rust-clippy/compare/b794b8e0...master)
[0f8eabd6...master](https://github.com/rust-lang/rust-clippy/compare/0f8eabd6...master)
## Rust 1.82
Current stable, released 2024-10-17
[View all 108 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2024-07-11T20%3A12%3A07Z..2024-08-24T20%3A55%3A35Z+base%3Amaster)
### New Lints
* Added [`too_long_first_doc_paragraph`] to `nursery`
[#12993](https://github.com/rust-lang/rust-clippy/pull/12993)
* Added [`unused_result_ok`] to `restriction`
[#12150](https://github.com/rust-lang/rust-clippy/pull/12150)
* Added [`pathbuf_init_then_push`] to `restriction`
[#11700](https://github.com/rust-lang/rust-clippy/pull/11700)
### Enhancements
* [`explicit_iter_loop`]: Now respects the `msrv` configuration
[#13288](https://github.com/rust-lang/rust-clippy/pull/13288)
* [`assigning_clones`]: No longer lints in test code
[#13273](https://github.com/rust-lang/rust-clippy/pull/13273)
* [`inconsistent_struct_constructor`]: Lint attributes now work on the struct definition
[#13211](https://github.com/rust-lang/rust-clippy/pull/13211)
* [`set_contains_or_insert`]: Now also checks for `BTreeSet`
[#13053](https://github.com/rust-lang/rust-clippy/pull/13053)
* [`doc_markdown`]: Added the following identifiers to [`doc-valid-idents`]: AccessKit,
CoreFoundation, CoreGraphics, CoreText, Direct2D, Direct3D, DirectWrite, PostScript,
OpenAL, OpenType, WebRTC, WebSocket, WebTransport, NetBSD, and OpenBSD
[#13093](https://github.com/rust-lang/rust-clippy/pull/13093)
### ICE Fixes
* [`uninit_vec`]
[rust#128720](https://github.com/rust-lang/rust/pull/128720)
## Rust 1.81
Current stable, released 2024-09-05
Released 2024-09-05
### New Lints
@ -5621,6 +5656,7 @@ Released 2018-09-13
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
[`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp
[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
@ -5874,6 +5910,7 @@ Released 2018-09-13
[`ref_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
[`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns
[`regex_creation_in_loops`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_creation_in_loops
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
[`renamed_function_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
@ -6027,6 +6064,7 @@ Released 2018-09-13
[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_literal_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_bound
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap
[`unnecessary_map_on_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_map_on_constructor
[`unnecessary_min_or_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_min_or_max

View File

@ -23,7 +23,7 @@ path = "src/driver.rs"
[dependencies]
clippy_config = { path = "clippy_config" }
clippy_lints = { path = "clippy_lints" }
rustc_tools_util = "0.3.0"
rustc_tools_util = "0.4.0"
tempfile = { version = "3.3", optional = true }
termize = "0.1"
color-print = "0.3.4"
@ -39,6 +39,8 @@ toml = "0.7.3"
walkdir = "2.3"
filetime = "0.2.9"
itertools = "0.12"
pulldown-cmark = "0.11"
rinja = { version = "0.3", default-features = false, features = ["config"] }
# UI test dependencies
clippy_utils = { path = "clippy_utils" }
@ -50,7 +52,7 @@ parking_lot = "0.12"
tokio = { version = "1", features = ["io-util"] }
[build-dependencies]
rustc_tools_util = "0.3.0"
rustc_tools_util = "0.4.0"
[features]
integration = ["tempfile"]
@ -67,3 +69,10 @@ harness = false
[[test]]
name = "dogfood"
harness = false
# quine-mc_cluskey makes up a significant part of the runtime in dogfood
# due to the number of conditions in the clippy_lints crate
# and enabling optimizations for that specific dependency helps a bit
# without increasing total build times.
[profile.dev.package.quine-mc_cluskey]
opt-level = 3

View File

@ -329,7 +329,7 @@ arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
## `array-size-threshold`
The maximum allowed size for arrays on the stack
**Default Value:** `512000`
**Default Value:** `16384`
---
**Affected lints:**

View File

@ -97,6 +97,30 @@ impl ConfError {
}
}
// Remove code tags and code behind '# 's, as they are not needed for the lint docs and --explain
pub fn sanitize_explanation(raw_docs: &str) -> String {
// Remove tags and hidden code:
let mut explanation = String::with_capacity(128);
let mut in_code = false;
for line in raw_docs.lines().map(str::trim) {
if let Some(lang) = line.strip_prefix("```") {
let tag = lang.split_once(',').map_or(lang, |(left, _)| left);
if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") {
explanation += "```rust\n";
} else {
explanation += line;
explanation.push('\n');
}
in_code = !in_code;
} else if !(in_code && line.starts_with("# ")) {
explanation += line;
explanation.push('\n');
}
}
explanation
}
macro_rules! wrap_option {
() => {
None
@ -366,7 +390,7 @@ define_Conf! {
arithmetic_side_effects_allowed_unary: Vec<String> = <_>::default(),
/// The maximum allowed size for arrays on the stack
#[lints(large_const_arrays, large_stack_arrays)]
array_size_threshold: u64 = 512_000,
array_size_threshold: u64 = 16 * 1024,
/// Suppress lints whenever the suggested change would cause breakage for other crates.
#[lints(
box_collection,

View File

@ -26,5 +26,5 @@ mod metadata;
pub mod msrvs;
pub mod types;
pub use conf::{Conf, get_configuration_metadata, lookup_conf_file};
pub use conf::{Conf, get_configuration_metadata, lookup_conf_file, sanitize_explanation};
pub use metadata::ClippyConfiguration;

View File

@ -17,8 +17,7 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,83,0 { CONST_EXTERN_FN }
1,83,0 { CONST_FLOAT_BITS_CONV }
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY }
1,82,0 { IS_NONE_OR }
1,81,0 { LINT_REASONS_STABILIZATION }
1,80,0 { BOX_INTO_ITER}

View File

@ -9,7 +9,7 @@ aho-corasick = "1.0"
clap = { version = "4.4", features = ["derive"] }
indoc = "1.0"
itertools = "0.12"
opener = "0.6"
opener = "0.7"
shell-escape = "0.1"
walkdir = "2.3"

View File

@ -207,13 +207,13 @@ pub(crate) fn get_stabilization_version() -> String {
fn get_test_file_contents(lint_name: &str, msrv: bool) -> String {
let mut test = formatdoc!(
r#"
r"
#![warn(clippy::{lint_name})]
fn main() {{
// test code goes here
}}
"#
"
);
if msrv {
@ -272,23 +272,23 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
result.push_str(&if enable_msrv {
formatdoc!(
r#"
r"
use clippy_config::msrvs::{{self, Msrv}};
use clippy_config::Conf;
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
use rustc_session::impl_lint_pass;
"#
"
)
} else {
formatdoc!(
r#"
r"
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}}};
use rustc_session::declare_lint_pass;
"#
"
)
});
@ -296,7 +296,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
result.push_str(&if enable_msrv {
formatdoc!(
r#"
r"
pub struct {name_camel} {{
msrv: Msrv,
}}
@ -315,15 +315,15 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
// TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed.
// TODO: Update msrv config comment in `clippy_config/src/conf.rs`
"#
"
)
} else {
formatdoc!(
r#"
r"
declare_lint_pass!({name_camel} => [{name_upper}]);
impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
"#
"
)
});
@ -416,7 +416,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
} else {
let _: fmt::Result = writedoc!(
lint_file_contents,
r#"
r"
use rustc_lint::{{{context_import}, LintContext}};
use super::{name_upper};
@ -425,7 +425,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
pub(super) fn check(cx: &{context_import}{pass_lifetimes}) {{
todo!();
}}
"#
"
);
}
@ -470,7 +470,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
});
// Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token == TokenKind::Ident) {
while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token_kind == TokenKind::Ident) {
let mut iter = iter
.by_ref()
.filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. }));
@ -480,7 +480,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
// matches `!{`
match_tokens!(iter, Bang OpenBrace);
if let Some(LintDeclSearchResult { range, .. }) =
iter.find(|result| result.token == TokenKind::CloseBrace)
iter.find(|result| result.token_kind == TokenKind::CloseBrace)
{
last_decl_curly_offset = Some(range.end);
}

View File

@ -19,7 +19,9 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
});
loop {
if mtime("util/gh-pages/lints.json") < mtime("clippy_lints/src") {
let index_time = mtime("util/gh-pages/index.html");
if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") {
Command::new(env::var("CARGO").unwrap_or("cargo".into()))
.arg("collect-metadata")
.spawn()

View File

@ -13,7 +13,6 @@ arrayvec = { version = "0.7", default-features = false }
cargo_metadata = "0.18"
clippy_config = { path = "../clippy_config" }
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
itertools = "0.12"
quine-mc_cluskey = "0.2"
regex-syntax = "0.8"

View File

@ -47,7 +47,7 @@ impl LateLintPass<'_> for BoxDefault {
// And the call is that of a `Box` method
&& path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box())
// And the single argument to the call is another function call
// This is the `T::default()` of `Box::new(T::default())`
// This is the `T::default()` (or default equivalent) of `Box::new(T::default())`
&& let ExprKind::Call(arg_path, _) = arg.kind
// And we are not in a foreign crate's macro
&& !in_external_macro(cx.sess(), expr.span)

View File

@ -41,7 +41,7 @@ impl EarlyLintPass for ByteCharSlice {
"can be more succinctly written as a byte str",
"try",
format!("b\"{slice}\""),
Applicability::MaybeIncorrect,
Applicability::MachineApplicable,
);
}
}

View File

@ -19,7 +19,7 @@ pub(super) fn check(
if msrv.meets(msrvs::UNSIGNED_ABS)
&& let ty::Int(from) = cast_from.kind()
&& let ty::Uint(to) = cast_to.kind()
&& let ExprKind::MethodCall(method_path, receiver, ..) = cast_expr.kind
&& let ExprKind::MethodCall(method_path, receiver, [], _) = cast_expr.kind
&& method_path.ident.name.as_str() == "abs"
{
let span = if from.bit_width() == to.bit_width() {

View File

@ -19,7 +19,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
cx.typeck_results().expr_ty(expr),
);
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, self_arg, ..) = &expr.kind {
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind {
if method_path.ident.name == sym!(cast)
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args

View File

@ -34,7 +34,7 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Call(func, [arg, ..]) = expr.kind
if let ExprKind::Call(func, [arg]) = expr.kind
&& let ExprKind::Path(ref path) = func.kind
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id)

View File

@ -0,0 +1,162 @@
#[macro_export]
#[allow(clippy::crate_in_macro_def)]
macro_rules! declare_clippy_lint {
(@
$(#[doc = $lit:literal])*
pub $lint_name:ident,
$category:ident,
$lintcategory:expr,
$desc:literal,
$version_expr:expr,
$version_lit:literal
) => {
rustc_session::declare_tool_lint! {
$(#[doc = $lit])*
#[clippy::version = $version_lit]
pub clippy::$lint_name,
$category,
$desc,
report_in_external_macro:true
}
pub(crate) static ${concat($lint_name, _INFO)}: &'static crate::LintInfo = &crate::LintInfo {
lint: &$lint_name,
category: $lintcategory,
explanation: concat!($($lit,"\n",)*),
location: concat!(file!(), "#L", line!()),
version: $version_expr
};
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
restriction,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Restriction, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
style,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Style, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
correctness,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Deny, crate::LintCategory::Correctness, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
perf,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Perf, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
complexity,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Complexity, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
suspicious,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
nursery,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Nursery, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
pedantic,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
#[clippy::version = $version:literal]
pub $lint_name:ident,
cargo,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Cargo, $desc,
Some($version), $version
}
};
(
$(#[doc = $lit:literal])*
pub $lint_name:ident,
internal,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Internal, $desc,
None, "0.0.0"
}
};
}

View File

@ -306,6 +306,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO,
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
@ -639,6 +640,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::ref_patterns::REF_PATTERNS_INFO,
crate::reference::DEREF_ADDROF_INFO,
crate::regex::INVALID_REGEX_INFO,
crate::regex::REGEX_CREATION_IN_LOOPS_INFO,
crate::regex::TRIVIAL_REGEX_INFO,
crate::repeat_vec_with_capacity::REPEAT_VEC_WITH_CAPACITY_INFO,
crate::reserve_after_initialization::RESERVE_AFTER_INITIALIZATION_INFO,
@ -736,6 +738,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::unit_types::UNIT_CMP_INFO,
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
crate::unnecessary_literal_bound::UNNECESSARY_LITERAL_BOUND_INFO,
crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO,
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,

View File

@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
if !expr.span.from_expansion()
// Avoid cases already linted by `field_reassign_with_default`
&& !self.reassigned_linted.contains(&expr.span)
&& let ExprKind::Call(path, ..) = expr.kind
&& let ExprKind::Call(path, []) = expr.kind
&& !in_automatically_derived(cx.tcx, expr.hir_id)
&& let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
@ -253,7 +253,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
if let ExprKind::Call(fn_expr, _) = &expr.kind
if let ExprKind::Call(fn_expr, []) = &expr.kind
&& let ExprKind::Path(qpath) = &fn_expr.kind
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
{

View File

@ -452,7 +452,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.82.0"]
pub TOO_LONG_FIRST_DOC_PARAGRAPH,
style,
nursery,
"ensure that the first line of a documentation paragraph isn't too long"
}

View File

@ -43,7 +43,7 @@ declare_lint_pass!(Exit => [EXIT]);
impl<'tcx> LateLintPass<'tcx> for Exit {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Call(path_expr, _args) = e.kind
if let ExprKind::Call(path_expr, [_]) = e.kind
&& let ExprKind::Path(ref path) = path_expr.kind
&& let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::process_exit, def_id)

View File

@ -57,7 +57,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
&& unwrap_fun.ident.name == sym::unwrap
// match call to write_fmt
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind)
&& let ExprKind::Call(write_recv_path, _) = write_recv.kind
&& let ExprKind::Call(write_recv_path, []) = write_recv.kind
&& write_fun.ident.name == sym!(write_fmt)
&& let Some(def_id) = path_def_id(cx, write_recv_path)
{

View File

@ -436,12 +436,12 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
lhs,
rhs,
) = expr.kind
&& let ExprKind::MethodCall(path, self_arg, [], _) = &lhs.kind
&& path.ident.name.as_str() == "exp"
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& let Some(value) = ConstEvalCtxt::new(cx).eval(rhs)
&& (F32(1.0) == value || F64(1.0) == value)
&& let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind
&& cx.typeck_results().expr_ty(self_arg).is_floating_point()
&& path.ident.name.as_str() == "exp"
{
span_lint_and_sugg(
cx,

View File

@ -151,7 +151,7 @@ struct FormatImplExpr<'a, 'tcx> {
impl FormatImplExpr<'_, '_> {
fn check_to_string_in_display(&self) {
if self.format_trait_impl.name == sym::Display
&& let ExprKind::MethodCall(path, self_arg, ..) = self.expr.kind
&& let ExprKind::MethodCall(path, self_arg, [], _) = self.expr.kind
// Get the hir_id of the object we are calling the method on
// Is the method to_string() ?
&& path.ident.name == sym::to_string

View File

@ -181,6 +181,9 @@ fn convert_to_from(
let from = self_ty.span.get_source_text(cx)?;
let into = target_ty.span.get_source_text(cx)?;
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
.then_some(String::from("Self"))
.unwrap_or_default();
let mut suggestions = vec![
// impl Into<T> for U -> impl From<T> for U
// ~~~~ ~~~~
@ -197,13 +200,10 @@ fn convert_to_from(
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
];
if let FnRetTy::Return(_) = sig.decl.output {
// fn into(self) -> T -> fn into(self) -> Self
// ~ ~~~~
suggestions.push((sig.decl.output.span(), String::from("Self")));
}
(sig.decl.output.span(), return_type),
];
let mut finder = SelfFinder {
cx,

View File

@ -82,7 +82,7 @@ fn mutex_lock_call<'tcx>(
expr: &'tcx Expr<'_>,
op_mutex: Option<&'tcx Expr<'_>>,
) -> ControlFlow<&'tcx Expr<'tcx>> {
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind
&& path.ident.as_str() == "lock"
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& is_type_diagnostic_item(cx, ty, sym::Mutex)

View File

@ -139,6 +139,13 @@ fn check_manual_check<'tcx>(
if_block,
else_block,
msrv,
matches!(
clippy_utils::get_parent_expr(cx, expr),
Some(Expr {
kind: ExprKind::If(..),
..
})
),
),
BinOpKind::Lt | BinOpKind::Le => check_gt(
cx,
@ -149,6 +156,13 @@ fn check_manual_check<'tcx>(
if_block,
else_block,
msrv,
matches!(
clippy_utils::get_parent_expr(cx, expr),
Some(Expr {
kind: ExprKind::If(..),
..
})
),
),
_ => {},
}
@ -165,6 +179,7 @@ fn check_gt(
if_block: &Expr<'_>,
else_block: &Expr<'_>,
msrv: &Msrv,
is_composited: bool,
) {
if let Some(big_var) = Var::new(big_var)
&& let Some(little_var) = Var::new(little_var)
@ -178,6 +193,7 @@ fn check_gt(
if_block,
else_block,
msrv,
is_composited,
);
}
}
@ -206,6 +222,7 @@ fn check_subtraction(
if_block: &Expr<'_>,
else_block: &Expr<'_>,
msrv: &Msrv,
is_composited: bool,
) {
let if_block = peel_blocks(if_block);
let else_block = peel_blocks(else_block);
@ -226,6 +243,7 @@ fn check_subtraction(
else_block,
if_block,
msrv,
is_composited,
);
return;
}
@ -242,13 +260,18 @@ fn check_subtraction(
&& let Some(little_var_snippet) = snippet_opt(cx, little_var.span)
&& (!is_in_const_context(cx) || msrv.meets(msrvs::SATURATING_SUB_CONST))
{
let sugg = format!(
"{}{big_var_snippet}.saturating_sub({little_var_snippet}){}",
if is_composited { "{ " } else { "" },
if is_composited { " }" } else { "" }
);
span_lint_and_sugg(
cx,
IMPLICIT_SATURATING_SUB,
expr_span,
"manual arithmetic check found",
"replace it with",
format!("{big_var_snippet}.saturating_sub({little_var_snippet})"),
sugg,
Applicability::MachineApplicable,
);
}

View File

@ -3,8 +3,8 @@ use clippy_utils::source::snippet;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def_id::DefId;
use rustc_hir::{
AssocItemConstraint, GenericArg, GenericBound, GenericBounds, PredicateOrigin, TraitBoundModifier,
TyKind, WherePredicate,
AssocItemConstraint, GenericArg, GenericBound, GenericBounds, PredicateOrigin, TraitBoundModifier, TyKind,
WherePredicate,
};
use rustc_hir_analysis::lower_ty;
use rustc_lint::{LateContext, LateLintPass};

View File

@ -50,11 +50,28 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Detects type names that are prefixed or suffixed by the
/// containing module's name.
/// Detects public item names that are prefixed or suffixed by the
/// containing public module's name.
///
/// ### Why is this bad?
/// It requires the user to type the module name twice.
/// It requires the user to type the module name twice in each usage,
/// especially if they choose to import the module rather than its contents.
///
/// Lack of such repetition is also the style used in the Rust standard library;
/// e.g. `io::Error` and `fmt::Error` rather than `io::IoError` and `fmt::FmtError`;
/// and `array::from_ref` rather than `array::array_from_ref`.
///
/// ### Known issues
/// Glob re-exports are ignored; e.g. this will not warn even though it should:
///
/// ```no_run
/// pub mod foo {
/// mod iteration {
/// pub struct FooIter {}
/// }
/// pub use iteration::*; // creates the path `foo::FooIter`
/// }
/// ```
///
/// ### Example
/// ```no_run
@ -71,7 +88,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.33.0"]
pub MODULE_NAME_REPETITIONS,
pedantic,
restriction,
"type names prefixed/postfixed with their containing module's name"
}
@ -389,12 +406,12 @@ impl LateLintPass<'_> for ItemNameRepetitions {
let item_name = item.ident.name.as_str();
let item_camel = to_camel_case(item_name);
if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
if let [.., (mod_name, mod_camel, owner_id)] = &*self.modules {
if let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules {
// constants don't have surrounding modules
if !mod_camel.is_empty() {
if mod_name == &item.ident.name
&& let ItemKind::Mod(..) = item.kind
&& (!self.allow_private_module_inception || cx.tcx.visibility(owner_id.def_id).is_public())
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
{
span_lint(
cx,
@ -403,9 +420,13 @@ impl LateLintPass<'_> for ItemNameRepetitions {
"module has the same name as its containing module",
);
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is accepted.
if cx.tcx.visibility(item.owner_id).is_public() && item_camel.len() > mod_camel.len() {
if cx.tcx.visibility(item.owner_id).is_public()
&& cx.tcx.visibility(mod_owner_id.def_id).is_public()
&& item_camel.len() > mod_camel.len()
{
let matching = count_match_start(mod_camel, &item_camel);
let rmatching = count_match_end(mod_camel, &item_camel);
let nchars = mod_camel.chars().count();

View File

@ -57,7 +57,7 @@ impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
impl<'tcx> LateLintPass<'tcx> for LargeFuture {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Match(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind
&& let ExprKind::Call(func, [arg, ..]) = scrutinee.kind
&& let ExprKind::Call(func, [arg]) = scrutinee.kind
&& let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
&& !expr.span.from_expansion()
&& let ty = cx.typeck_results().expr_ty(arg)

View File

@ -1,3 +1,5 @@
use std::num::Saturating;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
@ -30,6 +32,7 @@ declare_clippy_lint! {
pub struct LargeStackArrays {
maximum_allowed_size: u64,
prev_vec_macro_callsite: Option<Span>,
const_item_counter: Saturating<u16>,
}
impl LargeStackArrays {
@ -37,6 +40,7 @@ impl LargeStackArrays {
Self {
maximum_allowed_size: conf.array_size_threshold,
prev_vec_macro_callsite: None,
const_item_counter: Saturating(0),
}
}
@ -60,8 +64,21 @@ impl LargeStackArrays {
impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if matches!(item.kind, ItemKind::Static(..) | ItemKind::Const(..)) {
self.const_item_counter += 1;
}
}
fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if matches!(item.kind, ItemKind::Static(..) | ItemKind::Const(..)) {
self.const_item_counter -= 1;
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind
if self.const_item_counter.0 == 0
&& let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind
&& !self.is_from_vec_macro(cx, expr.span)
&& let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind()
&& let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind()

View File

@ -513,7 +513,7 @@ fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>
return;
}
if let (&ExprKind::MethodCall(method_path, receiver, args, _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
// check if we are in an is_empty() method
if let Some(name) = get_item_name(cx, method) {
if name.as_str() == "is_empty" {
@ -521,29 +521,17 @@ fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>
}
}
check_len(
cx,
span,
method_path.ident.name,
receiver,
args,
&lit.node,
op,
compare_to,
);
check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to);
} else {
check_empty_expr(cx, span, method, lit, op);
}
}
// FIXME(flip1995): Figure out how to reduce the number of arguments
#[allow(clippy::too_many_arguments)]
fn check_len(
cx: &LateContext<'_>,
span: Span,
method_name: Symbol,
receiver: &Expr<'_>,
args: &[Expr<'_>],
lit: &LitKind,
op: &str,
compare_to: u32,
@ -554,7 +542,7 @@ fn check_len(
return;
}
if method_name == sym::len && args.is_empty() && has_is_empty(cx, receiver) {
if method_name == sym::len && has_is_empty(cx, receiver) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,

View File

@ -1,6 +1,7 @@
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
#![feature(macro_metavar_expr_concat)]
#![feature(f128)]
#![feature(f16)]
#![feature(if_let_guard)]
@ -56,9 +57,10 @@ extern crate rustc_trait_selection;
extern crate thin_vec;
#[macro_use]
extern crate clippy_utils;
mod declare_clippy_lint;
#[macro_use]
extern crate declare_clippy_lint;
extern crate clippy_utils;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
mod utils;
@ -203,6 +205,7 @@ mod manual_clamp;
mod manual_div_ceil;
mod manual_float_methods;
mod manual_hash_one;
mod manual_ignore_case_cmp;
mod manual_is_ascii_check;
mod manual_is_power_of_two;
mod manual_let_else;
@ -360,6 +363,7 @@ mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
mod unnecessary_box_returns;
mod unnecessary_literal_bound;
mod unnecessary_map_on_constructor;
mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports;
@ -391,7 +395,7 @@ mod zero_sized_map_values;
mod zombie_processes;
// end lints modules, do not remove this comment, its used in `update_lints`
use clippy_config::{Conf, get_configuration_metadata};
use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation};
use clippy_utils::macros::FormatArgsStorage;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
@ -519,8 +523,9 @@ impl LintInfo {
pub fn explain(name: &str) -> i32 {
let target = format!("clippy::{}", name.to_ascii_uppercase());
if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name == target) {
println!("{}", info.explanation);
println!("{}", sanitize_explanation(info.explanation));
// Check if the lint has configuration
let mut mdconf = get_configuration_metadata();
let name = name.to_ascii_lowercase();
@ -896,7 +901,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
store.register_early_pass(|| Box::new(visibility::Visibility));
store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions::new(conf)));
store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods));
store.register_late_pass(move |_| Box::new(manual_float_methods::ManualFloatMethods::new(conf)));
store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes));
store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError));
store.register_late_pass(move |_| Box::new(absolute_paths::AbsolutePaths::new(conf)));
@ -941,5 +946,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -6,8 +6,8 @@ use rustc_errors::Applicability;
use rustc_hir::FnRetTy::Return;
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
use rustc_hir::intravisit::{
Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_param_bound,
walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_where_predicate,
Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_param_bound, walk_poly_trait_ref,
walk_trait_ref, walk_ty, walk_where_predicate,
};
use rustc_hir::{
BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericArgs, GenericBound, GenericParam, GenericParamKind, Generics,

View File

@ -412,7 +412,6 @@ impl LiteralDigitGrouping {
}
}
#[expect(clippy::module_name_repetitions)]
pub struct DecimalLiteralRepresentation {
threshold: u64,
}

View File

@ -42,6 +42,7 @@ pub(super) fn check<'tcx>(
let mut loop_visitor = LoopVisitor {
cx,
label,
inner_labels: label.into_iter().collect(),
is_finite: false,
loop_depth: 0,
};
@ -93,6 +94,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
struct LoopVisitor<'hir, 'tcx> {
cx: &'hir LateContext<'tcx>,
label: Option<Label>,
inner_labels: Vec<Label>,
loop_depth: usize,
is_finite: bool,
}
@ -108,11 +110,24 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> {
self.is_finite = true;
}
},
ExprKind::Continue(hir::Destination { label, .. }) => {
// Check whether we are leaving this loop by continuing into an outer loop
// whose label we did not encounter.
if label.is_some_and(|label| !self.inner_labels.contains(&label)) {
self.is_finite = true;
}
},
ExprKind::Ret(..) => self.is_finite = true,
ExprKind::Loop(..) => {
ExprKind::Loop(_, label, _, _) => {
if let Some(label) = label {
self.inner_labels.push(*label);
}
self.loop_depth += 1;
walk_expr(self, ex);
self.loop_depth = self.loop_depth.saturating_sub(1);
self.loop_depth -= 1;
if label.is_some() {
self.inner_labels.pop();
}
},
_ => {
// Calls to a function that never return

View File

@ -47,8 +47,9 @@ fn report_lint(cx: &LateContext<'_>, pop_span: Span, pop_stmt_kind: PopStmt<'_>,
);
}
fn match_method_call(cx: &LateContext<'_>, expr: &Expr<'_>, method: Symbol) -> bool {
if let ExprKind::MethodCall(..) = expr.kind
fn match_method_call<const ARGS_COUNT: usize>(cx: &LateContext<'_>, expr: &Expr<'_>, method: Symbol) -> bool {
if let ExprKind::MethodCall(_, _, args, _) = expr.kind
&& args.len() == ARGS_COUNT
&& let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
{
cx.tcx.is_diagnostic_item(method, id)
@ -58,9 +59,9 @@ fn match_method_call(cx: &LateContext<'_>, expr: &Expr<'_>, method: Symbol) -> b
}
fn is_vec_pop_unwrap(cx: &LateContext<'_>, expr: &Expr<'_>, is_empty_recv: &Expr<'_>) -> bool {
if (match_method_call(cx, expr, sym::option_unwrap) || match_method_call(cx, expr, sym::option_expect))
if (match_method_call::<0>(cx, expr, sym::option_unwrap) || match_method_call::<1>(cx, expr, sym::option_expect))
&& let ExprKind::MethodCall(_, unwrap_recv, ..) = expr.kind
&& match_method_call(cx, unwrap_recv, sym::vec_pop)
&& match_method_call::<0>(cx, unwrap_recv, sym::vec_pop)
&& let ExprKind::MethodCall(_, pop_recv, ..) = unwrap_recv.kind
{
// make sure they're the same `Vec`
@ -96,7 +97,7 @@ fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &E
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, full_cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>, loop_span: Span) {
if let ExprKind::Unary(UnOp::Not, cond) = full_cond.kind
&& let ExprKind::MethodCall(_, is_empty_recv, _, _) = cond.kind
&& match_method_call(cx, cond, sym::vec_is_empty)
&& match_method_call::<0>(cx, cond, sym::vec_is_empty)
&& let ExprKind::Block(body, _) = body.kind
&& let Some(stmt) = body.stmts.first()
{

View File

@ -172,10 +172,8 @@ fn get_vec_push<'tcx>(
stmt: &'tcx Stmt<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> {
if let StmtKind::Semi(semi_stmt) = &stmt.kind
// Extract method being called
&& let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind
// Figure out the parameters for the method call
&& let Some(pushed_item) = args.first()
// Extract method being called and figure out the parameters for the method call
&& let ExprKind::MethodCall(path, self_expr, [pushed_item], _) = &semi_stmt.kind
// Check that the method being called is push() on a Vec
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec)
&& path.ident.name.as_str() == "push"

View File

@ -43,7 +43,6 @@ impl MacroRefData {
}
#[derive(Default)]
#[expect(clippy::module_name_repetitions)]
pub struct MacroUseImports {
/// the actual import path used and the span of the attribute above it. The value is
/// the location, where the lint should be emitted.

View File

@ -42,7 +42,7 @@ impl LateLintPass<'_> for MainRecursion {
return;
}
if let ExprKind::Call(func, _) = &expr.kind
if let ExprKind::Call(func, []) = &expr.kind
&& let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind
&& let Some(def_id) = path.res.opt_def_id()
&& is_entrypoint_fn(cx, def_id)

View File

@ -95,7 +95,7 @@ fn get_one_size_of_ty<'tcx>(
}
fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Ty<'tcx>)> {
if let ExprKind::Call(count_func, _func_args) = expr.kind
if let ExprKind::Call(count_func, []) = expr.kind
&& let ExprKind::Path(ref count_func_qpath) = count_func.kind
&& let QPath::Resolved(_, count_func_path) = count_func_qpath
&& let Some(segment_zero) = count_func_path.segments.first()

View File

@ -1,3 +1,5 @@
use clippy_config::msrvs::Msrv;
use clippy_config::{Conf, msrvs};
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt;
@ -6,7 +8,7 @@ use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
@ -56,7 +58,7 @@ declare_clippy_lint! {
style,
"use dedicated method to check if a float is finite"
}
declare_lint_pass!(ManualFloatMethods => [MANUAL_IS_INFINITE, MANUAL_IS_FINITE]);
impl_lint_pass!(ManualFloatMethods => [MANUAL_IS_INFINITE, MANUAL_IS_FINITE]);
#[derive(Clone, Copy)]
enum Variant {
@ -80,6 +82,18 @@ impl Variant {
}
}
pub struct ManualFloatMethods {
msrv: Msrv,
}
impl ManualFloatMethods {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv.clone(),
}
}
}
impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Binary(kind, lhs, rhs) = expr.kind
@ -92,7 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
&& !in_external_macro(cx.sess(), expr.span)
&& (
matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
|| cx.tcx.features().declared(sym!(const_float_classify))
|| self.msrv.meets(msrvs::CONST_FLOAT_CLASSIFY)
)
&& let [first, second, const_1, const_2] = exprs
&& let ecx = ConstEvalCtxt::new(cx)
@ -150,6 +164,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
});
}
}
extract_msrv_attr!(LateContext);
}
fn is_infinity(constant: &Constant<'_>) -> bool {

View File

@ -0,0 +1,127 @@
use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::ExprKind::{Binary, Lit, MethodCall};
use rustc_hir::{BinOpKind, Expr, LangItem};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{Ty, UintTy};
use rustc_session::declare_lint_pass;
use rustc_span::{Span, sym};
declare_clippy_lint! {
/// ### What it does
/// Checks for manual case-insensitive ASCII comparison.
///
/// ### Why is this bad?
/// The `eq_ignore_ascii_case` method is faster because it does not allocate
/// memory for the new strings, and it is more readable.
///
/// ### Example
/// ```no_run
/// fn compare(a: &str, b: &str) -> bool {
/// a.to_ascii_lowercase() == b.to_ascii_lowercase() || a.to_ascii_lowercase() == "abc"
/// }
/// ```
/// Use instead:
/// ```no_run
/// fn compare(a: &str, b: &str) -> bool {
/// a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc")
/// }
/// ```
#[clippy::version = "1.82.0"]
pub MANUAL_IGNORE_CASE_CMP,
perf,
"manual case-insensitive ASCII comparison"
}
declare_lint_pass!(ManualIgnoreCaseCmp => [MANUAL_IGNORE_CASE_CMP]);
enum MatchType<'a, 'b> {
ToAscii(bool, Ty<'a>),
Literal(&'b LitKind),
}
fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) -> Option<(Span, MatchType<'a, 'b>)> {
if let MethodCall(path, expr, _, _) = kind {
let is_lower = match path.ident.name.as_str() {
"to_ascii_lowercase" => true,
"to_ascii_uppercase" => false,
_ => return None,
};
let ty_raw = cx.typeck_results().expr_ty(expr);
let ty = ty_raw.peel_refs();
if needs_ref_to_cmp(cx, ty)
|| ty.is_str()
|| ty.is_slice()
|| matches!(get_type_diagnostic_name(cx, ty), Some(sym::OsStr | sym::OsString))
{
return Some((expr.span, ToAscii(is_lower, ty_raw)));
}
} else if let Lit(expr) = kind {
return Some((expr.span, Literal(&expr.node)));
}
None
}
/// Returns true if the type needs to be dereferenced to be compared
fn needs_ref_to_cmp(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
ty.is_char()
|| *ty.kind() == ty::Uint(UintTy::U8)
|| is_type_diagnostic_item(cx, ty, sym::Vec)
|| is_type_lang_item(cx, ty, LangItem::String)
}
impl LateLintPass<'_> for ManualIgnoreCaseCmp {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
// check if expression represents a comparison of two strings
// using .to_ascii_lowercase() or .to_ascii_uppercase() methods,
// or one of the sides is a literal
// Offer to replace it with .eq_ignore_ascii_case() method
if let Binary(op, left, right) = &expr.kind
&& (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne)
&& let Some((left_span, left_val)) = get_ascii_type(cx, left.kind)
&& let Some((right_span, right_val)) = get_ascii_type(cx, right.kind)
&& match (&left_val, &right_val) {
(ToAscii(l_lower, ..), ToAscii(r_lower, ..)) if l_lower == r_lower => true,
(ToAscii(..), Literal(..)) | (Literal(..), ToAscii(..)) => true,
_ => false,
}
{
let deref = match right_val {
ToAscii(_, ty) if needs_ref_to_cmp(cx, ty) => "&",
ToAscii(..) => "",
Literal(ty) => {
if let LitKind::Char(_) | LitKind::Byte(_) = ty {
"&"
} else {
""
}
},
};
let neg = if op.node == BinOpKind::Ne { "!" } else { "" };
span_lint_and_then(
cx,
MANUAL_IGNORE_CASE_CMP,
expr.span,
"manual case-insensitive ASCII comparison",
|diag| {
let mut app = Applicability::MachineApplicable;
diag.span_suggestion_verbose(
expr.span,
"consider using `.eq_ignore_ascii_case()` instead",
format!(
"{neg}{}.eq_ignore_ascii_case({deref}{})",
snippet_with_applicability(cx, left_span, "_", &mut app),
snippet_with_applicability(cx, right_span, "_", &mut app)
),
app,
);
},
);
}
}
}

View File

@ -11,10 +11,12 @@ use rustc_session::declare_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for expressions like `x.count_ones() == 1` or `x & (x - 1) == 0`, with x and unsigned integer, which are manual
/// Checks for expressions like `x.count_ones() == 1` or `x & (x - 1) == 0`, with x and unsigned integer, which may be manual
/// reimplementations of `x.is_power_of_two()`.
///
/// ### Why is this bad?
/// Manual reimplementations of `is_power_of_two` increase code complexity for little benefit.
///
/// ### Example
/// ```no_run
/// let a: u32 = 4;
@ -27,7 +29,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.82.0"]
pub MANUAL_IS_POWER_OF_TWO,
complexity,
pedantic,
"manually reimplementing `is_power_of_two`"
}
@ -41,7 +43,7 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
&& bin_op.node == BinOpKind::Eq
{
// a.count_ones() == 1
if let ExprKind::MethodCall(method_name, reciever, _, _) = left.kind
if let ExprKind::MethodCall(method_name, reciever, [], _) = left.kind
&& method_name.ident.as_str() == "count_ones"
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
&& check_lit(right, 1)
@ -50,7 +52,7 @@ impl LateLintPass<'_> for ManualIsPowerOfTwo {
}
// 1 == a.count_ones()
if let ExprKind::MethodCall(method_name, reciever, _, _) = right.kind
if let ExprKind::MethodCall(method_name, reciever, [], _) = right.kind
&& method_name.ident.as_str() == "count_ones"
&& let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
&& check_lit(left, 1)

View File

@ -45,10 +45,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
&& !expr.span.from_expansion()
// Does not apply inside const because size_of_val is not cost in stable.
&& !is_in_const_context(cx)
&& let Some(receiver) = simplify(cx, left, right)
&& let Some((receiver, refs_count)) = simplify(cx, left, right)
{
let ctxt = expr.span.ctxt();
let mut app = Applicability::MachineApplicable;
let deref = "*".repeat(refs_count - 1);
let val_name = snippet_with_context(cx, receiver.span, ctxt, "slice", &mut app).0;
let Some(sugg) = std_or_core(cx) else { return };
@ -58,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
expr.span,
"manual slice size calculation",
"try",
format!("{sugg}::mem::size_of_val({val_name})"),
format!("{sugg}::mem::size_of_val({deref}{val_name})"),
app,
);
}
@ -69,7 +70,7 @@ fn simplify<'tcx>(
cx: &LateContext<'tcx>,
expr1: &'tcx Expr<'tcx>,
expr2: &'tcx Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
) -> Option<(&'tcx Expr<'tcx>, usize)> {
let expr1 = expr_or_init(cx, expr1);
let expr2 = expr_or_init(cx, expr2);
@ -80,15 +81,16 @@ fn simplify_half<'tcx>(
cx: &LateContext<'tcx>,
expr1: &'tcx Expr<'tcx>,
expr2: &'tcx Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
) -> Option<(&'tcx Expr<'tcx>, usize)> {
if !expr1.span.from_expansion()
// expr1 is `[T1].len()`?
&& let ExprKind::MethodCall(method_path, receiver, _, _) = expr1.kind
&& let ExprKind::MethodCall(method_path, receiver, [], _) = expr1.kind
&& method_path.ident.name == sym::len
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let ty::Slice(ty1) = receiver_ty.peel_refs().kind()
&& let (receiver_ty, refs_count) = clippy_utils::ty::walk_ptrs_ty_depth(receiver_ty)
&& let ty::Slice(ty1) = receiver_ty.kind()
// expr2 is `size_of::<T2>()`?
&& let ExprKind::Call(func, _) = expr2.kind
&& let ExprKind::Call(func, []) = expr2.kind
&& let ExprKind::Path(ref func_qpath) = func.kind
&& let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
@ -96,7 +98,7 @@ fn simplify_half<'tcx>(
// T1 == T2?
&& *ty1 == ty2
{
Some(receiver)
Some((receiver, refs_count))
} else {
None
}

View File

@ -52,8 +52,8 @@ impl LateLintPass<'_> for ManualStringNew {
}
match expr.kind {
ExprKind::Call(func, args) => {
parse_call(cx, expr.span, func, args);
ExprKind::Call(func, [arg]) => {
parse_call(cx, expr.span, func, arg);
},
ExprKind::MethodCall(path_segment, receiver, ..) => {
parse_method_call(cx, expr.span, path_segment, receiver);
@ -93,20 +93,15 @@ fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegmen
let method_arg_kind = &receiver.kind;
if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) {
warn_then_suggest(cx, span);
} else if let ExprKind::Call(func, args) = method_arg_kind {
} else if let ExprKind::Call(func, [arg]) = method_arg_kind {
// If our first argument is a function call itself, it could be an `unwrap`-like function.
// E.g. String::try_from("hello").unwrap(), TryFrom::try_from("").expect("hello"), etc.
parse_call(cx, span, func, args);
parse_call(cx, span, func, arg);
}
}
/// Tries to parse an expression as a function call, emitting the warning if necessary.
fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_>]) {
if args.len() != 1 {
return;
}
let arg_kind = &args[0].kind;
fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, arg: &Expr<'_>) {
if let ExprKind::Path(qpath) = &func.kind {
// String::from(...) or String::try_from(...)
if let QPath::TypeRelative(ty, path_seg) = qpath
@ -115,13 +110,13 @@ fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, args: &[Expr<'_
&& let QPath::Resolved(_, path) = qpath
&& let [path_seg] = path.segments
&& path_seg.ident.name == sym::String
&& is_expr_kind_empty_str(arg_kind)
&& is_expr_kind_empty_str(&arg.kind)
{
warn_then_suggest(cx, span);
} else if let QPath::Resolved(_, path) = qpath {
// From::from(...) or TryFrom::try_from(...)
if let [path_seg1, path_seg2] = path.segments
&& is_expr_kind_empty_str(arg_kind)
&& is_expr_kind_empty_str(&arg.kind)
&& ((path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from)
|| (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from))
{

View File

@ -210,7 +210,7 @@ fn find_method_sugg_for_if_let<'tcx>(
// check that `while_let_on_iterator` lint does not trigger
if keyword == "while"
&& let ExprKind::MethodCall(method_path, ..) = let_expr.kind
&& let ExprKind::MethodCall(method_path, _, [], _) = let_expr.kind
&& method_path.ident.name == sym::next
&& is_trait_method(cx, let_expr, sym::Iterator)
{

View File

@ -21,10 +21,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
// #[allow(unreachable_code)]
// val,
// };
if let ExprKind::Call(match_fun, [try_arg, ..]) = scrutinee.kind
if let ExprKind::Call(match_fun, [try_arg]) = scrutinee.kind
&& let ExprKind::Path(ref match_fun_path) = match_fun.kind
&& matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..))
&& let ExprKind::Call(err_fun, [err_arg, ..]) = try_arg.kind
&& let ExprKind::Call(err_fun, [err_arg]) = try_arg.kind
&& is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr)
&& let Some(return_ty) = find_return_type(cx, &expr.kind)
{

View File

@ -58,7 +58,7 @@ pub(super) fn check(
return;
},
// ? is a Call, makes sure not to rec *x?, but rather (*x)?
ExprKind::Call(hir_callee, _) => matches!(
ExprKind::Call(hir_callee, [_]) => matches!(
hir_callee.kind,
ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, ..))
),

View File

@ -143,7 +143,7 @@ pub(super) fn check<'tcx>(
cx,
EXPECT_FUN_CALL,
span_replace_word,
format!("use of `{name}` followed by a function call"),
format!("function call inside of `{name}`"),
"try",
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
applicability,
@ -161,7 +161,7 @@ pub(super) fn check<'tcx>(
cx,
EXPECT_FUN_CALL,
span_replace_word,
format!("use of `{name}` followed by a function call"),
format!("function call inside of `{name}`"),
"try",
format!("unwrap_or_else({closure_args} {{ panic!(\"{{}}\", {arg_root_snippet}) }})"),
applicability,

View File

@ -106,9 +106,9 @@ fn is_method(
fn parent_is_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
if let Some(expr) = get_parent_expr(cx, expr)
&& is_trait_method(cx, expr, sym::Iterator)
&& let ExprKind::MethodCall(path, _, _, _) = expr.kind
&& let ExprKind::MethodCall(path, _, [_], _) = expr.kind
&& path.ident.name == sym::map
&& is_trait_method(cx, expr, sym::Iterator)
{
return true;
}

View File

@ -6,6 +6,7 @@ use rustc_ast::{LitKind, StrStyle};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{Span, Symbol, sym};
use super::MANUAL_C_STR_LITERALS;
@ -25,6 +26,7 @@ pub(super) fn check_as_ptr<'tcx>(
) {
if let ExprKind::Lit(lit) = receiver.kind
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node
&& cx.tcx.sess.edition() >= Edition2021
&& let casts_removed = peel_ptr_cast_ancestors(cx, expr)
&& !get_parent_expr(cx, casts_removed).is_some_and(
|parent| matches!(parent.kind, ExprKind::Call(func, _) if is_c_str_function(cx, func).is_some()),
@ -66,6 +68,7 @@ fn is_c_str_function(cx: &LateContext<'_>, func: &Expr<'_>) -> Option<Symbol> {
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
if let Some(fn_name) = is_c_str_function(cx, func)
&& let [arg] = args
&& cx.tcx.sess.edition() >= Edition2021
&& msrv.meets(msrvs::C_STR_LITERALS)
{
match fn_name.as_str() {
@ -84,7 +87,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
/// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
if let ExprKind::MethodCall(method, lit, ..) = peel_ptr_cast(arg).kind
if let ExprKind::MethodCall(method, lit, [], _) = peel_ptr_cast(arg).kind
&& method.ident.name == sym::as_ptr
&& !lit.span.from_expansion()
&& let ExprKind::Lit(lit) = lit.kind

View File

@ -68,8 +68,7 @@ enum MinMax {
fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
// `T::max_value()` `T::min_value()` inherent methods
if let hir::ExprKind::Call(func, args) = &expr.kind
&& args.is_empty()
if let hir::ExprKind::Call(func, []) = &expr.kind
&& let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind
{
match segment.ident.as_str() {

View File

@ -86,9 +86,8 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_
}
}
},
hir::ExprKind::Call(call, args) => {
hir::ExprKind::Call(call, [arg]) => {
if let hir::ExprKind::Path(qpath) = call.kind
&& let [arg] = args
&& ident_eq(name, arg)
{
handle_path(cx, call, &qpath, e, recv);

View File

@ -4046,7 +4046,7 @@ declare_clippy_lint! {
/// Checks the usage of `.get().is_some()` or `.get().is_none()` on std map types.
///
/// ### Why is this bad?
/// It can be done in one call with `.contains()`/`.contains_keys()`.
/// It can be done in one call with `.contains()`/`.contains_key()`.
///
/// ### Example
/// ```no_run
@ -5182,7 +5182,7 @@ impl ShouldImplTraitCase {
}
#[rustfmt::skip]
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
static TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),

View File

@ -321,7 +321,10 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// Check function calls on our collection
if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind {
if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) {
if args.is_empty()
&& method_name.ident.name == sym!(collect)
&& is_trait_method(self.cx, expr, sym::Iterator)
{
self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
self.visit_expr(recv);
return;

View File

@ -183,7 +183,7 @@ pub(super) fn check<'tcx>(
cx,
OR_FUN_CALL,
span_replace_word,
format!("use of `{name}` followed by a function call"),
format!("function call inside of `{name}`"),
"try",
format!("{name}_{suffix}({sugg})"),
app,
@ -259,7 +259,7 @@ fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>)
if body.params.is_empty()
&& let hir::Expr { kind, .. } = &body.value
&& let hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, self_arg, _, _) = kind
&& let hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, self_arg, [], _) = kind
&& ident.name == sym::to_string
&& let hir::Expr { kind, .. } = self_arg
&& let hir::ExprKind::Lit(lit) = kind

View File

@ -43,7 +43,8 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<
for_each_local_use_after_expr(cx, local_id, call.hir_id, |expr| {
if let Some(parent) = get_parent_expr(cx, expr) {
let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind {
if segment.ident.name == sym!(parse)
if args.is_empty()
&& segment.ident.name == sym!(parse)
&& let parse_result_ty = cx.typeck_results().expr_ty(parent)
&& is_type_diagnostic_item(cx, parse_result_ty, sym::Result)
&& let ty::Adt(_, substs) = parse_result_ty.kind()

View File

@ -10,7 +10,7 @@ use rustc_middle::mir::{Location, START_BLOCK};
use rustc_span::sym;
fn is_unwrap_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let ExprKind::MethodCall(path, receiver, ..) = expr.kind
if let ExprKind::MethodCall(path, receiver, [], _) = expr.kind
&& path.ident.name == sym::unwrap
{
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver).peel_refs(), sym::Result)

View File

@ -34,14 +34,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'
}
fn arg_is_seek_from_current<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if let ExprKind::Call(f, args) = expr.kind
if let ExprKind::Call(f, [arg]) = expr.kind
&& let ExprKind::Path(ref path) = f.kind
&& let Some(ctor_call_id) = cx.qpath_res(path, f.hir_id).opt_def_id()
&& is_enum_variant_ctor(cx, sym::SeekFrom, sym!(Current), ctor_call_id)
{
// check if argument of `SeekFrom::Current` is `0`
if args.len() == 1
&& let ExprKind::Lit(lit) = args[0].kind
if let ExprKind::Lit(lit) = arg.kind
&& let LitKind::Int(Pu128(0), LitIntType::Unsuffixed) = lit.node
{
return true;

View File

@ -26,12 +26,11 @@ pub(super) fn check<'tcx>(
if let Some(seek_trait_id) = cx.tcx.get_diagnostic_item(sym::IoSeek)
&& implements_trait(cx, ty, seek_trait_id, &[])
&& let ExprKind::Call(func, args1) = arg.kind
&& let ExprKind::Call(func, [arg]) = arg.kind
&& let ExprKind::Path(ref path) = func.kind
&& let Some(ctor_call_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& is_enum_variant_ctor(cx, sym::SeekFrom, sym!(Start), ctor_call_id)
&& args1.len() == 1
&& let ExprKind::Lit(lit) = args1[0].kind
&& let ExprKind::Lit(lit) = arg.kind
&& let LitKind::Int(Pu128(0), LitIntType::Unsuffixed) = lit.node
{
let method_call_span = expr.span.with_lo(name_span.lo());

View File

@ -27,7 +27,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::
}
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].kind
&& let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind
&& let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind
&& path_segment.ident.name == rustc_span::sym::to_string
&& (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
{

View File

@ -26,7 +26,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::
}
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].kind
&& let ExprKind::MethodCall(path_segment, method_arg, _, _) = &arg.kind
&& let ExprKind::MethodCall(path_segment, method_arg, [], _) = &arg.kind
&& path_segment.ident.name == rustc_span::sym::to_string
&& (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
{

View File

@ -333,7 +333,7 @@ fn parse_iter_usage<'tcx>(
kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)),
..
},
_,
[_],
) => {
let parent_span = e.span.parent_callsite().unwrap();
if parent_span.ctxt() == ctxt {

View File

@ -9,8 +9,7 @@ use super::UNINIT_ASSUMED_INIT;
/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter)
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
if let hir::ExprKind::Call(callee, args) = recv.kind
&& args.is_empty()
if let hir::ExprKind::Call(callee, []) = recv.kind
&& is_path_diagnostic_item(cx, callee, sym::maybe_uninit_uninit)
&& !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr))
{

View File

@ -50,7 +50,7 @@ pub(super) fn check(
),
"replace this with",
suggestion,
Applicability::MaybeIncorrect,
Applicability::MachineApplicable,
);
}
}

View File

@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArgKind};
use rustc_middle::ty;
use rustc_middle::ty::GenericArgKind;
use rustc_span::sym;
use rustc_span::symbol::Ident;
use std::iter;

View File

@ -86,12 +86,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
// changing the type, then we can move forward.
&& rcv_ty.peel_refs() == res_ty.peel_refs()
&& let Some(parent) = get_parent_expr(cx, expr)
&& let hir::ExprKind::MethodCall(segment, _, args, _) = parent.kind
// Check that it only has one argument.
&& let hir::ExprKind::MethodCall(segment, _, [arg], _) = parent.kind
&& segment.ident.span != expr.span
// We check that the called method name is `map`.
&& segment.ident.name == sym::map
// And that it only has one argument.
&& let [arg] = args
&& is_calling_clone(cx, arg)
// And that we are not recommending recv.clone() over Arc::clone() or similar
&& !should_call_clone_as_function(cx, rcv_ty)

View File

@ -139,7 +139,7 @@ fn assert_len_expr<'hir>(
&& let ExprKind::Binary(bin_op, left, right) = &condition.kind
&& let Some((cmp, asserted_len, slice_len)) = len_comparison(*bin_op, left, right)
&& let ExprKind::MethodCall(method, recv, ..) = &slice_len.kind
&& let ExprKind::MethodCall(method, recv, [], _) = &slice_len.kind
&& cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
&& method.ident.name == sym::len

View File

@ -193,7 +193,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
| hir::ItemKind::Trait(..)
| hir::ItemKind::TraitAlias(..)
| hir::ItemKind::TyAlias(..)
| hir::ItemKind::Union(..) => {}
| hir::ItemKind::Union(..) => {},
hir::ItemKind::ExternCrate(..)
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::GlobalAsm(..)

View File

@ -1,9 +1,10 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::trait_ref_of_method;
use clippy_utils::ty::InteriorMut;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -132,8 +133,14 @@ impl<'tcx> MutableKeyType<'tcx> {
)
{
let subst_ty = args.type_at(0);
if self.interior_mut.is_interior_mut_ty(cx, subst_ty) {
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
if let Some(chain) = self.interior_mut.interior_mut_ty_chain(cx, subst_ty) {
span_lint_and_then(cx, MUTABLE_KEY_TYPE, span, "mutable key type", |diag| {
for ty in chain.iter().rev() {
diag.note(with_forced_trimmed_paths!(format!(
"... because it contains `{ty}`, which has interior mutability"
)));
}
});
}
}
}

View File

@ -47,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
if let ExprKind::Path(ref path) = fn_expr.kind {
check_arguments(
cx,
arguments.iter().collect(),
&mut arguments.iter(),
cx.typeck_results().expr_ty(fn_expr),
&rustc_hir_pretty::qpath_to_string(&cx.tcx, path),
"function",
@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
let method_type = cx.tcx.type_of(def_id).instantiate(cx.tcx, args);
check_arguments(
cx,
iter::once(receiver).chain(arguments.iter()).collect(),
&mut iter::once(receiver).chain(arguments.iter()),
method_type,
path.ident.as_str(),
"method",
@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
fn check_arguments<'tcx>(
cx: &LateContext<'tcx>,
arguments: Vec<&Expr<'_>>,
arguments: &mut dyn Iterator<Item = &'tcx Expr<'tcx>>,
type_definition: Ty<'tcx>,
name: &str,
fn_kind: &str,

View File

@ -96,10 +96,6 @@ impl<'tcx> Visitor<'tcx> for MutArgVisitor<'_, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View File

@ -64,7 +64,7 @@ fn type_param_bounds<'tcx>(generics: &'tcx Generics<'tcx>) -> impl Iterator<Item
.iter()
.enumerate()
.filter_map(move |(bound_pos, bound)| match bound {
&GenericBound::Trait(ref trait_bound) => Some(Bound {
GenericBound::Trait(trait_bound) => Some(Bound {
param,
ident,
trait_bound,

View File

@ -281,7 +281,7 @@ fn self_cmp_call<'tcx>(
needs_fully_qualified: &mut bool,
) -> bool {
match cmp_expr.kind {
ExprKind::Call(path, [_self, _other]) => path_res(cx, path)
ExprKind::Call(path, [_, _]) => path_res(cx, path)
.opt_def_id()
.is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)),
ExprKind::MethodCall(_, _, [_other], ..) => {

View File

@ -71,7 +71,7 @@ fn check_non_zero_conversion(cx: &LateContext<'_>, expr: &Expr<'_>, applicabilit
if let ExprKind::Call(func, [arg]) = expr.kind
&& let ExprKind::Path(qpath) = &func.kind
&& let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id()
&& let ExprKind::MethodCall(rcv_path, receiver, _, _) = &arg.kind
&& let ExprKind::MethodCall(rcv_path, receiver, [], _) = &arg.kind
&& rcv_path.ident.name.as_str() == "get"
{
let fn_name = cx.tcx.item_name(def_id);

View File

@ -106,7 +106,7 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
return is_signum(cx, child_expr);
}
if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind
if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind
&& sym!(signum) == method_name.ident.name
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of
// the method call)

View File

@ -37,7 +37,7 @@ declare_clippy_lint! {
/// // or
/// let path_buf = PathBuf::new().join("foo");
/// ```
#[clippy::version = "1.81.0"]
#[clippy::version = "1.82.0"]
pub PATHBUF_INIT_THEN_PUSH,
restriction,
"`push` immediately after `PathBuf` creation"

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::visitors::contains_unsafe_block;
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local};
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core};
use hir::LifetimeName;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::hir_id::{HirId, HirIdMap};
@ -294,14 +294,16 @@ fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
};
for &arg_idx in arg_indices {
if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg))
&& let Some(std_or_core) = std_or_core(cx)
{
span_lint_and_sugg(
cx,
INVALID_NULL_PTR_USAGE,
arg.span,
"pointer must be non-null",
"change this to",
"core::ptr::NonNull::dangling().as_ptr()".to_string(),
format!("{std_or_core}::ptr::NonNull::dangling().as_ptr()"),
Applicability::MachineApplicable,
);
}

View File

@ -91,7 +91,7 @@ fn expr_as_ptr_offset_call<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> {
if let ExprKind::MethodCall(path_segment, arg_0, [arg_1, ..], _) = &expr.kind {
if let ExprKind::MethodCall(path_segment, arg_0, [arg_1], _) = &expr.kind {
if is_expr_ty_raw_ptr(cx, arg_0) {
if path_segment.ident.name == sym::offset {
return Some((arg_0, arg_1, Method::Offset));

View File

@ -206,12 +206,11 @@ fn expr_return_none_or_err(
sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
_ => false,
},
ExprKind::Call(call_expr, args_expr) => {
ExprKind::Call(call_expr, [arg]) => {
if smbl == sym::Result
&& let ExprKind::Path(QPath::Resolved(_, path)) = &call_expr.kind
&& let Some(segment) = path.segments.first()
&& let Some(err_sym) = err_sym
&& let Some(arg) = args_expr.first()
&& let ExprKind::Path(QPath::Resolved(_, arg_path)) = &arg.kind
&& let Some(PathSegment { ident, .. }) = arg_path.segments.first()
{
@ -241,7 +240,7 @@ fn expr_return_none_or_err(
fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr)
&& !is_else_clause(cx.tcx, expr)
&& let ExprKind::MethodCall(segment, caller, ..) = &cond.kind
&& let ExprKind::MethodCall(segment, caller, [], _) = &cond.kind
&& let caller_ty = cx.typeck_results().expr_ty(caller)
&& let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then)
&& (is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block))
@ -332,7 +331,7 @@ impl QuestionMark {
fn is_try_block(cx: &LateContext<'_>, bl: &Block<'_>) -> bool {
if let Some(expr) = bl.expr
&& let ExprKind::Call(callee, _) = expr.kind
&& let ExprKind::Call(callee, [_]) = expr.kind
{
is_path_lang_item(cx, callee, LangItem::TryTraitFromOutput)
} else {

View File

@ -1,6 +1,6 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::source::{SpanRangeExt, snippet_opt};
use rustc_ast::ast::{Expr, ExprKind};
use rustc_ast::token::LitKind;
use rustc_errors::Applicability;
@ -71,6 +71,23 @@ impl RawStrings {
impl EarlyLintPass for RawStrings {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::FormatArgs(format_args) = &expr.kind
&& !in_external_macro(cx.sess(), format_args.span)
&& format_args.span.check_source_text(cx, |src| src.starts_with('r'))
&& let Some(str) = snippet_opt(cx.sess(), format_args.span)
&& let count_hash = str.bytes().skip(1).take_while(|b| *b == b'#').count()
&& let Some(str) = str.get(count_hash + 2..str.len() - count_hash - 1)
{
self.check_raw_string(
cx,
str,
format_args.span,
"r",
u8::try_from(count_hash).unwrap(),
"string",
);
}
if let ExprKind::Lit(lit) = expr.kind
&& let (prefix, max) = match lit.kind {
LitKind::StrRaw(max) => ("r", max),
@ -81,20 +98,32 @@ impl EarlyLintPass for RawStrings {
&& !in_external_macro(cx.sess(), expr.span)
&& expr.span.check_source_text(cx, |src| src.starts_with(prefix))
{
let str = lit.symbol.as_str();
let descr = lit.kind.descr();
self.check_raw_string(cx, lit.symbol.as_str(), expr.span, prefix, max, lit.kind.descr());
}
}
}
impl RawStrings {
fn check_raw_string(
&mut self,
cx: &EarlyContext<'_>,
str: &str,
lit_span: Span,
prefix: &str,
max: u8,
descr: &str,
) {
if !str.contains(['\\', '"']) {
span_lint_and_then(
cx,
NEEDLESS_RAW_STRINGS,
expr.span,
lit_span,
"unnecessary raw string literal",
|diag| {
let (start, end) = hash_spans(expr.span, prefix.len(), 0, max);
let (start, end) = hash_spans(lit_span, prefix.len(), 0, max);
// BytePos: skip over the `b` in `br`, we checked the prefix appears in the source text
let r_pos = expr.span.lo() + BytePos::from_usize(prefix.len() - 1);
let r_pos = lit_span.lo() + BytePos::from_usize(prefix.len() - 1);
let start = start.with_lo(r_pos);
let mut remove = vec![(start, String::new())];
@ -150,10 +179,10 @@ impl EarlyLintPass for RawStrings {
span_lint_and_then(
cx,
NEEDLESS_RAW_STRING_HASHES,
expr.span,
lit_span,
"unnecessary hashes around raw string literal",
|diag| {
let (start, end) = hash_spans(expr.span, prefix.len(), req, max);
let (start, end) = hash_spans(lit_span, prefix.len(), req, max);
let message = match max - req {
_ if req == 0 => format!("remove all the hashes around the {descr} literal"),
@ -170,7 +199,6 @@ impl EarlyLintPass for RawStrings {
);
}
}
}
}
/// Returns spans pointing at the unneeded hashes, e.g. for a `req` of `1` and `max` of `3`:

View File

@ -65,11 +65,11 @@ impl LateLintPass<'_> for RcCloneInVecInit {
fn loop_init_suggestion(elem: &str, len: &str, indent: &str) -> String {
format!(
r#"{{
r"{{
{indent} let mut v = Vec::with_capacity({len});
{indent} (0..{len}).for_each(|_| v.push({elem}));
{indent} v
{indent}}}"#
{indent}}}"
)
}

View File

@ -6,7 +6,7 @@ use clippy_utils::source::SpanRangeExt;
use clippy_utils::{def_path_res_with_base, find_crates, path_def_id, paths};
use rustc_ast::ast::{LitKind, StrStyle};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{BorrowKind, Expr, ExprKind};
use rustc_hir::{BorrowKind, Expr, ExprKind, OwnerId};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{BytePos, Span};
@ -55,6 +55,44 @@ declare_clippy_lint! {
"trivial regular expressions"
}
declare_clippy_lint! {
/// ### What it does
///
/// Checks for [regex](https://crates.io/crates/regex) compilation inside a loop with a literal.
///
/// ### Why is this bad?
///
/// Compiling a regex is a much more expensive operation than using one, and a compiled regex can be used multiple times.
/// This is documented as an antipattern [on the regex documentation](https://docs.rs/regex/latest/regex/#avoid-re-compiling-regexes-especially-in-a-loop)
///
/// ### Example
/// ```no_run
/// # let haystacks = [""];
/// # const MY_REGEX: &str = "a.b";
/// for haystack in haystacks {
/// let regex = regex::Regex::new(MY_REGEX).unwrap();
/// if regex.is_match(haystack) {
/// // Perform operation
/// }
/// }
/// ```
/// can be replaced with
/// ```no_run
/// # let haystacks = [""];
/// # const MY_REGEX: &str = "a.b";
/// let regex = regex::Regex::new(MY_REGEX).unwrap();
/// for haystack in haystacks {
/// if regex.is_match(haystack) {
/// // Perform operation
/// }
/// }
/// ```
#[clippy::version = "1.83.0"]
pub REGEX_CREATION_IN_LOOPS,
perf,
"regular expression compilation performed in a loop"
}
#[derive(Copy, Clone)]
enum RegexKind {
Unicode,
@ -66,9 +104,10 @@ enum RegexKind {
#[derive(Default)]
pub struct Regex {
definitions: DefIdMap<RegexKind>,
loop_stack: Vec<(OwnerId, Span)>,
}
impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]);
impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX, REGEX_CREATION_IN_LOOPS]);
impl<'tcx> LateLintPass<'tcx> for Regex {
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
@ -99,12 +138,34 @@ impl<'tcx> LateLintPass<'tcx> for Regex {
&& let Some(def_id) = path_def_id(cx, fun)
&& let Some(regex_kind) = self.definitions.get(&def_id)
{
if let Some(&(loop_item_id, loop_span)) = self.loop_stack.last()
&& loop_item_id == fun.hir_id.owner
&& (matches!(arg.kind, ExprKind::Lit(_)) || const_str(cx, arg).is_some())
{
span_lint_and_help(
cx,
REGEX_CREATION_IN_LOOPS,
fun.span,
"compiling a regex in a loop",
Some(loop_span),
"move the regex construction outside this loop",
);
}
match regex_kind {
RegexKind::Unicode => check_regex(cx, arg, true),
RegexKind::UnicodeSet => check_set(cx, arg, true),
RegexKind::Bytes => check_regex(cx, arg, false),
RegexKind::BytesSet => check_set(cx, arg, false),
}
} else if let ExprKind::Loop(block, _, _, span) = expr.kind {
self.loop_stack.push((block.hir_id.owner, span));
}
}
fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if matches!(expr.kind, ExprKind::Loop(..)) {
self.loop_stack.pop();
}
}
}

View File

@ -357,7 +357,7 @@ fn check_final_expr<'tcx>(
let replacement = if let Some(inner_expr) = inner {
// if desugar of `do yeet`, don't lint
if let ExprKind::Call(path_expr, _) = inner_expr.kind
if let ExprKind::Call(path_expr, [_]) = inner_expr.kind
&& let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind
{
return;

View File

@ -421,11 +421,10 @@ fn dummy_stmt_expr<'any>(expr: &'any hir::Expr<'any>) -> hir::Stmt<'any> {
}
fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: &Ident, lcx: &LateContext<'_>) -> bool {
if let hir::ExprKind::Call(fun, args) = expr.kind
if let hir::ExprKind::Call(fun, [first_arg]) = expr.kind
&& let hir::ExprKind::Path(hir::QPath::Resolved(_, fun_path)) = &fun.kind
&& let Res::Def(DefKind::Fn, did) = fun_path.res
&& lcx.tcx.is_diagnostic_item(sym::mem_drop, did)
&& let [first_arg, ..] = args
{
let has_ident = |local_expr: &hir::Expr<'_>| {
if let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &local_expr.kind

View File

@ -34,7 +34,7 @@ declare_lint_pass!(SizeOfInElementCount => [SIZE_OF_IN_ELEMENT_COUNT]);
fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool) -> Option<Ty<'tcx>> {
match expr.kind {
ExprKind::Call(count_func, _func_args) => {
ExprKind::Call(count_func, _) => {
if !inverted
&& let ExprKind::Path(ref count_func_qpath) = count_func.kind
&& let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id()

View File

@ -152,7 +152,7 @@ impl SlowVectorInit {
&& is_path_diagnostic_item(cx, func, sym::vec_with_capacity)
{
Some(InitializedSize::Initialized(len_expr))
} else if matches!(expr.kind, ExprKind::Call(func, _) if is_path_diagnostic_item(cx, func, sym::vec_new)) {
} else if matches!(expr.kind, ExprKind::Call(func, []) if is_path_diagnostic_item(cx, func, sym::vec_new)) {
Some(InitializedSize::Uninitialized)
} else {
None
@ -268,7 +268,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> {
/// Returns `true` if give expression is `repeat(0).take(...)`
fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool {
if let ExprKind::MethodCall(take_path, recv, [len_arg, ..], _) = expr.kind
if let ExprKind::MethodCall(take_path, recv, [len_arg], _) = expr.kind
&& take_path.ident.name == sym!(take)
// Check that take is applied to `repeat(0)`
&& self.is_repeat_zero(recv)

View File

@ -253,18 +253,17 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
use rustc_ast::LitKind;
if let ExprKind::Call(fun, args) = e.kind
if let ExprKind::Call(fun, [bytes_arg]) = e.kind
// Find std::str::converts::from_utf8
&& is_path_diagnostic_item(cx, fun, sym::str_from_utf8)
// Find string::as_bytes
&& let ExprKind::AddrOf(BorrowKind::Ref, _, args) = args[0].kind
&& let ExprKind::AddrOf(BorrowKind::Ref, _, args) = bytes_arg.kind
&& let ExprKind::Index(left, right, _) = args.kind
&& let (method_names, expressions, _) = method_calls(left, 1)
&& method_names.len() == 1
&& method_names == [sym!(as_bytes)]
&& expressions.len() == 1
&& expressions[0].1.is_empty()
&& method_names[0] == sym!(as_bytes)
// Check for slicer
&& let ExprKind::Struct(QPath::LangItem(LangItem::Range, ..), _, _) = right.kind
@ -393,7 +392,7 @@ impl<'tcx> LateLintPass<'tcx> for StrToString {
return;
}
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind
&& path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)
&& let ty::Ref(_, ty, ..) = ty.kind()
@ -449,7 +448,7 @@ impl<'tcx> LateLintPass<'tcx> for StringToString {
return;
}
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind
&& path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)
&& is_type_lang_item(cx, ty, LangItem::String)

View File

@ -51,9 +51,8 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
None
}
},
hir::ExprKind::Call(to_digits_call, to_digit_args) => {
if let [char_arg, radix_arg] = *to_digit_args
&& let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind
hir::ExprKind::Call(to_digits_call, [char_arg, radix_arg]) => {
if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind
&& let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id)
&& let Some(to_digits_def_id) = to_digits_call_res.opt_def_id()
&& match_def_path(cx, to_digits_def_id, &[

View File

@ -10,7 +10,7 @@ use rustc_data_structures::unhash::UnhashMap;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{
GenericArg, GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath,
GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath,
TraitBoundModifier, TraitItem, TraitRef, Ty, TyKind, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass};
@ -152,7 +152,10 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
.filter_map(get_trait_info_from_bound)
.for_each(|(trait_item_res, trait_item_segments, span)| {
if let Some(self_segments) = self_bounds_map.get(&trait_item_res) {
if SpanlessEq::new(cx).eq_path_segments(self_segments, trait_item_segments) {
if SpanlessEq::new(cx)
.paths_by_resolution()
.eq_path_segments(self_segments, trait_item_segments)
{
span_lint_and_help(
cx,
TRAIT_DUPLICATION_IN_BOUNDS,
@ -302,7 +305,7 @@ impl TraitBounds {
}
}
fn check_trait_bound_duplication(cx: &LateContext<'_>, generics: &'_ Generics<'_>) {
fn check_trait_bound_duplication<'tcx>(cx: &LateContext<'tcx>, generics: &'_ Generics<'tcx>) {
if generics.span.from_expansion() {
return;
}
@ -314,6 +317,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, generics: &'_ Generics<'_
// |
// collects each of these where clauses into a set keyed by generic name and comparable trait
// eg. (T, Clone)
#[expect(clippy::mutable_key_type)]
let where_predicates = generics
.predicates
.iter()
@ -367,11 +371,27 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, generics: &'_ Generics<'_
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct ComparableTraitRef(Res, Vec<Res>);
impl Default for ComparableTraitRef {
fn default() -> Self {
Self(Res::Err, Vec::new())
struct ComparableTraitRef<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
trait_ref: &'tcx TraitRef<'tcx>,
modifier: TraitBoundModifier,
}
impl PartialEq for ComparableTraitRef<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.modifier == other.modifier
&& SpanlessEq::new(self.cx)
.paths_by_resolution()
.eq_path(self.trait_ref.path, other.trait_ref.path)
}
}
impl Eq for ComparableTraitRef<'_, '_> {}
impl Hash for ComparableTraitRef<'_, '_> {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut s = SpanlessHash::new(self.cx).paths_by_resolution();
s.hash_path(self.trait_ref.path);
state.write_u64(s.finish());
self.modifier.hash(state);
}
}
@ -392,69 +412,41 @@ fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'
}
}
fn get_ty_res(ty: Ty<'_>) -> Option<Res> {
match ty.kind {
TyKind::Path(QPath::Resolved(_, path)) => Some(path.res),
TyKind::Path(QPath::TypeRelative(ty, _)) => get_ty_res(*ty),
_ => None,
}
}
// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
ComparableTraitRef(
trait_ref.path.res,
trait_ref
.path
.segments
.iter()
.filter_map(|segment| {
// get trait bound type arguments
Some(segment.args?.args.iter().filter_map(|arg| {
if let GenericArg::Type(ty) = arg {
return get_ty_res(**ty);
}
None
}))
})
.flatten()
.collect(),
)
}
fn rollup_traits(
cx: &LateContext<'_>,
bounds: &[GenericBound<'_>],
fn rollup_traits<'cx, 'tcx>(
cx: &'cx LateContext<'tcx>,
bounds: &'tcx [GenericBound<'tcx>],
msg: &'static str,
) -> Vec<(ComparableTraitRef, Span)> {
) -> Vec<(ComparableTraitRef<'cx, 'tcx>, Span)> {
// Source order is needed for joining spans
let mut map = FxIndexMap::default();
let mut repeated_res = false;
let only_comparable_trait_refs = |bound: &GenericBound<'_>| {
let only_comparable_trait_refs = |bound: &'tcx GenericBound<'tcx>| {
if let GenericBound::Trait(t) = bound {
Some((into_comparable_trait_ref(&t.trait_ref), t.span))
Some((
ComparableTraitRef {
cx,
trait_ref: &t.trait_ref,
modifier: t.modifiers,
},
t.span,
))
} else {
None
}
};
let mut i = 0usize;
for bound in bounds.iter().filter_map(only_comparable_trait_refs) {
let (comparable_bound, span_direct) = bound;
match map.entry(comparable_bound) {
IndexEntry::Occupied(_) => repeated_res = true,
IndexEntry::Vacant(e) => {
e.insert((span_direct, i));
i += 1;
e.insert(span_direct);
},
}
}
// Put bounds in source order
let mut comparable_bounds = vec![Default::default(); map.len()];
for (k, (v, i)) in map {
comparable_bounds[i] = (k, v);
}
let comparable_bounds: Vec<_> = map.into_iter().collect();
if repeated_res && let [first_trait, .., last_trait] = bounds {
let all_trait_span = first_trait.span().to(last_trait.span());

View File

@ -25,14 +25,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
return;
}
let args: Vec<_> = match expr.kind {
ExprKind::Call(_, args) => args.iter().collect(),
ExprKind::MethodCall(_, receiver, args, _) => std::iter::once(receiver).chain(args.iter()).collect(),
let (reciever, args) = match expr.kind {
ExprKind::Call(_, args) => (None, args),
ExprKind::MethodCall(_, receiver, args, _) => (Some(receiver), args),
_ => return,
};
let args_to_recover = args
let args_to_recover = reciever
.into_iter()
.chain(args)
.filter(|arg| {
if cx.typeck_results().expr_ty(arg).is_unit() && !utils::is_unit_literal(arg) {
!matches!(

View File

@ -0,0 +1,158 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::path_res;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{FnKind, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnRetTy, Lit, MutTy, Mutability, PrimTy, Ty, TyKind, intravisit};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
declare_clippy_lint! {
/// ### What it does
///
/// Detects functions that are written to return `&str` that could return `&'static str` but instead return a `&'a str`.
///
/// ### Why is this bad?
///
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unneccessary allocations or confusion.
/// This is also most likely what you meant to write.
///
/// ### Example
/// ```no_run
/// # struct MyType;
/// impl MyType {
/// fn returns_literal(&self) -> &str {
/// "Literal"
/// }
/// }
/// ```
/// Use instead:
/// ```no_run
/// # struct MyType;
/// impl MyType {
/// fn returns_literal(&self) -> &'static str {
/// "Literal"
/// }
/// }
/// ```
/// Or, in case you may return a non-literal `str` in future:
/// ```no_run
/// # struct MyType;
/// impl MyType {
/// fn returns_literal<'a>(&'a self) -> &'a str {
/// "Literal"
/// }
/// }
/// ```
#[clippy::version = "1.83.0"]
pub UNNECESSARY_LITERAL_BOUND,
pedantic,
"detects &str that could be &'static str in function return types"
}
declare_lint_pass!(UnnecessaryLiteralBound => [UNNECESSARY_LITERAL_BOUND]);
fn extract_anonymous_ref<'tcx>(hir_ty: &Ty<'tcx>) -> Option<&'tcx Ty<'tcx>> {
let TyKind::Ref(lifetime, MutTy { ty, mutbl }) = hir_ty.kind else {
return None;
};
if !lifetime.is_anonymous() || !matches!(mutbl, Mutability::Not) {
return None;
}
Some(ty)
}
fn is_str_literal(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Lit(Lit {
node: LitKind::Str(..),
..
}),
)
}
struct FindNonLiteralReturn;
impl<'hir> Visitor<'hir> for FindNonLiteralReturn {
type Result = std::ops::ControlFlow<()>;
type NestedFilter = intravisit::nested_filter::None;
fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> Self::Result {
if let ExprKind::Ret(Some(ret_val_expr)) = expr.kind
&& !is_str_literal(ret_val_expr)
{
Self::Result::Break(())
} else {
intravisit::walk_expr(self, expr)
}
}
}
fn check_implicit_returns_static_str(body: &Body<'_>) -> bool {
// TODO: Improve this to the same complexity as the Visitor to catch more implicit return cases.
if let ExprKind::Block(block, _) = body.value.kind
&& let Some(implicit_ret) = block.expr
{
return is_str_literal(implicit_ret);
}
false
}
fn check_explicit_returns_static_str(expr: &Expr<'_>) -> bool {
let mut visitor = FindNonLiteralReturn;
visitor.visit_expr(expr).is_continue()
}
impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>,
span: Span,
_: LocalDefId,
) {
if span.from_expansion() {
return;
}
// Checking closures would be a little silly
if matches!(kind, FnKind::Closure) {
return;
}
// Check for `-> &str`
let FnRetTy::Return(ret_hir_ty) = decl.output else {
return;
};
let Some(inner_hir_ty) = extract_anonymous_ref(ret_hir_ty) else {
return;
};
if path_res(cx, inner_hir_ty) != Res::PrimTy(PrimTy::Str) {
return;
}
// Check for all return statements returning literals
if check_explicit_returns_static_str(body.value) && check_implicit_returns_static_str(body) {
span_lint_and_sugg(
cx,
UNNECESSARY_LITERAL_BOUND,
ret_hir_ty.span,
"returning a `str` unnecessarily tied to the lifetime of arguments",
"try",
"&'static str".into(), // how ironic, a lint about `&'static str` requiring a `String` alloc...
Applicability::MachineApplicable,
);
}
}
}

View File

@ -38,13 +38,11 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
if expr.span.from_expansion() {
return;
}
if let hir::ExprKind::MethodCall(path, recv, args, ..) = expr.kind
if let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind
&& let Some(sym::Option | sym::Result) = get_type_diagnostic_name(cx, cx.typeck_results().expr_ty(recv))
{
let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, constructor_args) =
recv.kind
let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, [arg, ..]) = recv.kind
&& let hir::ExprKind::Path(constructor_path) = constructor.kind
&& let Some(arg) = constructor_args.first()
{
if constructor.span.from_expansion() || arg.span.from_expansion() {
return;
@ -70,9 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
_ => return,
}
if let Some(map_arg) = args.first()
&& let hir::ExprKind::Path(fun) = map_arg.kind
{
if let hir::ExprKind::Path(fun) = map_arg.kind {
if map_arg.span.from_expansion() {
return;
}

View File

@ -52,8 +52,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings {
Applicability::MachineApplicable,
);
} else if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id)
&& let [.., last_arg] = args
&& let ExprKind::Lit(spanned) = &last_arg.kind
&& let [arg] = args
&& let ExprKind::Lit(spanned) = &arg.kind
&& let LitKind::Str(symbol, _) = spanned.node
&& symbol.is_empty()
&& let inner_expr_type = cx.typeck_results().expr_ty(inner_expr)

View File

@ -222,7 +222,7 @@ fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
}
fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
while let ExprKind::Call(func, [ref arg_0, ..]) = expr.kind
while let ExprKind::Call(func, [ref arg_0]) = expr.kind
&& matches!(
func.kind,
ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
@ -244,7 +244,7 @@ fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
/// waited on. Otherwise return None.
fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
if let ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
if let ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
if let ExprKind::Call(func, [ref arg_0]) = expr.kind {
if matches!(
func.kind,
ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))

View File

@ -26,7 +26,7 @@ declare_clippy_lint! {
/// # fn some_function() -> Result<(), ()> { Ok(()) }
/// let _ = some_function();
/// ```
#[clippy::version = "1.70.0"]
#[clippy::version = "1.82.0"]
pub UNUSED_RESULT_OK,
restriction,
"Use of `.ok()` to silence `Result`'s `#[must_use]` is misleading. Use `let _ =` instead."

View File

@ -153,13 +153,12 @@ fn collect_unwrap_info<'tcx>(
}
} else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
} else if let ExprKind::MethodCall(method_name, receiver, args, _) = &expr.kind
} else if let ExprKind::MethodCall(method_name, receiver, [], _) = &expr.kind
&& let Some(local_id) = path_to_local(receiver)
&& let ty = cx.typeck_results().expr_ty(receiver)
&& let name = method_name.ident.as_str()
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name))
{
assert!(args.is_empty());
let unwrappable = match name {
"is_some" | "is_ok" => true,
"is_err" | "is_none" => false,
@ -208,7 +207,7 @@ struct MutationVisitor<'tcx> {
/// expression: that will be where the actual method call is.
fn is_option_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
if let Node::Expr(mutating_expr) = tcx.parent_hir_node(expr_id)
&& let ExprKind::MethodCall(path, ..) = mutating_expr.kind
&& let ExprKind::MethodCall(path, _, [], _) = mutating_expr.kind
{
path.ident.name.as_str() == "as_mut"
} else {
@ -275,7 +274,7 @@ enum AsRefKind {
/// Checks if the expression is a method call to `as_{ref,mut}` and returns the receiver of it.
/// If it isn't, the expression itself is returned.
fn consume_option_as_ref<'tcx>(expr: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, Option<AsRefKind>) {
if let ExprKind::MethodCall(path, recv, ..) = expr.kind {
if let ExprKind::MethodCall(path, recv, [], _) = expr.kind {
if path.ident.name == sym::as_ref {
(recv, Some(AsRefKind::AsRef))
} else if path.ident.name.as_str() == "as_mut" {
@ -303,7 +302,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
self.visit_branch(expr, cond, else_inner, true);
}
} else {
// find `unwrap[_err]()` calls:
// find `unwrap[_err]()` or `expect("...")` calls:
if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind
&& let (self_arg, as_ref_kind) = consume_option_as_ref(self_arg)
&& let Some(id) = path_to_local(self_arg)

View File

@ -129,7 +129,7 @@ fn into_iter_bound<'tcx>(
/// Extracts the receiver of a `.into_iter()` method call.
fn into_iter_call<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>) -> Option<&'hir Expr<'hir>> {
if let ExprKind::MethodCall(name, recv, _, _) = expr.kind
if let ExprKind::MethodCall(name, recv, [], _) = expr.kind
&& is_trait_method(cx, expr, sym::IntoIterator)
&& name.ident.name == sym::into_iter
{
@ -173,7 +173,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
}
},
ExprKind::MethodCall(name, recv, ..) => {
ExprKind::MethodCall(name, recv, [], _) => {
if is_trait_method(cx, e, sym::Into) && name.ident.as_str() == "into" {
let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(recv);

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