mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Merge commit '2dc84cb744ad19d187871afb0385a616d80c209d' into clippy-subtree-update
This commit is contained in:
@@ -181,7 +181,7 @@ jobs:
|
||||
|
||||
# Download
|
||||
- name: Download target dir
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: binaries
|
||||
path: target/debug
|
||||
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Download JSON
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
|
||||
- name: Store PR number
|
||||
run: echo ${{ github.event.pull_request.number }} > pr.txt
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: summary
|
||||
path: untrusted
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Format comment
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const fs = require("fs");
|
||||
|
||||
+3
-1
@@ -17,7 +17,7 @@ Current stable, released 2025-09-18
|
||||
Note: This Clippy release does not introduce many new lints and is focused entirely on bug fixes — see
|
||||
[#15086](https://github.com/rust-lang/rust-clippy/issues/15086) for more details.
|
||||
|
||||
## New Lints
|
||||
### New Lints
|
||||
|
||||
* Added [`manual_is_multiple_of`] to `complexity` [#14292](https://github.com/rust-lang/rust-clippy/pull/14292)
|
||||
* Added [`doc_broken_link`] to `pedantic` [#13696](https://github.com/rust-lang/rust-clippy/pull/13696)
|
||||
@@ -6598,6 +6598,7 @@ Released 2018-09-13
|
||||
[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
|
||||
[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
|
||||
[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
|
||||
[`self_only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_only_used_in_recursion
|
||||
[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
|
||||
[`semicolon_inside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_inside_block
|
||||
[`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block
|
||||
@@ -6703,6 +6704,7 @@ Released 2018-09-13
|
||||
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
|
||||
[`unbuffered_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#unbuffered_bytes
|
||||
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
|
||||
[`unchecked_time_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction
|
||||
[`unconditional_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion
|
||||
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
|
||||
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
|
||||
|
||||
+6
-1
@@ -38,13 +38,18 @@ ui_test = "0.30.2"
|
||||
regex = "1.5.5"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde_json = "1.0.122"
|
||||
toml = "0.7.3"
|
||||
walkdir = "2.3"
|
||||
filetime = "0.2.9"
|
||||
itertools = "0.12"
|
||||
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
|
||||
askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] }
|
||||
|
||||
[dev-dependencies.toml]
|
||||
version = "0.9.7"
|
||||
default-features = false
|
||||
# preserve_order keeps diagnostic output in file order
|
||||
features = ["parse", "preserve_order"]
|
||||
|
||||
[build-dependencies]
|
||||
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
|
||||
|
||||
|
||||
@@ -24,12 +24,11 @@ use rustc_span::symbol::sym;
|
||||
|
||||
impl LateLintPass<'_> for CheckIteratorTraitLint {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
let implements_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
|
||||
implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[])
|
||||
});
|
||||
if implements_iterator {
|
||||
// [...]
|
||||
}
|
||||
let implements_iterator = (cx.tcx.get_diagnostic_item(sym::Iterator))
|
||||
.is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(expr), id, &[]));
|
||||
if implements_iterator {
|
||||
// [...]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,6 +845,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
||||
* [`from_over_into`](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into)
|
||||
* [`if_then_some_else_none`](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none)
|
||||
* [`index_refutable_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice)
|
||||
* [`inefficient_to_string`](https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string)
|
||||
* [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error)
|
||||
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
|
||||
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
|
||||
@@ -883,6 +884,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
||||
* [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
|
||||
* [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics)
|
||||
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
|
||||
* [`or_fun_call`](https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call)
|
||||
* [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
|
||||
* [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark)
|
||||
* [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names)
|
||||
@@ -894,9 +896,10 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
||||
* [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref)
|
||||
* [`tuple_array_conversions`](https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions)
|
||||
* [`type_repetition_in_bounds`](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds)
|
||||
* [`unchecked_duration_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction)
|
||||
* [`unchecked_time_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction)
|
||||
* [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args)
|
||||
* [`unnecessary_lazy_evaluations`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations)
|
||||
* [`unnecessary_unwrap`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap)
|
||||
* [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns)
|
||||
* [`unused_trait_names`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names)
|
||||
* [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self)
|
||||
|
||||
@@ -741,6 +741,7 @@ fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
|
||||
from_over_into,
|
||||
if_then_some_else_none,
|
||||
index_refutable_slice,
|
||||
inefficient_to_string,
|
||||
io_other_error,
|
||||
iter_kv_map,
|
||||
legacy_numeric_constants,
|
||||
@@ -779,6 +780,7 @@ fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
|
||||
needless_borrow,
|
||||
non_std_lazy_statics,
|
||||
option_as_ref_deref,
|
||||
or_fun_call,
|
||||
ptr_as_ptr,
|
||||
question_mark,
|
||||
redundant_field_names,
|
||||
@@ -790,9 +792,10 @@ fn span_from_toml_range(file: &SourceFile, span: Range<usize>) -> Span {
|
||||
transmute_ptr_to_ref,
|
||||
tuple_array_conversions,
|
||||
type_repetition_in_bounds,
|
||||
unchecked_duration_subtraction,
|
||||
unchecked_time_subtraction,
|
||||
uninlined_format_args,
|
||||
unnecessary_lazy_evaluations,
|
||||
unnecessary_unwrap,
|
||||
unnested_or_patterns,
|
||||
unused_trait_names,
|
||||
use_self,
|
||||
|
||||
@@ -18,12 +18,17 @@ itertools = "0.12"
|
||||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.7.3"
|
||||
unicode-normalization = "0.1"
|
||||
unicode-script = { version = "0.5", default-features = false }
|
||||
semver = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
[dependencies.toml]
|
||||
version = "0.9.7"
|
||||
default-features = false
|
||||
# preserve_order keeps diagnostic output in file order
|
||||
features = ["parse", "preserve_order"]
|
||||
|
||||
[dev-dependencies]
|
||||
walkdir = "2.3"
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_inside_always_const_context;
|
||||
use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_node};
|
||||
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node};
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::{is_inside_always_const_context, msrvs};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@@ -28,56 +31,60 @@
|
||||
"`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
|
||||
impl_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
|
||||
pub struct AssertionsOnConstants {
|
||||
msrv: Msrv,
|
||||
}
|
||||
impl AssertionsOnConstants {
|
||||
pub fn new(conf: &Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, e) else {
|
||||
return;
|
||||
};
|
||||
let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
|
||||
Some(sym::debug_assert_macro) => true,
|
||||
Some(sym::assert_macro) => false,
|
||||
_ => return,
|
||||
};
|
||||
let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else {
|
||||
return;
|
||||
};
|
||||
let Some(Constant::Bool(val)) = ConstEvalCtxt::new(cx).eval(condition) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match condition.kind {
|
||||
ExprKind::Path(..) | ExprKind::Lit(_) => {},
|
||||
_ if is_inside_always_const_context(cx.tcx, e.hir_id) => return,
|
||||
_ => {},
|
||||
}
|
||||
|
||||
if val {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
ASSERTIONS_ON_CONSTANTS,
|
||||
macro_call.span,
|
||||
format!(
|
||||
"`{}!(true)` will be optimized out by the compiler",
|
||||
cx.tcx.item_name(macro_call.def_id)
|
||||
),
|
||||
None,
|
||||
"remove it",
|
||||
);
|
||||
} else if !is_debug {
|
||||
let (assert_arg, panic_arg) = match panic_expn {
|
||||
PanicExpn::Empty => ("", ""),
|
||||
_ => (", ..", ".."),
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, e)
|
||||
&& let is_debug = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
|
||||
Some(sym::debug_assert_macro) => true,
|
||||
Some(sym::assert_macro) => false,
|
||||
_ => return,
|
||||
}
|
||||
&& let Some((condition, _)) = find_assert_args(cx, e, macro_call.expn)
|
||||
&& let Some((Constant::Bool(assert_val), const_src)) =
|
||||
ConstEvalCtxt::new(cx).eval_with_source(condition, macro_call.span.ctxt())
|
||||
&& let in_const_context = is_inside_always_const_context(cx.tcx, e.hir_id)
|
||||
&& (const_src.is_local() || !in_const_context)
|
||||
&& !(is_debug && as_bool_lit(condition) == Some(false))
|
||||
{
|
||||
let (msg, help) = if !const_src.is_local() {
|
||||
let help = if self.msrv.meets(cx, msrvs::CONST_BLOCKS) {
|
||||
"consider moving this into a const block: `const { assert!(..) }`"
|
||||
} else if self.msrv.meets(cx, msrvs::CONST_PANIC) {
|
||||
"consider moving this to an anonymous constant: `const _: () = { assert!(..); }`"
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
("this assertion has a constant value", help)
|
||||
} else if assert_val {
|
||||
("this assertion is always `true`", "remove the assertion")
|
||||
} else {
|
||||
(
|
||||
"this assertion is always `false`",
|
||||
"replace this with `panic!()` or `unreachable!()`",
|
||||
)
|
||||
};
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
ASSERTIONS_ON_CONSTANTS,
|
||||
macro_call.span,
|
||||
format!("`assert!(false{assert_arg})` should probably be replaced"),
|
||||
None,
|
||||
format!("use `panic!({panic_arg})` or `unreachable!({panic_arg})`"),
|
||||
);
|
||||
|
||||
span_lint_and_help(cx, ASSERTIONS_ON_CONSTANTS, macro_call.span, msg, None, help);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bool_lit(e: &Expr<'_>) -> Option<bool> {
|
||||
if let ExprKind::Lit(l) = e.kind
|
||||
&& let LitKind::Bool(b) = l.node
|
||||
{
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,72 +4,10 @@
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{LateContext, unerased_lint_store};
|
||||
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use toml::Spanned;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct LintConfigTable {
|
||||
level: String,
|
||||
priority: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
enum LintConfig {
|
||||
Level(String),
|
||||
Table(LintConfigTable),
|
||||
}
|
||||
|
||||
impl LintConfig {
|
||||
fn level(&self) -> &str {
|
||||
match self {
|
||||
LintConfig::Level(level) => level,
|
||||
LintConfig::Table(table) => &table.level,
|
||||
}
|
||||
}
|
||||
|
||||
fn priority(&self) -> i64 {
|
||||
match self {
|
||||
LintConfig::Level(_) => 0,
|
||||
LintConfig::Table(table) => table.priority.unwrap_or(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_implicit(&self) -> bool {
|
||||
if let LintConfig::Table(table) = self {
|
||||
table.priority.is_none()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LintTable = BTreeMap<Spanned<String>, Spanned<LintConfig>>;
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
struct Lints {
|
||||
#[serde(default)]
|
||||
rust: LintTable,
|
||||
#[serde(default)]
|
||||
clippy: LintTable,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
struct Workspace {
|
||||
#[serde(default)]
|
||||
lints: Lints,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct CargoToml {
|
||||
#[serde(default)]
|
||||
lints: Lints,
|
||||
#[serde(default)]
|
||||
workspace: Workspace,
|
||||
}
|
||||
use toml::de::{DeTable, DeValue};
|
||||
|
||||
fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
|
||||
Span::new(
|
||||
@@ -80,66 +18,89 @@ fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
|
||||
)
|
||||
}
|
||||
|
||||
fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet<&str>, file: &SourceFile) {
|
||||
struct LintConfig<'a> {
|
||||
sp: Range<usize>,
|
||||
level: &'a str,
|
||||
priority: Option<i64>,
|
||||
}
|
||||
impl<'a> LintConfig<'a> {
|
||||
fn priority(&self) -> i64 {
|
||||
self.priority.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn is_implicit(&self) -> bool {
|
||||
self.priority.is_none()
|
||||
}
|
||||
|
||||
fn parse(value: &'a Spanned<DeValue<'a>>) -> Option<Self> {
|
||||
let sp = value.span();
|
||||
let (level, priority) = match value.get_ref() {
|
||||
DeValue::String(level) => (&**level, None),
|
||||
DeValue::Table(tbl) => {
|
||||
let level = tbl.get("level")?.get_ref().as_str()?;
|
||||
let priority = if let Some(priority) = tbl.get("priority") {
|
||||
let priority = priority.get_ref().as_integer()?;
|
||||
Some(i64::from_str_radix(priority.as_str(), priority.radix()).ok()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(level, priority)
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
Some(Self { sp, level, priority })
|
||||
}
|
||||
}
|
||||
|
||||
fn check_table(cx: &LateContext<'_>, table: &DeTable<'_>, known_groups: &FxHashSet<&str>, file: &SourceFile) {
|
||||
let mut lints = Vec::new();
|
||||
let mut groups = Vec::new();
|
||||
for (name, config) in table {
|
||||
if name.get_ref() == "warnings" {
|
||||
continue;
|
||||
}
|
||||
|
||||
if known_groups.contains(name.get_ref().as_str()) {
|
||||
groups.push((name, config));
|
||||
} else {
|
||||
lints.push((name, config.into_inner()));
|
||||
if name.get_ref() != "warnings"
|
||||
&& let Some(config) = LintConfig::parse(config)
|
||||
{
|
||||
if known_groups.contains(&**name.get_ref()) {
|
||||
groups.push((name, config));
|
||||
} else {
|
||||
lints.push((name, config));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (group, group_config) in groups {
|
||||
let priority = group_config.get_ref().priority();
|
||||
let level = group_config.get_ref().level();
|
||||
if let Some((conflict, _)) = lints
|
||||
.iter()
|
||||
.rfind(|(_, lint_config)| lint_config.priority() == priority && lint_config.level() != level)
|
||||
{
|
||||
if let Some((conflict, _)) = lints.iter().rfind(|(_, lint_config)| {
|
||||
lint_config.priority() == group_config.priority() && lint_config.level != group_config.level
|
||||
}) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LINT_GROUPS_PRIORITY,
|
||||
toml_span(group.span(), file),
|
||||
format!(
|
||||
"lint group `{}` has the same priority ({priority}) as a lint",
|
||||
group.as_ref()
|
||||
"lint group `{}` has the same priority ({}) as a lint",
|
||||
group.as_ref(),
|
||||
group_config.priority(),
|
||||
),
|
||||
|diag| {
|
||||
let config_span = toml_span(group_config.span(), file);
|
||||
let config_span = toml_span(group_config.sp.clone(), file);
|
||||
|
||||
if group_config.as_ref().is_implicit() {
|
||||
if group_config.is_implicit() {
|
||||
diag.span_label(config_span, "has an implicit priority of 0");
|
||||
}
|
||||
diag.span_label(toml_span(conflict.span(), file), "has the same priority as this lint");
|
||||
diag.note("the order of the lints in the table is ignored by Cargo");
|
||||
|
||||
let mut suggestion = String::new();
|
||||
let low_priority = lints
|
||||
.iter()
|
||||
.map(|(_, config)| config.priority().saturating_sub(1))
|
||||
.map(|(_, lint_config)| lint_config.priority().saturating_sub(1))
|
||||
.min()
|
||||
.unwrap_or(-1);
|
||||
Serialize::serialize(
|
||||
&LintConfigTable {
|
||||
level: level.into(),
|
||||
priority: Some(low_priority),
|
||||
},
|
||||
toml::ser::ValueSerializer::new(&mut suggestion),
|
||||
)
|
||||
.unwrap();
|
||||
diag.span_suggestion_verbose(
|
||||
config_span,
|
||||
format!(
|
||||
"to have lints override the group set `{}` to a lower priority",
|
||||
group.as_ref()
|
||||
),
|
||||
suggestion,
|
||||
format!("{{ level = {:?}, priority = {low_priority} }}", group_config.level,),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
@@ -148,10 +109,29 @@ fn check_table(cx: &LateContext<'_>, table: LintTable, known_groups: &FxHashSet<
|
||||
}
|
||||
}
|
||||
|
||||
struct LintTbls<'a> {
|
||||
rust: Option<&'a DeTable<'a>>,
|
||||
clippy: Option<&'a DeTable<'a>>,
|
||||
}
|
||||
fn get_lint_tbls<'a>(tbl: &'a DeTable<'a>) -> LintTbls<'a> {
|
||||
if let Some(lints) = tbl.get("lints")
|
||||
&& let Some(lints) = lints.get_ref().as_table()
|
||||
{
|
||||
let rust = lints.get("rust").and_then(|x| x.get_ref().as_table());
|
||||
let clippy = lints.get("clippy").and_then(|x| x.get_ref().as_table());
|
||||
LintTbls { rust, clippy }
|
||||
} else {
|
||||
LintTbls {
|
||||
rust: None,
|
||||
clippy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(cx: &LateContext<'_>) {
|
||||
if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml"))
|
||||
&& let Some(src) = file.src.as_deref()
|
||||
&& let Ok(cargo_toml) = toml::from_str::<CargoToml>(src)
|
||||
&& let Ok(cargo_toml) = DeTable::parse(src)
|
||||
{
|
||||
let mut rustc_groups = FxHashSet::default();
|
||||
let mut clippy_groups = FxHashSet::default();
|
||||
@@ -167,9 +147,23 @@ pub fn check(cx: &LateContext<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file);
|
||||
check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file);
|
||||
check_table(cx, cargo_toml.workspace.lints.rust, &rustc_groups, &file);
|
||||
check_table(cx, cargo_toml.workspace.lints.clippy, &clippy_groups, &file);
|
||||
let lints = get_lint_tbls(cargo_toml.get_ref());
|
||||
if let Some(lints) = lints.rust {
|
||||
check_table(cx, lints, &rustc_groups, &file);
|
||||
}
|
||||
if let Some(lints) = lints.clippy {
|
||||
check_table(cx, lints, &clippy_groups, &file);
|
||||
}
|
||||
if let Some(tbl) = cargo_toml.get_ref().get("workspace")
|
||||
&& let Some(tbl) = tbl.get_ref().as_table()
|
||||
{
|
||||
let lints = get_lint_tbls(tbl);
|
||||
if let Some(lints) = lints.rust {
|
||||
check_table(cx, lints, &rustc_groups, &file);
|
||||
}
|
||||
if let Some(lints) = lints.clippy {
|
||||
check_table(cx, lints, &clippy_groups, &file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use clippy_utils::source::{IntoSpan, SpanRangeExt};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn, sym};
|
||||
use clippy_utils::{LimitStack, get_async_fn_body, sym};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Attribute, Body, Expr, ExprKind, FnDecl};
|
||||
@@ -147,7 +147,7 @@ fn check_fn(
|
||||
def_id: LocalDefId,
|
||||
) {
|
||||
if !cx.tcx.has_attr(def_id, sym::test) {
|
||||
let expr = if is_async_fn(kind) {
|
||||
let expr = if kind.asyncness().is_async() {
|
||||
match get_async_fn_body(cx.tcx, body) {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability};
|
||||
use clippy_utils::{span_contains_non_whitespace, tokenize_with_text};
|
||||
use rustc_ast::BinOpKind;
|
||||
use clippy_utils::{can_use_if_let_chains, span_contains_non_whitespace, sym, tokenize_with_text};
|
||||
use rustc_ast::{BinOpKind, MetaItemInner};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::{LateContext, LateLintPass, Level};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::{BytePos, Span};
|
||||
use rustc_span::{BytePos, Span, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -95,14 +95,14 @@ pub fn new(conf: &'static Conf) -> Self {
|
||||
|
||||
fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
|
||||
if let Some(else_) = expr_block(else_block)
|
||||
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
|
||||
&& !else_.span.from_expansion()
|
||||
&& let ExprKind::If(else_if_cond, ..) = else_.kind
|
||||
&& !block_starts_with_significant_tokens(cx, else_block, else_, self.lint_commented_code)
|
||||
&& self.check_significant_tokens_and_expect_attrs(cx, else_block, else_, sym::collapsible_else_if)
|
||||
{
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_ELSE_IF,
|
||||
else_.hir_id,
|
||||
else_block.span,
|
||||
"this `else { if .. }` block can be collapsed",
|
||||
|diag| {
|
||||
@@ -166,15 +166,15 @@ fn check_collapsible_else_if(&self, cx: &LateContext<'_>, then_span: Span, else_
|
||||
|
||||
fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) {
|
||||
if let Some(inner) = expr_block(then)
|
||||
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
|
||||
&& let ExprKind::If(check_inner, _, None) = &inner.kind
|
||||
&& self.eligible_condition(cx, check_inner)
|
||||
&& expr.span.eq_ctxt(inner.span)
|
||||
&& !block_starts_with_significant_tokens(cx, then, inner, self.lint_commented_code)
|
||||
&& self.check_significant_tokens_and_expect_attrs(cx, then, inner, sym::collapsible_if)
|
||||
{
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
COLLAPSIBLE_IF,
|
||||
inner.hir_id,
|
||||
expr.span,
|
||||
"this `if` statement can be collapsed",
|
||||
|diag| {
|
||||
@@ -216,8 +216,46 @@ fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check:
|
||||
}
|
||||
|
||||
fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool {
|
||||
!matches!(cond.kind, ExprKind::Let(..))
|
||||
|| (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS))
|
||||
!matches!(cond.kind, ExprKind::Let(..)) || can_use_if_let_chains(cx, self.msrv)
|
||||
}
|
||||
|
||||
// Check that nothing significant can be found between the initial `{` of `inner_if` and
|
||||
// the beginning of `inner_if_expr`...
|
||||
//
|
||||
// Unless it's only an `#[expect(clippy::collapsible{,_else}_if)]` attribute, in which case we
|
||||
// _do_ need to lint, in order to actually fulfill its expectation (#13365)
|
||||
fn check_significant_tokens_and_expect_attrs(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
inner_if: &Block<'_>,
|
||||
inner_if_expr: &Expr<'_>,
|
||||
expected_lint_name: Symbol,
|
||||
) -> bool {
|
||||
match cx.tcx.hir_attrs(inner_if_expr.hir_id) {
|
||||
[] => {
|
||||
// There aren't any attributes, so just check for significant tokens
|
||||
let span = inner_if.span.split_at(1).1.until(inner_if_expr.span);
|
||||
!span_contains_non_whitespace(cx, span, self.lint_commented_code)
|
||||
},
|
||||
|
||||
[attr]
|
||||
if matches!(Level::from_attr(attr), Some((Level::Expect, _)))
|
||||
&& let Some(metas) = attr.meta_item_list()
|
||||
&& let Some(MetaItemInner::MetaItem(meta_item)) = metas.first()
|
||||
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
|
||||
&& tool.ident.name == sym::clippy
|
||||
&& [expected_lint_name, sym::style, sym::all].contains(&lint_name.ident.name) =>
|
||||
{
|
||||
// There is an `expect` attribute -- check that there is no _other_ significant text
|
||||
let span_before_attr = inner_if.span.split_at(1).1.until(attr.span());
|
||||
let span_after_attr = attr.span().between(inner_if_expr.span);
|
||||
!span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code)
|
||||
&& !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code)
|
||||
},
|
||||
|
||||
// There are other attributes, which are significant tokens -- check failed
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,18 +280,6 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check that nothing significant can be found but whitespaces between the initial `{` of `block`
|
||||
// and the beginning of `stop_at`.
|
||||
fn block_starts_with_significant_tokens(
|
||||
cx: &LateContext<'_>,
|
||||
block: &Block<'_>,
|
||||
stop_at: &Expr<'_>,
|
||||
lint_commented_code: bool,
|
||||
) -> bool {
|
||||
let span = block.span.split_at(1).1.until(stop_at.span);
|
||||
span_contains_non_whitespace(cx, span, lint_commented_code)
|
||||
}
|
||||
|
||||
/// If `block` is a block with either one expression or a statement containing an expression,
|
||||
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
|
||||
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
|
||||
@@ -84,10 +84,6 @@
|
||||
crate::collapsible_if::COLLAPSIBLE_IF_INFO,
|
||||
crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO,
|
||||
crate::comparison_chain::COMPARISON_CHAIN_INFO,
|
||||
crate::copies::BRANCHES_SHARING_CODE_INFO,
|
||||
crate::copies::IFS_SAME_COND_INFO,
|
||||
crate::copies::IF_SAME_THEN_ELSE_INFO,
|
||||
crate::copies::SAME_FUNCTIONS_IN_IF_CONDITION_INFO,
|
||||
crate::copy_iterator::COPY_ITERATOR_INFO,
|
||||
crate::crate_in_macro_def::CRATE_IN_MACRO_DEF_INFO,
|
||||
crate::create_dir::CREATE_DIR_INFO,
|
||||
@@ -204,6 +200,10 @@
|
||||
crate::if_let_mutex::IF_LET_MUTEX_INFO,
|
||||
crate::if_not_else::IF_NOT_ELSE_INFO,
|
||||
crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO,
|
||||
crate::ifs::BRANCHES_SHARING_CODE_INFO,
|
||||
crate::ifs::IFS_SAME_COND_INFO,
|
||||
crate::ifs::IF_SAME_THEN_ELSE_INFO,
|
||||
crate::ifs::SAME_FUNCTIONS_IN_IF_CONDITION_INFO,
|
||||
crate::ignored_unit_patterns::IGNORED_UNIT_PATTERNS_INFO,
|
||||
crate::impl_hash_with_borrow_str_and_bytes::IMPL_HASH_BORROW_WITH_STR_AND_BYTES_INFO,
|
||||
crate::implicit_hasher::IMPLICIT_HASHER_INFO,
|
||||
@@ -226,8 +226,6 @@
|
||||
crate::inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY_INFO,
|
||||
crate::init_numbered_fields::INIT_NUMBERED_FIELDS_INFO,
|
||||
crate::inline_fn_without_body::INLINE_FN_WITHOUT_BODY_INFO,
|
||||
crate::instant_subtraction::MANUAL_INSTANT_ELAPSED_INFO,
|
||||
crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
|
||||
crate::int_plus_one::INT_PLUS_ONE_INFO,
|
||||
crate::integer_division_remainder_used::INTEGER_DIVISION_REMAINDER_USED_INFO,
|
||||
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
|
||||
@@ -535,7 +533,6 @@
|
||||
crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
|
||||
crate::mut_key::MUTABLE_KEY_TYPE_INFO,
|
||||
crate::mut_mut::MUT_MUT_INFO,
|
||||
crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO,
|
||||
crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO,
|
||||
crate::mutex_atomic::MUTEX_ATOMIC_INFO,
|
||||
crate::mutex_atomic::MUTEX_INTEGER_INFO,
|
||||
@@ -576,6 +573,7 @@
|
||||
crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO,
|
||||
crate::octal_escapes::OCTAL_ESCAPES_INFO,
|
||||
crate::only_used_in_recursion::ONLY_USED_IN_RECURSION_INFO,
|
||||
crate::only_used_in_recursion::SELF_ONLY_USED_IN_RECURSION_INFO,
|
||||
crate::operators::ABSURD_EXTREME_COMPARISONS_INFO,
|
||||
crate::operators::ARITHMETIC_SIDE_EFFECTS_INFO,
|
||||
crate::operators::ASSIGN_OP_PATTERN_INFO,
|
||||
@@ -704,6 +702,8 @@
|
||||
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
|
||||
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
|
||||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::time_subtraction::MANUAL_INSTANT_ELAPSED_INFO,
|
||||
crate::time_subtraction::UNCHECKED_TIME_SUBTRACTION_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
|
||||
crate::toplevel_ref_arg::TOPLEVEL_REF_ARG_INFO,
|
||||
@@ -751,6 +751,7 @@
|
||||
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_mut_passed::UNNECESSARY_MUT_PASSED_INFO,
|
||||
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
|
||||
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
|
||||
crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO,
|
||||
|
||||
@@ -75,7 +75,7 @@ fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tc
|
||||
&& !base.is_suggestable_infer_ty()
|
||||
{
|
||||
let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())];
|
||||
if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) {
|
||||
if expr.span.check_source_text(cx, |s| s.starts_with('<')) {
|
||||
// Remove `<`, '>` has already been removed by the existing removal expression.
|
||||
removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new()));
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ macro_rules! declare_with_version {
|
||||
("clippy::assign_ops", "compound operators are harmless and linting on them is not in scope for clippy"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::extend_from_slice", "`Vec::extend_from_slice` is no longer faster than `Vec::extend` due to specialization"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::misaligned_transmute", "split into `clippy::cast_ptr_alignment` and `clippy::transmute_ptr_to_ptr`"),
|
||||
#[clippy::version = "1.86.0"]
|
||||
#[clippy::version = "1.87.0"]
|
||||
("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
|
||||
#[clippy::version = "1.54.0"]
|
||||
("clippy::pub_enum_variant_names", "`clippy::enum_variant_names` now covers this case via the `avoid-breaking-exported-api` config"),
|
||||
@@ -34,7 +34,7 @@ macro_rules! declare_with_version {
|
||||
("clippy::replace_consts", "`min_value` and `max_value` are now deprecated"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
|
||||
#[clippy::version = "1.90.0"]
|
||||
#[clippy::version = "1.91.0"]
|
||||
("clippy::string_to_string", "`clippy:implicit_clone` covers those cases"),
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
("clippy::unsafe_vector_initialization", "the suggested alternative could be substantially slower"),
|
||||
@@ -184,6 +184,8 @@ macro_rules! declare_with_version {
|
||||
("clippy::transmute_int_to_float", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.88.0"]
|
||||
("clippy::transmute_num_to_bytes", "unnecessary_transmutes"),
|
||||
#[clippy::version = "1.90.0"]
|
||||
("clippy::unchecked_duration_subtraction", "clippy::unchecked_time_subtraction"),
|
||||
#[clippy::version = ""]
|
||||
("clippy::undropped_manually_drops", "undropped_manually_drops"),
|
||||
#[clippy::version = ""]
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs};
|
||||
use clippy_utils::{
|
||||
DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local,
|
||||
DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_from_proc_macro, is_lint_allowed,
|
||||
path_to_local,
|
||||
};
|
||||
use rustc_ast::util::parser::ExprPrecedence;
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
@@ -260,6 +261,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
};
|
||||
self.skip_expr = skip_expr;
|
||||
|
||||
if is_from_proc_macro(cx, expr) {
|
||||
if let Some((state, data)) = self.state.take() {
|
||||
report(cx, expr, state, data, cx.typeck_results());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match (self.state.take(), kind) {
|
||||
(None, kind) => {
|
||||
let expr_ty = typeck.expr_ty(expr);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_hir as hir;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use rustc_hir::{self as hir, HirId};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{Span, sym};
|
||||
@@ -12,6 +12,7 @@ pub(super) fn check<'tcx>(
|
||||
span: Span,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
adt_hir_id: HirId,
|
||||
ord_is_automatically_derived: bool,
|
||||
) {
|
||||
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
@@ -38,7 +39,7 @@ pub(super) fn check<'tcx>(
|
||||
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
|
||||
};
|
||||
|
||||
span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| {
|
||||
span_lint_hir_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, adt_hir_id, span, mess, |diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here");
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
use clippy_utils::has_non_exhaustive_attr;
|
||||
use clippy_utils::ty::implements_trait_with_env;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{self as hir, HirId};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast};
|
||||
use rustc_span::{Span, sym};
|
||||
@@ -11,7 +11,13 @@
|
||||
use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ;
|
||||
|
||||
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
adt_hir_id: HirId,
|
||||
) {
|
||||
if let ty::Adt(adt, args) = ty.kind()
|
||||
&& cx.tcx.visibility(adt.did()).is_public()
|
||||
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
|
||||
@@ -20,7 +26,6 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::T
|
||||
&& !has_non_exhaustive_attr(cx.tcx, *adt)
|
||||
&& !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id)
|
||||
&& let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||
&& let Some(local_def_id) = adt.did().as_local()
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
&& adt
|
||||
.all_fields()
|
||||
@@ -30,7 +35,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::T
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
cx.tcx.local_def_id_to_hir_id(local_def_id),
|
||||
adt_hir_id,
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"you are deriving `PartialEq` and can implement `Eq`",
|
||||
|diag| {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_hir as hir;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use rustc_hir::{HirId, TraitRef};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{Span, sym};
|
||||
@@ -10,8 +10,9 @@
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
trait_ref: &TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
adt_hir_id: HirId,
|
||||
hash_is_automatically_derived: bool,
|
||||
) {
|
||||
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
|
||||
@@ -31,9 +32,10 @@ pub(super) fn check<'tcx>(
|
||||
// Only care about `impl PartialEq<Foo> for Foo`
|
||||
// For `impl PartialEq<B> for A, input_types is [A, B]
|
||||
if trait_ref.instantiate_identity().args.type_at(1) == ty {
|
||||
span_lint_and_then(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
adt_hir_id,
|
||||
span,
|
||||
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|
||||
|diag| {
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use rustc_hir::{self as hir, Item};
|
||||
use rustc_hir::{self as hir, HirId, Item};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind, Ty};
|
||||
|
||||
use super::EXPL_IMPL_CLONE_ON_COPY;
|
||||
|
||||
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
item: &Item<'_>,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
adt_hir_id: HirId,
|
||||
) {
|
||||
let clone_id = match cx.tcx.lang_items().clone_trait() {
|
||||
Some(id) if trait_ref.trait_def_id() == Some(id) => id,
|
||||
_ => return,
|
||||
@@ -54,12 +60,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_note(
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
EXPL_IMPL_CLONE_ON_COPY,
|
||||
adt_hir_id,
|
||||
item.span,
|
||||
"you are implementing `Clone` explicitly on a `Copy` type",
|
||||
Some(item.span),
|
||||
"consider deriving `Clone` or removing `Copy`",
|
||||
|diag| {
|
||||
diag.span_help(item.span, "consider deriving `Clone` or removing `Copy`");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use clippy_utils::path_res;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
@@ -194,21 +196,25 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(of_trait),
|
||||
self_ty,
|
||||
..
|
||||
}) = item.kind
|
||||
&& let Res::Def(_, def_id) = path_res(cx, self_ty)
|
||||
&& let Some(local_def_id) = def_id.as_local()
|
||||
{
|
||||
let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id);
|
||||
let trait_ref = &of_trait.trait_ref;
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
|
||||
|
||||
derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, is_automatically_derived);
|
||||
derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, is_automatically_derived);
|
||||
derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived);
|
||||
derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, adt_hir_id, is_automatically_derived);
|
||||
|
||||
if is_automatically_derived {
|
||||
unsafe_derive_deserialize::check(cx, item, trait_ref, ty);
|
||||
derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty);
|
||||
unsafe_derive_deserialize::check(cx, item, trait_ref, ty, adt_hir_id);
|
||||
derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty, adt_hir_id);
|
||||
} else {
|
||||
expl_impl_clone_on_copy::check(cx, item, trait_ref, ty);
|
||||
expl_impl_clone_on_copy::check(cx, item, trait_ref, ty, adt_hir_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use clippy_utils::{is_lint_allowed, paths};
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
|
||||
use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Item, UnsafeSource};
|
||||
use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Item, UnsafeSource};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
@@ -13,7 +13,13 @@
|
||||
use super::UNSAFE_DERIVE_DESERIALIZE;
|
||||
|
||||
/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
item: &Item<'_>,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
adt_hir_id: HirId,
|
||||
) {
|
||||
fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
|
||||
let mut visitor = UnsafeVisitor { cx };
|
||||
walk_item(&mut visitor, item).is_break()
|
||||
@@ -22,8 +28,6 @@ fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id()
|
||||
&& paths::SERDE_DESERIALIZE.matches(cx, trait_def_id)
|
||||
&& let ty::Adt(def, _) = ty.kind()
|
||||
&& let Some(local_def_id) = def.did().as_local()
|
||||
&& let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id)
|
||||
&& !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id)
|
||||
&& cx
|
||||
.tcx
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
/// /// [example of a good link](https://github.com/rust-lang/rust-clippy/)
|
||||
/// pub fn do_something() {}
|
||||
/// ```
|
||||
#[clippy::version = "1.84.0"]
|
||||
#[clippy::version = "1.90.0"]
|
||||
pub DOC_BROKEN_LINK,
|
||||
pedantic,
|
||||
"broken document link"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::ast::{Expr, ExprKind};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{HasSession, snippet_with_applicability, snippet_with_context};
|
||||
use rustc_ast::ast::{Expr, ExprKind, MethodCall};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
@@ -24,7 +26,7 @@
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn simple_no_parens() -> i32 {
|
||||
/// 0
|
||||
/// (0)
|
||||
/// }
|
||||
///
|
||||
/// # fn foo(bar: usize) {}
|
||||
@@ -40,29 +42,54 @@
|
||||
|
||||
impl EarlyLintPass for DoubleParens {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
let span = match &expr.kind {
|
||||
ExprKind::Paren(in_paren) if matches!(in_paren.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => expr.span,
|
||||
ExprKind::Call(_, params)
|
||||
if let [param] = &**params
|
||||
&& let ExprKind::Paren(_) = param.kind =>
|
||||
{
|
||||
param.span
|
||||
match &expr.kind {
|
||||
// ((..))
|
||||
// ^^^^^^ expr
|
||||
// ^^^^ inner
|
||||
ExprKind::Paren(inner) if matches!(inner.kind, ExprKind::Paren(_) | ExprKind::Tup(_)) => {
|
||||
// suggest removing the outer parens
|
||||
if expr.span.eq_ctxt(inner.span) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
// We don't need to use `snippet_with_context` here, because:
|
||||
// - if `inner`'s `ctxt` is from macro, we don't lint in the first place (see the check above)
|
||||
// - otherwise, calling `snippet_with_applicability` on a not-from-macro span is fine
|
||||
let sugg = snippet_with_applicability(cx.sess(), inner.span, "_", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOUBLE_PARENS,
|
||||
expr.span,
|
||||
"unnecessary parentheses",
|
||||
"remove them",
|
||||
sugg.to_string(),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(call)
|
||||
if let [arg] = &*call.args
|
||||
&& let ExprKind::Paren(_) = arg.kind =>
|
||||
|
||||
// func((n))
|
||||
// ^^^^^^^^^ expr
|
||||
// ^^^ arg
|
||||
// ^ inner
|
||||
ExprKind::Call(_, args) | ExprKind::MethodCall(box MethodCall { args, .. })
|
||||
if let [arg] = &**args
|
||||
&& let ExprKind::Paren(inner) = &arg.kind =>
|
||||
{
|
||||
arg.span
|
||||
// suggest removing the inner parens
|
||||
if expr.span.eq_ctxt(arg.span) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let sugg = snippet_with_context(cx.sess(), inner.span, arg.span.ctxt(), "_", &mut applicability).0;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DOUBLE_PARENS,
|
||||
arg.span,
|
||||
"unnecessary parentheses",
|
||||
"remove them",
|
||||
sugg.to_string(),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
if !expr.span.from_expansion() {
|
||||
span_lint(
|
||||
cx,
|
||||
DOUBLE_PARENS,
|
||||
span,
|
||||
"consider removing unnecessary double parentheses",
|
||||
);
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,12 +43,8 @@ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
|
||||
if let Some(anon_const) = &var.disr_expr {
|
||||
let def_id = cx.tcx.hir_body_owner_def_id(anon_const.body);
|
||||
let mut ty = cx.tcx.type_of(def_id.to_def_id()).instantiate_identity();
|
||||
let constant = cx
|
||||
.tcx
|
||||
.const_eval_poly(def_id.to_def_id())
|
||||
.ok()
|
||||
.map(|val| rustc_middle::mir::Const::from_value(val, ty));
|
||||
if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) {
|
||||
let constant = cx.tcx.const_eval_poly(def_id.to_def_id()).ok();
|
||||
if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c, ty)) {
|
||||
if let ty::Adt(adt, _) = ty.kind()
|
||||
&& adt.is_enum()
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use std::f32::consts as f32_consts;
|
||||
use std::f64::consts as f64_consts;
|
||||
@@ -110,8 +111,8 @@
|
||||
|
||||
// Returns the specialized log method for a given base if base is constant
|
||||
// and is one of 2, 10 and e
|
||||
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
|
||||
if let Some(value) = ConstEvalCtxt::new(cx).eval(base) {
|
||||
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> {
|
||||
if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) {
|
||||
if F32(2.0) == value || F64(2.0) == value {
|
||||
return Some("log2");
|
||||
} else if F32(10.0) == value || F64(10.0) == value {
|
||||
@@ -157,7 +158,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
|
||||
}
|
||||
|
||||
fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
if let Some(method) = get_specialized_log_method(cx, &args[0]) {
|
||||
if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
@@ -205,7 +206,7 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
|
||||
// ranges [-16777215, 16777216) for type f32 as whole number floats outside
|
||||
// this range are lossy and ambiguous.
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> {
|
||||
fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
|
||||
match value {
|
||||
F32(num) if num.fract() == 0.0 => {
|
||||
if (-16_777_215.0..16_777_216.0).contains(num) {
|
||||
@@ -517,8 +518,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
|
||||
match op {
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
@@ -530,8 +531,8 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
|
||||
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
|
||||
match op {
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
@@ -540,8 +541,8 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
|
||||
}
|
||||
|
||||
/// Returns true iff expr is some zero literal
|
||||
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
match ConstEvalCtxt::new(cx).eval_simple(expr) {
|
||||
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
|
||||
match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
|
||||
Some(Int(i)) => i == 0,
|
||||
Some(F32(f)) => f == 0.0,
|
||||
Some(F64(f)) => f == 0.0,
|
||||
|
||||
@@ -60,10 +60,14 @@ fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
|
||||
),
|
||||
// Don't lint on `… != 0`, as these are likely to be bit tests.
|
||||
// For example, `if foo & 0x0F00 != 0 { … } else { … }` is already in the "proper" order.
|
||||
ExprKind::Binary(op, _, rhs) if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs) => (
|
||||
"unnecessary `!=` operation",
|
||||
"change to `==` and swap the blocks of the `if`/`else`",
|
||||
),
|
||||
ExprKind::Binary(op, _, rhs)
|
||||
if op.node == BinOpKind::Ne && !is_zero_integer_const(cx, rhs, e.span.ctxt()) =>
|
||||
{
|
||||
(
|
||||
"unnecessary `!=` operation",
|
||||
"change to `==` and swap the blocks of the `if`/`else`",
|
||||
)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
&& !is_in_const_context(cx)
|
||||
&& self.msrv.meets(cx, msrvs::BOOL_THEN)
|
||||
&& !contains_return(then_block.stmts)
|
||||
&& then_block.expr.is_none_or(|expr| !contains_return(expr))
|
||||
{
|
||||
let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) {
|
||||
sym::then_some
|
||||
|
||||
@@ -1,218 +1,23 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_note, span_lint_and_then};
|
||||
use clippy_utils::higher::has_let_expr;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{IntoSpan, SpanRangeExt, first_line_of_span, indent_of, reindent_multiline, snippet};
|
||||
use clippy_utils::ty::{InteriorMut, needs_ordered_drop};
|
||||
use clippy_utils::ty::needs_ordered_drop;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{
|
||||
ContainsName, HirEqInterExpr, SpanlessEq, capture_local_usage, eq_expr_value, find_binding_init,
|
||||
get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, path_to_local,
|
||||
search_same,
|
||||
ContainsName, HirEqInterExpr, SpanlessEq, capture_local_usage, get_enclosing_block, hash_expr, hash_stmt,
|
||||
path_to_local,
|
||||
};
|
||||
use core::iter;
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Node, Stmt, StmtKind, intravisit};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::hygiene::walk_chain;
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for consecutive `if`s with the same condition.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably a copy & paste error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// if a == b {
|
||||
/// …
|
||||
/// } else if a == b {
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that this lint ignores all conditions with a function call as it could
|
||||
/// have side effects:
|
||||
///
|
||||
/// ```ignore
|
||||
/// if foo() {
|
||||
/// …
|
||||
/// } else if foo() { // not linted
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub IFS_SAME_COND,
|
||||
correctness,
|
||||
"consecutive `if`s with the same condition"
|
||||
}
|
||||
use super::BRANCHES_SHARING_CODE;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for consecutive `if`s with the same function call.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably a copy & paste error.
|
||||
/// Despite the fact that function can have side effects and `if` works as
|
||||
/// intended, such an approach is implicit and can be considered a "code smell".
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// if foo() == bar {
|
||||
/// …
|
||||
/// } else if foo() == bar {
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This probably should be:
|
||||
/// ```ignore
|
||||
/// if foo() == bar {
|
||||
/// …
|
||||
/// } else if foo() == baz {
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// or if the original code was not a typo and called function mutates a state,
|
||||
/// consider move the mutation out of the `if` condition to avoid similarity to
|
||||
/// a copy & paste error:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let first = foo();
|
||||
/// if first == bar {
|
||||
/// …
|
||||
/// } else {
|
||||
/// let second = foo();
|
||||
/// if second == bar {
|
||||
/// …
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.41.0"]
|
||||
pub SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
pedantic,
|
||||
"consecutive `if`s with the same function call"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `if/else` with the same body as the *then* part
|
||||
/// and the *else* part.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably a copy & paste error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// let foo = if … {
|
||||
/// 42
|
||||
/// } else {
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub IF_SAME_THEN_ELSE,
|
||||
style,
|
||||
"`if` with the same `then` and `else` blocks"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks if the `if` and `else` block contain shared code that can be
|
||||
/// moved out of the blocks.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Duplicate code is less maintainable.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// let foo = if … {
|
||||
/// println!("Hello World");
|
||||
/// 13
|
||||
/// } else {
|
||||
/// println!("Hello World");
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```ignore
|
||||
/// println!("Hello World");
|
||||
/// let foo = if … {
|
||||
/// 13
|
||||
/// } else {
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
#[clippy::version = "1.53.0"]
|
||||
pub BRANCHES_SHARING_CODE,
|
||||
nursery,
|
||||
"`if` statement with shared code in all blocks"
|
||||
}
|
||||
|
||||
pub struct CopyAndPaste<'tcx> {
|
||||
interior_mut: InteriorMut<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> CopyAndPaste<'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
interior_mut: InteriorMut::new(tcx, &conf.ignore_interior_mutability),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(CopyAndPaste<'_> => [
|
||||
IFS_SAME_COND,
|
||||
SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
IF_SAME_THEN_ELSE,
|
||||
BRANCHES_SHARING_CODE
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) {
|
||||
let (conds, blocks) = if_sequence(expr);
|
||||
lint_same_cond(cx, &conds, &mut self.interior_mut);
|
||||
lint_same_fns_in_if_cond(cx, &conds);
|
||||
let all_same =
|
||||
!is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks);
|
||||
if !all_same && conds.len() != blocks.len() {
|
||||
lint_branches_sharing_code(cx, &conds, &blocks, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool {
|
||||
let mut eq = SpanlessEq::new(cx);
|
||||
blocks
|
||||
.array_windows::<2>()
|
||||
.enumerate()
|
||||
.fold(true, |all_eq, (i, &[lhs, rhs])| {
|
||||
if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
IF_SAME_THEN_ELSE,
|
||||
lhs.span,
|
||||
"this `if` has identical blocks",
|
||||
Some(rhs.span),
|
||||
"same as this",
|
||||
);
|
||||
all_eq
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn lint_branches_sharing_code<'tcx>(
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
conds: &[&'tcx Expr<'_>],
|
||||
blocks: &[&'tcx Block<'_>],
|
||||
@@ -356,8 +161,8 @@ fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Checks if the given statement should be considered equal to the statement in the same position
|
||||
/// for each block.
|
||||
/// Checks if the given statement should be considered equal to the statement in the same
|
||||
/// position for each block.
|
||||
fn eq_stmts(
|
||||
stmt: &Stmt<'_>,
|
||||
blocks: &[&Block<'_>],
|
||||
@@ -516,9 +321,9 @@ fn scan_block_for_eq<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts the index for which the statements begin to differ to the closest macro callsite. This
|
||||
/// avoids giving suggestions that requires splitting a macro call in half, when only a part of the
|
||||
/// macro expansion is equal.
|
||||
/// Adjusts the index for which the statements begin to differ to the closest macro callsite.
|
||||
/// This avoids giving suggestions that requires splitting a macro call in half, when only a
|
||||
/// part of the macro expansion is equal.
|
||||
///
|
||||
/// For example, for the following macro:
|
||||
/// ```rust,ignore
|
||||
@@ -587,70 +392,6 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
|
||||
})
|
||||
}
|
||||
|
||||
fn method_caller_is_mutable<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
caller_expr: &Expr<'_>,
|
||||
interior_mut: &mut InteriorMut<'tcx>,
|
||||
) -> bool {
|
||||
let caller_ty = cx.typeck_results().expr_ty(caller_expr);
|
||||
|
||||
interior_mut.is_interior_mut_ty(cx, caller_ty)
|
||||
|| caller_ty.is_mutable_ptr()
|
||||
// `find_binding_init` will return the binding iff its not mutable
|
||||
|| path_to_local(caller_expr)
|
||||
.and_then(|hid| find_binding_init(cx, hid))
|
||||
.is_none()
|
||||
}
|
||||
|
||||
/// Implementation of `IFS_SAME_COND`.
|
||||
fn lint_same_cond<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) {
|
||||
for group in search_same(
|
||||
conds,
|
||||
|e| hash_expr(cx, e),
|
||||
|lhs, rhs| {
|
||||
// Ignore eq_expr side effects iff one of the expression kind is a method call
|
||||
// and the caller is not a mutable, including inner mutable type.
|
||||
if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind {
|
||||
if method_caller_is_mutable(cx, caller, interior_mut) {
|
||||
false
|
||||
} else {
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
}
|
||||
} else {
|
||||
eq_expr_value(cx, lhs, rhs)
|
||||
}
|
||||
},
|
||||
) {
|
||||
let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
|
||||
span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition");
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`.
|
||||
fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
||||
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
|
||||
// Do not lint if any expr originates from a macro
|
||||
if lhs.span.from_expansion() || rhs.span.from_expansion() {
|
||||
return false;
|
||||
}
|
||||
// Do not spawn warning if `IFS_SAME_COND` already produced it.
|
||||
if eq_expr_value(cx, lhs, rhs) {
|
||||
return false;
|
||||
}
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
};
|
||||
|
||||
for group in search_same(conds, |e| hash_expr(cx, e), eq) {
|
||||
let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
|
||||
span_lint(
|
||||
cx,
|
||||
SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
spans,
|
||||
"these `if` branches have the same function call",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expr_parent_assignment(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let parent = cx.tcx.parent_hir_node(expr.hir_id);
|
||||
if let Node::LetStmt(LetStmt { init: Some(e), .. })
|
||||
@@ -0,0 +1,29 @@
|
||||
use clippy_utils::SpanlessEq;
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::higher::has_let_expr;
|
||||
use rustc_hir::{Block, Expr};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::IF_SAME_THEN_ELSE;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool {
|
||||
let mut eq = SpanlessEq::new(cx);
|
||||
blocks
|
||||
.array_windows::<2>()
|
||||
.enumerate()
|
||||
.fold(true, |all_eq, (i, &[lhs, rhs])| {
|
||||
if eq.eq_block(lhs, rhs) && !has_let_expr(conds[i]) && conds.get(i + 1).is_none_or(|e| !has_let_expr(e)) {
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
IF_SAME_THEN_ELSE,
|
||||
lhs.span,
|
||||
"this `if` has identical blocks",
|
||||
Some(rhs.span),
|
||||
"same as this",
|
||||
);
|
||||
all_eq
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::InteriorMut;
|
||||
use clippy_utils::{SpanlessEq, eq_expr_value, find_binding_init, hash_expr, path_to_local, search_same};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::IFS_SAME_COND;
|
||||
|
||||
fn method_caller_is_mutable<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
caller_expr: &Expr<'_>,
|
||||
interior_mut: &mut InteriorMut<'tcx>,
|
||||
) -> bool {
|
||||
let caller_ty = cx.typeck_results().expr_ty(caller_expr);
|
||||
|
||||
interior_mut.is_interior_mut_ty(cx, caller_ty)
|
||||
|| caller_ty.is_mutable_ptr()
|
||||
// `find_binding_init` will return the binding iff its not mutable
|
||||
|| path_to_local(caller_expr)
|
||||
.and_then(|hid| find_binding_init(cx, hid))
|
||||
.is_none()
|
||||
}
|
||||
|
||||
/// Implementation of `IFS_SAME_COND`.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, conds: &[&Expr<'_>], interior_mut: &mut InteriorMut<'tcx>) {
|
||||
for group in search_same(
|
||||
conds,
|
||||
|e| hash_expr(cx, e),
|
||||
|lhs, rhs| {
|
||||
// Ignore eq_expr side effects iff one of the expression kind is a method call
|
||||
// and the caller is not a mutable, including inner mutable type.
|
||||
if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind {
|
||||
if method_caller_is_mutable(cx, caller, interior_mut) {
|
||||
false
|
||||
} else {
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
}
|
||||
} else {
|
||||
eq_expr_value(cx, lhs, rhs)
|
||||
}
|
||||
},
|
||||
) {
|
||||
let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
|
||||
span_lint(cx, IFS_SAME_COND, spans, "these `if` branches have the same condition");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::ty::InteriorMut;
|
||||
use clippy_utils::{if_sequence, is_else_clause, is_lint_allowed};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
||||
mod branches_sharing_code;
|
||||
mod if_same_then_else;
|
||||
mod ifs_same_cond;
|
||||
mod same_functions_in_if_cond;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for consecutive `if`s with the same condition.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably a copy & paste error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// if a == b {
|
||||
/// …
|
||||
/// } else if a == b {
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that this lint ignores all conditions with a function call as it could
|
||||
/// have side effects:
|
||||
///
|
||||
/// ```ignore
|
||||
/// if foo() {
|
||||
/// …
|
||||
/// } else if foo() { // not linted
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub IFS_SAME_COND,
|
||||
correctness,
|
||||
"consecutive `if`s with the same condition"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for consecutive `if`s with the same function call.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably a copy & paste error.
|
||||
/// Despite the fact that function can have side effects and `if` works as
|
||||
/// intended, such an approach is implicit and can be considered a "code smell".
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// if foo() == bar {
|
||||
/// …
|
||||
/// } else if foo() == bar {
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This probably should be:
|
||||
/// ```ignore
|
||||
/// if foo() == bar {
|
||||
/// …
|
||||
/// } else if foo() == baz {
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// or if the original code was not a typo and called function mutates a state,
|
||||
/// consider move the mutation out of the `if` condition to avoid similarity to
|
||||
/// a copy & paste error:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let first = foo();
|
||||
/// if first == bar {
|
||||
/// …
|
||||
/// } else {
|
||||
/// let second = foo();
|
||||
/// if second == bar {
|
||||
/// …
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.41.0"]
|
||||
pub SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
pedantic,
|
||||
"consecutive `if`s with the same function call"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `if/else` with the same body as the *then* part
|
||||
/// and the *else* part.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is probably a copy & paste error.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// let foo = if … {
|
||||
/// 42
|
||||
/// } else {
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub IF_SAME_THEN_ELSE,
|
||||
style,
|
||||
"`if` with the same `then` and `else` blocks"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks if the `if` and `else` block contain shared code that can be
|
||||
/// moved out of the blocks.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Duplicate code is less maintainable.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```ignore
|
||||
/// let foo = if … {
|
||||
/// println!("Hello World");
|
||||
/// 13
|
||||
/// } else {
|
||||
/// println!("Hello World");
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```ignore
|
||||
/// println!("Hello World");
|
||||
/// let foo = if … {
|
||||
/// 13
|
||||
/// } else {
|
||||
/// 42
|
||||
/// };
|
||||
/// ```
|
||||
#[clippy::version = "1.53.0"]
|
||||
pub BRANCHES_SHARING_CODE,
|
||||
nursery,
|
||||
"`if` statement with shared code in all blocks"
|
||||
}
|
||||
|
||||
pub struct CopyAndPaste<'tcx> {
|
||||
interior_mut: InteriorMut<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> CopyAndPaste<'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
interior_mut: InteriorMut::new(tcx, &conf.ignore_interior_mutability),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(CopyAndPaste<'_> => [
|
||||
IFS_SAME_COND,
|
||||
SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
IF_SAME_THEN_ELSE,
|
||||
BRANCHES_SHARING_CODE
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste<'tcx> {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) {
|
||||
let (conds, blocks) = if_sequence(expr);
|
||||
ifs_same_cond::check(cx, &conds, &mut self.interior_mut);
|
||||
same_functions_in_if_cond::check(cx, &conds);
|
||||
let all_same =
|
||||
!is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && if_same_then_else::check(cx, &conds, &blocks);
|
||||
if !all_same && conds.len() != blocks.len() {
|
||||
branches_sharing_code::check(cx, &conds, &blocks, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{SpanlessEq, eq_expr_value, hash_expr, search_same};
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::SAME_FUNCTIONS_IN_IF_CONDITION;
|
||||
|
||||
/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`.
|
||||
pub(super) fn check(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
||||
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
|
||||
// Do not lint if any expr originates from a macro
|
||||
if lhs.span.from_expansion() || rhs.span.from_expansion() {
|
||||
return false;
|
||||
}
|
||||
// Do not spawn warning if `IFS_SAME_COND` already produced it.
|
||||
if eq_expr_value(cx, lhs, rhs) {
|
||||
return false;
|
||||
}
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
};
|
||||
|
||||
for group in search_same(conds, |e| hash_expr(cx, e), eq) {
|
||||
let spans: Vec<_> = group.into_iter().map(|expr| expr.span).collect();
|
||||
span_lint(
|
||||
cx,
|
||||
SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
spans,
|
||||
"these `if` branches have the same function call",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro};
|
||||
use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_from_proc_macro};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
@@ -240,7 +240,7 @@ fn check_fn(
|
||||
return;
|
||||
}
|
||||
|
||||
let expr = if is_async_fn(kind) {
|
||||
let expr = if kind.asyncness().is_async() {
|
||||
match get_async_fn_body(cx.tcx, body) {
|
||||
Some(e) => e,
|
||||
None => return,
|
||||
|
||||
@@ -117,10 +117,11 @@ fn get_int_max(ty: Ty<'_>) -> Option<u128> {
|
||||
fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> {
|
||||
if let ExprKind::Binary(op, l, r) = expr.kind {
|
||||
let ecx = ConstEvalCtxt::new(cx);
|
||||
if let Some(Constant::Int(c)) = ecx.eval(r) {
|
||||
let ctxt = expr.span.ctxt();
|
||||
if let Some(Constant::Int(c)) = ecx.eval_local(r, ctxt) {
|
||||
return Some((c, op.node, l));
|
||||
}
|
||||
if let Some(Constant::Int(c)) = ecx.eval(l) {
|
||||
if let Some(Constant::Int(c)) = ecx.eval_local(l, ctxt) {
|
||||
return Some((c, invert_op(op.node)?, r));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{is_path_diagnostic_item, ty};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints subtraction between `Instant::now()` and another `Instant`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
|
||||
/// as `Instant` subtraction saturates.
|
||||
///
|
||||
/// `prev_instant.elapsed()` also more clearly signals intention.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// use std::time::Instant;
|
||||
/// let prev_instant = Instant::now();
|
||||
/// let duration = Instant::now() - prev_instant;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// use std::time::Instant;
|
||||
/// let prev_instant = Instant::now();
|
||||
/// let duration = prev_instant.elapsed();
|
||||
/// ```
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub MANUAL_INSTANT_ELAPSED,
|
||||
pedantic,
|
||||
"subtraction between `Instant::now()` and previous `Instant`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints subtraction between an `Instant` and a `Duration`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Unchecked subtraction could cause underflow on certain platforms, leading to
|
||||
/// unintentional panics.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::time::{Instant, Duration};
|
||||
/// let time_passed = Instant::now() - Duration::from_secs(5);
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::time::{Instant, Duration};
|
||||
/// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
|
||||
/// ```
|
||||
#[clippy::version = "1.67.0"]
|
||||
pub UNCHECKED_DURATION_SUBTRACTION,
|
||||
pedantic,
|
||||
"finds unchecked subtraction of a 'Duration' from an 'Instant'"
|
||||
}
|
||||
|
||||
pub struct InstantSubtraction {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl InstantSubtraction {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(InstantSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_DURATION_SUBTRACTION]);
|
||||
|
||||
impl LateLintPass<'_> for InstantSubtraction {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Sub, ..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = expr.kind
|
||||
&& let typeck = cx.typeck_results()
|
||||
&& ty::is_type_diagnostic_item(cx, typeck.expr_ty(lhs), sym::Instant)
|
||||
{
|
||||
let rhs_ty = typeck.expr_ty(rhs);
|
||||
|
||||
if is_instant_now_call(cx, lhs)
|
||||
&& ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant)
|
||||
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
|
||||
{
|
||||
print_manual_instant_elapsed_sugg(cx, expr, sugg);
|
||||
} else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration)
|
||||
&& !expr.span.from_expansion()
|
||||
&& self.msrv.meets(cx, msrvs::TRY_FROM)
|
||||
{
|
||||
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
|
||||
if let ExprKind::Call(fn_expr, []) = expr_block.kind
|
||||
&& is_path_diagnostic_item(cx, fn_expr, sym::instant_now)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_INSTANT_ELAPSED,
|
||||
expr.span,
|
||||
"manual implementation of `Instant::elapsed`",
|
||||
"try",
|
||||
format!("{}.elapsed()", sugg.maybe_paren()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
fn print_unchecked_duration_subtraction_sugg(
|
||||
cx: &LateContext<'_>,
|
||||
left_expr: &Expr<'_>,
|
||||
right_expr: &Expr<'_>,
|
||||
expr: &Expr<'_>,
|
||||
) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
||||
let ctxt = expr.span.ctxt();
|
||||
let left_expr = snippet_with_context(cx, left_expr.span, ctxt, "<instant>", &mut applicability).0;
|
||||
let right_expr = snippet_with_context(cx, right_expr.span, ctxt, "<duration>", &mut applicability).0;
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNCHECKED_DURATION_SUBTRACTION,
|
||||
expr.span,
|
||||
"unchecked subtraction of a 'Duration' from an 'Instant'",
|
||||
"try",
|
||||
format!("{left_expr}.checked_sub({right_expr}).unwrap()"),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ fn upcast_comparison_bounds_err<'tcx>(
|
||||
invert: bool,
|
||||
) {
|
||||
if let Some((lb, ub)) = lhs_bounds
|
||||
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs)
|
||||
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs, span.ctxt())
|
||||
{
|
||||
if rel == Rel::Eq || rel == Rel::Ne {
|
||||
if norm_rhs_val < lb || norm_rhs_val > ub {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
|
||||
use clippy_utils::is_bool;
|
||||
use clippy_utils::macros::span_is_local;
|
||||
use clippy_utils::source::is_present_in_source;
|
||||
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
|
||||
use clippy_utils::{is_bool, is_from_proc_macro};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData};
|
||||
use rustc_hir::{Body, EnumDef, FieldDef, Item, ItemKind, QPath, TyKind, UseKind, Variant, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::Symbol;
|
||||
@@ -158,7 +156,8 @@
|
||||
}
|
||||
|
||||
pub struct ItemNameRepetitions {
|
||||
modules: Vec<(Symbol, String, OwnerId)>,
|
||||
/// The module path the lint pass is in.
|
||||
modules: Vec<ModInfo>,
|
||||
enum_threshold: u64,
|
||||
struct_threshold: u64,
|
||||
avoid_breaking_exported_api: bool,
|
||||
@@ -167,6 +166,17 @@ pub struct ItemNameRepetitions {
|
||||
allowed_prefixes: FxHashSet<String>,
|
||||
}
|
||||
|
||||
struct ModInfo {
|
||||
name: Symbol,
|
||||
name_camel: String,
|
||||
/// Does this module have the `pub` visibility modifier.
|
||||
is_public: bool,
|
||||
/// How many bodies are between this module and the current lint pass position.
|
||||
///
|
||||
/// Only the most recently seen module is updated when entering/exiting a body.
|
||||
in_body_count: u32,
|
||||
}
|
||||
|
||||
impl ItemNameRepetitions {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
@@ -458,71 +468,109 @@ fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_>
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for ItemNameRepetitions {
|
||||
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
let Some(_ident) = item.kind.ident() else { return };
|
||||
fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
|
||||
if matches!(item.kind, ItemKind::Mod(..)) {
|
||||
let prev = self.modules.pop();
|
||||
debug_assert!(prev.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
let last = self.modules.pop();
|
||||
assert!(last.is_some());
|
||||
fn check_body(&mut self, _: &LateContext<'_>, _: &Body<'_>) {
|
||||
if let [.., last] = &mut *self.modules {
|
||||
last.in_body_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_body_post(&mut self, _: &LateContext<'_>, _: &Body<'_>) {
|
||||
if let [.., last] = &mut *self.modules {
|
||||
last.in_body_count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
let Some(ident) = item.kind.ident() else { return };
|
||||
let ident = match item.kind {
|
||||
ItemKind::Mod(ident, _) => {
|
||||
if let [.., prev] = &*self.modules
|
||||
&& prev.name == ident.name
|
||||
&& prev.in_body_count == 0
|
||||
&& (!self.allow_private_module_inception || prev.is_public)
|
||||
&& !item.span.from_expansion()
|
||||
&& !is_from_proc_macro(cx, item)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_INCEPTION,
|
||||
item.span,
|
||||
"module has the same name as its containing module",
|
||||
);
|
||||
}
|
||||
ident
|
||||
},
|
||||
|
||||
ItemKind::Enum(ident, _, def) => {
|
||||
if !ident.span.in_external_macro(cx.tcx.sess.source_map()) {
|
||||
self.check_variants(cx, item, &def);
|
||||
}
|
||||
ident
|
||||
},
|
||||
ItemKind::Struct(ident, _, data) => {
|
||||
if let VariantData::Struct { fields, .. } = data
|
||||
&& !ident.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
self.check_fields(cx, item, fields);
|
||||
}
|
||||
ident
|
||||
},
|
||||
|
||||
ItemKind::Const(ident, ..)
|
||||
| ItemKind::ExternCrate(_, ident)
|
||||
| ItemKind::Fn { ident, .. }
|
||||
| ItemKind::Macro(ident, ..)
|
||||
| ItemKind::Static(_, ident, ..)
|
||||
| ItemKind::Trait(_, _, _, ident, ..)
|
||||
| ItemKind::TraitAlias(ident, ..)
|
||||
| ItemKind::TyAlias(ident, ..)
|
||||
| ItemKind::Union(ident, ..)
|
||||
| ItemKind::Use(_, UseKind::Single(ident)) => ident,
|
||||
|
||||
ItemKind::ForeignMod { .. } | ItemKind::GlobalAsm { .. } | ItemKind::Impl(_) | ItemKind::Use(..) => return,
|
||||
};
|
||||
|
||||
let item_name = ident.name.as_str();
|
||||
let item_camel = to_camel_case(item_name);
|
||||
if !item.span.from_expansion() && is_present_in_source(cx, item.span)
|
||||
&& let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules
|
||||
// constants don't have surrounding modules
|
||||
&& !mod_camel.is_empty()
|
||||
|
||||
if let [.., prev] = &*self.modules
|
||||
&& prev.is_public
|
||||
&& prev.in_body_count == 0
|
||||
&& !item.span.from_expansion()
|
||||
&& !matches!(item.kind, ItemKind::Macro(..))
|
||||
&& cx.tcx.visibility(item.owner_id).is_public()
|
||||
{
|
||||
if mod_name == &ident.name
|
||||
&& let ItemKind::Mod(..) = item.kind
|
||||
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_INCEPTION,
|
||||
item.span,
|
||||
"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 only accepted if `allow_exact_repetition` is set to `true`.
|
||||
|
||||
let both_are_public =
|
||||
cx.tcx.visibility(item.owner_id).is_public() && cx.tcx.visibility(mod_owner_id.def_id).is_public();
|
||||
|
||||
if both_are_public && !self.allow_exact_repetitions && item_camel == *mod_camel {
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name is the same as its containing module's name",
|
||||
);
|
||||
}
|
||||
|
||||
let is_macro = matches!(item.kind, ItemKind::Macro(_, _, _));
|
||||
if both_are_public && item_camel.len() > mod_camel.len() && !is_macro {
|
||||
let matching = count_match_start(mod_camel, &item_camel);
|
||||
let rmatching = count_match_end(mod_camel, &item_camel);
|
||||
let nchars = mod_camel.chars().count();
|
||||
|
||||
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
|
||||
|
||||
if matching.char_count == nchars {
|
||||
match item_camel.chars().nth(nchars) {
|
||||
Some(c) if is_word_beginning(c) => span_lint(
|
||||
if !self.allow_exact_repetitions && item_camel == prev.name_camel {
|
||||
if !is_from_proc_macro(cx, item) {
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name is the same as its containing module's name",
|
||||
);
|
||||
}
|
||||
} else if item_camel.len() > prev.name_camel.len() {
|
||||
if let Some(s) = item_camel.strip_prefix(&prev.name_camel)
|
||||
&& let Some(c) = s.chars().next()
|
||||
&& (c == '_' || c.is_uppercase() || c.is_numeric())
|
||||
{
|
||||
if !is_from_proc_macro(cx, item) {
|
||||
span_lint(
|
||||
cx,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
ident.span,
|
||||
"item name starts with its containing module's name",
|
||||
),
|
||||
_ => (),
|
||||
);
|
||||
}
|
||||
}
|
||||
if rmatching.char_count == nchars
|
||||
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
|
||||
} else if let Some(s) = item_camel.strip_suffix(&prev.name_camel)
|
||||
&& !self.is_allowed_prefix(s)
|
||||
&& !is_from_proc_macro(cx, item)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
@@ -534,17 +582,13 @@ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
if span_is_local(item.span) {
|
||||
match item.kind {
|
||||
ItemKind::Enum(_, _, def) => {
|
||||
self.check_variants(cx, item, &def);
|
||||
},
|
||||
ItemKind::Struct(_, _, VariantData::Struct { fields, .. }) => {
|
||||
self.check_fields(cx, item, fields);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
if matches!(item.kind, ItemKind::Mod(..)) {
|
||||
self.modules.push(ModInfo {
|
||||
name: ident.name,
|
||||
name_camel: item_camel,
|
||||
is_public: cx.tcx.visibility(item.owner_id).is_public(),
|
||||
in_body_count: 0,
|
||||
});
|
||||
}
|
||||
self.modules.push((ident.name, item_camel, item.owner_id));
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -99,7 +99,6 @@
|
||||
mod collapsible_if;
|
||||
mod collection_is_never_read;
|
||||
mod comparison_chain;
|
||||
mod copies;
|
||||
mod copy_iterator;
|
||||
mod crate_in_macro_def;
|
||||
mod create_dir;
|
||||
@@ -157,6 +156,7 @@
|
||||
mod if_let_mutex;
|
||||
mod if_not_else;
|
||||
mod if_then_some_else_none;
|
||||
mod ifs;
|
||||
mod ignored_unit_patterns;
|
||||
mod impl_hash_with_borrow_str_and_bytes;
|
||||
mod implicit_hasher;
|
||||
@@ -175,7 +175,6 @@
|
||||
mod inherent_to_string;
|
||||
mod init_numbered_fields;
|
||||
mod inline_fn_without_body;
|
||||
mod instant_subtraction;
|
||||
mod int_plus_one;
|
||||
mod integer_division_remainder_used;
|
||||
mod invalid_upcast_comparisons;
|
||||
@@ -252,7 +251,6 @@
|
||||
mod multiple_unsafe_ops_per_block;
|
||||
mod mut_key;
|
||||
mod mut_mut;
|
||||
mod mut_reference;
|
||||
mod mutable_debug_assertion;
|
||||
mod mutex_atomic;
|
||||
mod needless_arbitrary_self_type;
|
||||
@@ -356,6 +354,7 @@
|
||||
mod tabs_in_doc_comments;
|
||||
mod temporary_assignment;
|
||||
mod tests_outside_test_module;
|
||||
mod time_subtraction;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_trait_impl;
|
||||
mod toplevel_ref_arg;
|
||||
@@ -374,6 +373,7 @@
|
||||
mod unnecessary_box_returns;
|
||||
mod unnecessary_literal_bound;
|
||||
mod unnecessary_map_on_constructor;
|
||||
mod unnecessary_mut_passed;
|
||||
mod unnecessary_owned_empty_strings;
|
||||
mod unnecessary_self_imports;
|
||||
mod unnecessary_semicolon;
|
||||
@@ -481,8 +481,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
||||
store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach));
|
||||
store.register_late_pass(|_| Box::new(misc::LintPass));
|
||||
store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction));
|
||||
store.register_late_pass(|_| Box::new(mut_mut::MutMut));
|
||||
store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed));
|
||||
store.register_late_pass(|_| Box::new(mut_mut::MutMut::default()));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed));
|
||||
store.register_late_pass(|_| Box::<significant_drop_tightening::SignificantDropTightening<'_>>::default());
|
||||
store.register_late_pass(|_| Box::new(len_zero::LenZero));
|
||||
store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf)));
|
||||
@@ -548,7 +548,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
||||
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
|
||||
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
|
||||
store.register_late_pass(|_| Box::<regex::Regex>::default());
|
||||
store.register_late_pass(move |tcx| Box::new(copies::CopyAndPaste::new(tcx, conf)));
|
||||
store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf)));
|
||||
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
|
||||
let format_args = format_args_storage.clone();
|
||||
store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone())));
|
||||
@@ -588,13 +588,13 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
||||
store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit));
|
||||
store.register_late_pass(|_| Box::new(inherent_impl::MultipleInherentImpl));
|
||||
store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
|
||||
store.register_late_pass(|_| Box::new(unwrap::Unwrap));
|
||||
store.register_late_pass(move |_| Box::new(unwrap::Unwrap::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf)));
|
||||
store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf)));
|
||||
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));
|
||||
store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit));
|
||||
store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants));
|
||||
store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates));
|
||||
store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString));
|
||||
store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf)));
|
||||
@@ -670,7 +670,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
||||
store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10));
|
||||
store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
|
||||
store.register_early_pass(move || Box::new(module_style::ModStyle));
|
||||
store.register_early_pass(move || Box::new(module_style::ModStyle::default()));
|
||||
store.register_late_pass(|_| Box::<unused_async::UnusedAsync>::default());
|
||||
store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf)));
|
||||
store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf)));
|
||||
@@ -717,7 +717,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
||||
store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate));
|
||||
store.register_late_pass(move |_| Box::new(operators::Operators::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
|
||||
store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf)));
|
||||
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf)));
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use itertools::Itertools;
|
||||
use rustc_hir::attrs::AttributeKind;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt};
|
||||
use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource};
|
||||
use rustc_hir::attrs::AttributeKind;
|
||||
use rustc_hir::find_attr;
|
||||
use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource, find_attr};
|
||||
use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
|
||||
use rustc_middle::lint::LevelAndSource;
|
||||
use rustc_session::impl_lint_pass;
|
||||
@@ -148,8 +147,8 @@ struct BodyVisitor<'a, 'tcx> {
|
||||
}
|
||||
|
||||
fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
|
||||
( cx.effective_visibilities.is_exported(def_id) ||
|
||||
find_attr!(cx.tcx.get_all_attrs(def_id), AttributeKind::MacroExport{..}) )
|
||||
(cx.effective_visibilities.is_exported(def_id)
|
||||
|| find_attr!(cx.tcx.get_all_attrs(def_id), AttributeKind::MacroExport { .. }))
|
||||
&& !cx.tcx.is_doc_hidden(def_id)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::consts::ConstEvalCtxt;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
@@ -146,13 +146,14 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
)
|
||||
&& let [first, second, const_1, const_2] = exprs
|
||||
&& let ecx = ConstEvalCtxt::new(cx)
|
||||
&& let Some(const_1) = ecx.eval(const_1)
|
||||
&& let Some(const_2) = ecx.eval(const_2)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& let Some(const_1) = ecx.eval_local(const_1, ctxt)
|
||||
&& let Some(const_2) = ecx.eval_local(const_2, ctxt)
|
||||
&& path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s))
|
||||
// The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in
|
||||
// case somebody does that for some reason
|
||||
&& (is_infinity(&const_1) && is_neg_infinity(&const_2)
|
||||
|| is_neg_infinity(&const_1) && is_infinity(&const_2))
|
||||
&& (const_1.is_pos_infinity() && const_2.is_neg_infinity()
|
||||
|| const_1.is_neg_infinity() && const_2.is_pos_infinity())
|
||||
&& let Some(local_snippet) = first.span.get_source_text(cx)
|
||||
{
|
||||
let variant = match (kind.node, lhs_kind.node, rhs_kind.node) {
|
||||
@@ -201,21 +202,3 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_infinity(constant: &Constant<'_>) -> bool {
|
||||
match constant {
|
||||
// FIXME(f16_f128): add f16 and f128 when constants are available
|
||||
Constant::F32(float) => *float == f32::INFINITY,
|
||||
Constant::F64(float) => *float == f64::INFINITY,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_neg_infinity(constant: &Constant<'_>) -> bool {
|
||||
match constant {
|
||||
// FIXME(f16_f128): add f16 and f128 when constants are available
|
||||
Constant::F32(float) => *float == f32::NEG_INFINITY,
|
||||
Constant::F64(float) => *float == f64::NEG_INFINITY,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -58,13 +59,13 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
&& add_lhs.span.ctxt() == ctxt
|
||||
&& add_rhs.span.ctxt() == ctxt
|
||||
&& !expr.span.in_external_macro(cx.sess().source_map())
|
||||
&& let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs)
|
||||
&& let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs)
|
||||
&& let Some(const1) = check_for_unsigned_int_constant(cx, ctxt, rem_rhs)
|
||||
&& let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, ctxt, add_lhs, add_rhs)
|
||||
&& let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind
|
||||
&& rem2_op.node == BinOpKind::Rem
|
||||
&& const1 == const2
|
||||
&& let Some(hir_id) = path_to_local(rem2_lhs)
|
||||
&& let Some(const3) = check_for_unsigned_int_constant(cx, rem2_rhs)
|
||||
&& let Some(const3) = check_for_unsigned_int_constant(cx, ctxt, rem2_rhs)
|
||||
// Also ensures the const is nonzero since zero can't be a divisor
|
||||
&& const2 == const3
|
||||
&& rem2_lhs.span.ctxt() == ctxt
|
||||
@@ -103,16 +104,21 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// constant along with the other expression unchanged if so
|
||||
fn check_for_either_unsigned_int_constant<'a>(
|
||||
cx: &'a LateContext<'_>,
|
||||
ctxt: SyntaxContext,
|
||||
left: &'a Expr<'_>,
|
||||
right: &'a Expr<'_>,
|
||||
) -> Option<(u128, &'a Expr<'a>)> {
|
||||
check_for_unsigned_int_constant(cx, left)
|
||||
check_for_unsigned_int_constant(cx, ctxt, left)
|
||||
.map(|int_const| (int_const, right))
|
||||
.or_else(|| check_for_unsigned_int_constant(cx, right).map(|int_const| (int_const, left)))
|
||||
.or_else(|| check_for_unsigned_int_constant(cx, ctxt, right).map(|int_const| (int_const, left)))
|
||||
}
|
||||
|
||||
fn check_for_unsigned_int_constant<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<u128> {
|
||||
let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr)?;
|
||||
fn check_for_unsigned_int_constant<'a>(
|
||||
cx: &'a LateContext<'_>,
|
||||
ctxt: SyntaxContext,
|
||||
expr: &'a Expr<'_>,
|
||||
) -> Option<u128> {
|
||||
let int_const = ConstEvalCtxt::new(cx).eval_full_int(expr, ctxt)?;
|
||||
match int_const {
|
||||
FullInt::S(s) => s.try_into().ok(),
|
||||
FullInt::U(u) => Some(u),
|
||||
|
||||
@@ -66,7 +66,7 @@ fn parse_shift<'tcx>(
|
||||
BinOpKind::Shr => ShiftDirection::Right,
|
||||
_ => return None,
|
||||
};
|
||||
let const_expr = ConstEvalCtxt::new(cx).eval(r)?;
|
||||
let const_expr = ConstEvalCtxt::new(cx).eval_local(r, expr.span.ctxt())?;
|
||||
if let Constant::Int(shift) = const_expr {
|
||||
return Some((dir, shift, l));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::{Symbol, sym};
|
||||
use rustc_span::{Symbol, SyntaxContext, sym};
|
||||
use std::iter;
|
||||
|
||||
declare_clippy_lint! {
|
||||
@@ -92,7 +92,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
return;
|
||||
}
|
||||
|
||||
let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then);
|
||||
let (strippings, bindings) = find_stripping(cx, strip_kind, target_res, pattern, then, expr.span.ctxt());
|
||||
if !strippings.is_empty() && self.msrv.meets(cx, msrvs::STR_STRIP_PREFIX) {
|
||||
let kind_word = match strip_kind {
|
||||
StripKind::Prefix => "prefix",
|
||||
@@ -166,8 +166,8 @@ fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
|
||||
}
|
||||
|
||||
// Returns the length of the `expr` if it's a constant string or char.
|
||||
fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
let value = ConstEvalCtxt::new(cx).eval(expr)?;
|
||||
fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option<u128> {
|
||||
let value = ConstEvalCtxt::new(cx).eval_local(expr, ctxt)?;
|
||||
match value {
|
||||
Constant::Str(value) => Some(value.len() as u128),
|
||||
Constant::Char(value) => Some(value.len_utf8() as u128),
|
||||
@@ -176,13 +176,18 @@ fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
}
|
||||
|
||||
// Tests if `expr` equals the length of the pattern.
|
||||
fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
|
||||
fn eq_pattern_length<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
pattern: &Expr<'_>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
ctxt: SyntaxContext,
|
||||
) -> bool {
|
||||
if let ExprKind::Lit(Spanned {
|
||||
node: LitKind::Int(n, _),
|
||||
..
|
||||
}) = expr.kind
|
||||
{
|
||||
constant_length(cx, pattern).is_some_and(|length| n == length)
|
||||
constant_length(cx, pattern, ctxt).is_some_and(|length| n == length)
|
||||
} else {
|
||||
len_arg(cx, expr).is_some_and(|arg| eq_expr_value(cx, pattern, arg))
|
||||
}
|
||||
@@ -215,6 +220,7 @@ fn find_stripping<'tcx>(
|
||||
target: Res,
|
||||
pattern: &'tcx Expr<'_>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
ctxt: SyntaxContext,
|
||||
) -> (Vec<&'tcx Expr<'tcx>>, FxHashMap<Symbol, usize>) {
|
||||
struct StrippingFinder<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
@@ -223,6 +229,7 @@ struct StrippingFinder<'a, 'tcx> {
|
||||
pattern: &'tcx Expr<'tcx>,
|
||||
results: Vec<&'tcx Expr<'tcx>>,
|
||||
bindings: FxHashMap<Symbol, usize>,
|
||||
ctxt: SyntaxContext,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for StrippingFinder<'_, 'tcx> {
|
||||
@@ -236,7 +243,7 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
{
|
||||
match (self.strip_kind, start, end) {
|
||||
(StripKind::Prefix, Some(start), None) => {
|
||||
if eq_pattern_length(self.cx, self.pattern, start) {
|
||||
if eq_pattern_length(self.cx, self.pattern, start, self.ctxt) {
|
||||
self.results.push(ex);
|
||||
return;
|
||||
}
|
||||
@@ -252,7 +259,7 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
&& let Some(left_arg) = len_arg(self.cx, left)
|
||||
&& let ExprKind::Path(left_path) = &left_arg.kind
|
||||
&& self.cx.qpath_res(left_path, left_arg.hir_id) == self.target
|
||||
&& eq_pattern_length(self.cx, self.pattern, right)
|
||||
&& eq_pattern_length(self.cx, self.pattern, right, self.ctxt)
|
||||
{
|
||||
self.results.push(ex);
|
||||
return;
|
||||
@@ -280,6 +287,7 @@ fn visit_pat(&mut self, pat: &'tcx rustc_hir::Pat<'tcx>) -> Self::Result {
|
||||
pattern,
|
||||
results: vec![],
|
||||
bindings: FxHashMap::default(),
|
||||
ctxt,
|
||||
};
|
||||
walk_expr(&mut finder, expr);
|
||||
(finder.results, finder.bindings)
|
||||
|
||||
@@ -155,7 +155,7 @@ fn handle(
|
||||
&& cx.typeck_results().expr_adjustments(body_some).is_empty()
|
||||
&& let Some(or_body_snippet) = peel_blocks(body_none).span.get_source_text(cx)
|
||||
&& let Some(indent) = indent_of(cx, expr.span)
|
||||
&& ConstEvalCtxt::new(cx).eval_simple(body_none).is_some()
|
||||
&& ConstEvalCtxt::new(cx).eval_local(body_none, expr.span.ctxt()).is_some()
|
||||
{
|
||||
let reindented_or_body = reindent_multiline(&or_body_snippet, true, Some(indent));
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::{SpanlessEq, SpanlessHash, fulfill_or_allowed, is_lint_allowed, path_to_local, search_same};
|
||||
use clippy_utils::{SpanlessEq, fulfill_or_allowed, hash_expr, is_lint_allowed, path_to_local, search_same};
|
||||
use core::cmp::Ordering;
|
||||
use core::{iter, slice};
|
||||
use itertools::Itertools;
|
||||
@@ -18,11 +18,7 @@
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
||||
let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
|
||||
let mut h = SpanlessHash::new(cx);
|
||||
h.hash_expr(arm.body);
|
||||
h.finish()
|
||||
};
|
||||
let hash = |&(_, arm): &(_, &Arm<'_>)| hash_expr(cx, arm.body);
|
||||
|
||||
let arena = DroplessArena::default();
|
||||
let normalized_pats: Vec<_> = arms
|
||||
@@ -35,9 +31,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, pat)| {
|
||||
normalized_pats[i + 1..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
(normalized_pats[i + 1..].iter().enumerate())
|
||||
.find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j))
|
||||
.unwrap_or(normalized_pats.len())
|
||||
})
|
||||
@@ -48,16 +42,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, pat)| {
|
||||
normalized_pats[..i]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.zip(forwards_blocking_idxs[..i].iter().copied().rev())
|
||||
.skip_while(|&(_, forward_block)| forward_block > i)
|
||||
.find_map(|((j, other), forward_block)| {
|
||||
(forward_block == i || pat.has_overlapping_values(other)).then_some(j)
|
||||
})
|
||||
.unwrap_or(0)
|
||||
iter::zip(
|
||||
normalized_pats[..i].iter().enumerate().rev(),
|
||||
forwards_blocking_idxs[..i].iter().copied().rev(),
|
||||
)
|
||||
.skip_while(|&(_, forward_block)| forward_block > i)
|
||||
.find_map(|((j, other), forward_block)| {
|
||||
(forward_block == i || pat.has_overlapping_values(other)).then_some(j)
|
||||
})
|
||||
.unwrap_or(0)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -158,12 +151,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
|
||||
.map(|(_, arm)| arm.pat.span.get_source_text(cx))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
{
|
||||
let mut suggs = src
|
||||
let suggs = src
|
||||
.iter()
|
||||
.map(|(_, arm)| (adjusted_arm_span(cx, arm.span), String::new()))
|
||||
.chain([(dest.pat.span, pat_snippets.iter().join(" | "))])
|
||||
.collect_vec();
|
||||
|
||||
suggs.push((dest.pat.span, pat_snippets.iter().join(" | ")));
|
||||
diag.multipart_suggestion_verbose(
|
||||
"otherwise merge the patterns into a single arm",
|
||||
suggs,
|
||||
@@ -396,10 +389,7 @@ fn has_overlapping_values(&self, other: &Self) -> bool {
|
||||
if lpath != rpath {
|
||||
return false;
|
||||
}
|
||||
lpats
|
||||
.iter()
|
||||
.zip(rpats.iter())
|
||||
.all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
|
||||
iter::zip(lpats, rpats).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
|
||||
},
|
||||
(Self::Path(x), Self::Path(y)) => x == y,
|
||||
(Self::LitStr(x), Self::LitStr(y)) => x == y,
|
||||
@@ -409,7 +399,7 @@ fn has_overlapping_values(&self, other: &Self) -> bool {
|
||||
(Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y),
|
||||
(Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x),
|
||||
(Self::Slice(lpats, None), Self::Slice(rpats, None)) => {
|
||||
lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y))
|
||||
lpats.len() == rpats.len() && iter::zip(lpats, rpats).all(|(x, y)| x.has_overlapping_values(y))
|
||||
},
|
||||
(Self::Slice(pats, None), Self::Slice(front, Some(back)))
|
||||
| (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
|
||||
@@ -418,16 +408,12 @@ fn has_overlapping_values(&self, other: &Self) -> bool {
|
||||
if pats.len() < front.len() + back.len() {
|
||||
return false;
|
||||
}
|
||||
pats[..front.len()]
|
||||
.iter()
|
||||
.zip(front.iter())
|
||||
.chain(pats[pats.len() - back.len()..].iter().zip(back.iter()))
|
||||
iter::zip(&pats[..front.len()], front)
|
||||
.chain(iter::zip(&pats[pats.len() - back.len()..], back))
|
||||
.all(|(x, y)| x.has_overlapping_values(y))
|
||||
},
|
||||
(Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront
|
||||
.iter()
|
||||
.zip(rfront.iter())
|
||||
.chain(lback.iter().rev().zip(rback.iter().rev()))
|
||||
(Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => iter::zip(lfront, rfront)
|
||||
.chain(iter::zip(lback.iter().rev(), rback.iter().rev()))
|
||||
.all(|(x, y)| x.has_overlapping_values(y)),
|
||||
|
||||
// Enums can mix unit variants with tuple/struct variants. These can never overlap.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use clippy_utils::consts::{ConstEvalCtxt, FullInt, mir_to_const};
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt};
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use core::cmp::Ordering;
|
||||
use rustc_hir::{Arm, Expr, PatKind, RangeEnd};
|
||||
@@ -35,12 +35,12 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
|
||||
let lhs_const = if let Some(lhs) = lhs {
|
||||
ConstEvalCtxt::new(cx).eval_pat_expr(lhs)?
|
||||
} else {
|
||||
mir_to_const(cx.tcx, ty.numeric_min_val(cx.tcx)?)?
|
||||
Constant::new_numeric_min(cx.tcx, ty)?
|
||||
};
|
||||
let rhs_const = if let Some(rhs) = rhs {
|
||||
ConstEvalCtxt::new(cx).eval_pat_expr(rhs)?
|
||||
} else {
|
||||
mir_to_const(cx.tcx, ty.numeric_max_val(cx.tcx)?)?
|
||||
Constant::new_numeric_max(cx.tcx, ty)?
|
||||
};
|
||||
let lhs_val = lhs_const.int_value(cx.tcx, ty)?;
|
||||
let rhs_val = rhs_const.int_value(cx.tcx, ty)?;
|
||||
|
||||
@@ -269,66 +269,61 @@ fn find_method_sugg_for_if_let<'tcx>(
|
||||
}
|
||||
|
||||
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
|
||||
if arms.len() == 2 {
|
||||
let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
|
||||
if let Ok(arms) = arms.try_into() // TODO: use `slice::as_array` once stabilized
|
||||
&& let Some((good_method, maybe_guard)) = found_good_method(cx, arms)
|
||||
{
|
||||
let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span));
|
||||
let result_expr = match &op.kind {
|
||||
ExprKind::AddrOf(_, _, borrowed) => borrowed,
|
||||
_ => op,
|
||||
};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren();
|
||||
let mut sugg = format!("{receiver_sugg}.{good_method}");
|
||||
|
||||
if let Some((good_method, maybe_guard)) = found_good_method(cx, arms, node_pair) {
|
||||
let span = is_expn_of(expr.span, sym::matches).unwrap_or(expr.span.to(op.span));
|
||||
let result_expr = match &op.kind {
|
||||
ExprKind::AddrOf(_, _, borrowed) => borrowed,
|
||||
_ => op,
|
||||
};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren();
|
||||
let mut sugg = format!("{receiver_sugg}.{good_method}");
|
||||
|
||||
if let Some(guard) = maybe_guard {
|
||||
// wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying!
|
||||
// `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs,
|
||||
// counter to the intuition that it should be `Guard::IfLet`, so we need another check
|
||||
// to see that there aren't any let chains anywhere in the guard, as that would break
|
||||
// if we suggest `t.is_none() && (let X = y && z)` for:
|
||||
// `match t { None if let X = y && z => true, _ => false }`
|
||||
let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| {
|
||||
if matches!(expr.kind, ExprKind::Let(..)) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
|
||||
if has_nested_let_chain {
|
||||
return;
|
||||
if let Some(guard) = maybe_guard {
|
||||
// wow, the HIR for match guards in `PAT if let PAT = expr && expr => ...` is annoying!
|
||||
// `guard` here is `Guard::If` with the let expression somewhere deep in the tree of exprs,
|
||||
// counter to the intuition that it should be `Guard::IfLet`, so we need another check
|
||||
// to see that there aren't any let chains anywhere in the guard, as that would break
|
||||
// if we suggest `t.is_none() && (let X = y && z)` for:
|
||||
// `match t { None if let X = y && z => true, _ => false }`
|
||||
let has_nested_let_chain = for_each_expr_without_closures(guard, |expr| {
|
||||
if matches!(expr.kind, ExprKind::Let(..)) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some();
|
||||
|
||||
let guard = Sugg::hir(cx, guard, "..");
|
||||
let _ = write!(sugg, " && {}", guard.maybe_paren());
|
||||
if has_nested_let_chain {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REDUNDANT_PATTERN_MATCHING,
|
||||
span,
|
||||
format!("redundant pattern matching, consider using `{good_method}`"),
|
||||
"try",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
let guard = Sugg::hir(cx, guard, "..");
|
||||
let _ = write!(sugg, " && {}", guard.maybe_paren());
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REDUNDANT_PATTERN_MATCHING,
|
||||
span,
|
||||
format!("redundant pattern matching, consider using `{good_method}`"),
|
||||
"try",
|
||||
sugg,
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn found_good_method<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
arms: &'tcx [Arm<'tcx>],
|
||||
node: (&PatKind<'_>, &PatKind<'_>),
|
||||
arms: &'tcx [Arm<'tcx>; 2],
|
||||
) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> {
|
||||
match node {
|
||||
(PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _))
|
||||
if patterns_left.len() == 1 && patterns_right.len() == 1 =>
|
||||
{
|
||||
if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
|
||||
match (&arms[0].pat.kind, &arms[1].pat.kind) {
|
||||
(PatKind::TupleStruct(path_left, [pattern_left], _), PatKind::TupleStruct(path_right, [pattern_right], _)) => {
|
||||
if let (PatKind::Wild, PatKind::Wild) = (&pattern_left.kind, &pattern_right.kind) {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
@@ -356,7 +351,7 @@ fn found_good_method<'tcx>(
|
||||
}
|
||||
},
|
||||
(
|
||||
PatKind::TupleStruct(path_left, patterns, _),
|
||||
PatKind::TupleStruct(path_left, [pattern], _),
|
||||
PatKind::Expr(PatExpr {
|
||||
kind: PatExprKind::Path(path_right),
|
||||
..
|
||||
@@ -367,9 +362,9 @@ fn found_good_method<'tcx>(
|
||||
kind: PatExprKind::Path(path_left),
|
||||
..
|
||||
}),
|
||||
PatKind::TupleStruct(path_right, patterns, _),
|
||||
) if patterns.len() == 1 => {
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
PatKind::TupleStruct(path_right, [pattern], _),
|
||||
) => {
|
||||
if let PatKind::Wild = pattern.kind {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
@@ -396,8 +391,8 @@ fn found_good_method<'tcx>(
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => {
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
(PatKind::TupleStruct(path_left, [pattern], _), PatKind::Wild) => {
|
||||
if let PatKind::Wild = pattern.kind {
|
||||
get_good_method(cx, arms, path_left)
|
||||
} else {
|
||||
None
|
||||
@@ -426,31 +421,23 @@ fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
|
||||
|
||||
fn get_good_method<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
arms: &'tcx [Arm<'tcx>],
|
||||
arms: &'tcx [Arm<'tcx>; 2],
|
||||
path_left: &QPath<'_>,
|
||||
) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> {
|
||||
if let Some(name) = get_ident(path_left) {
|
||||
let (expected_item_left, should_be_left, should_be_right) = match name.as_str() {
|
||||
"Ok" => (Item::Lang(ResultOk), "is_ok()", "is_err()"),
|
||||
"Err" => (Item::Lang(ResultErr), "is_err()", "is_ok()"),
|
||||
"Some" => (Item::Lang(OptionSome), "is_some()", "is_none()"),
|
||||
"None" => (Item::Lang(OptionNone), "is_none()", "is_some()"),
|
||||
"Ready" => (Item::Lang(PollReady), "is_ready()", "is_pending()"),
|
||||
"Pending" => (Item::Lang(PollPending), "is_pending()", "is_ready()"),
|
||||
"V4" => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"),
|
||||
"V6" => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"),
|
||||
_ => return None,
|
||||
};
|
||||
return find_good_method_for_matches_macro(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
expected_item_left,
|
||||
should_be_left,
|
||||
should_be_right,
|
||||
);
|
||||
}
|
||||
None
|
||||
let ident = get_ident(path_left)?;
|
||||
|
||||
let (expected_item_left, should_be_left, should_be_right) = match ident.name {
|
||||
sym::Ok => (Item::Lang(ResultOk), "is_ok()", "is_err()"),
|
||||
sym::Err => (Item::Lang(ResultErr), "is_err()", "is_ok()"),
|
||||
sym::Some => (Item::Lang(OptionSome), "is_some()", "is_none()"),
|
||||
sym::None => (Item::Lang(OptionNone), "is_none()", "is_some()"),
|
||||
sym::Ready => (Item::Lang(PollReady), "is_ready()", "is_pending()"),
|
||||
sym::Pending => (Item::Lang(PollPending), "is_pending()", "is_ready()"),
|
||||
sym::V4 => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"),
|
||||
sym::V6 => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"),
|
||||
_ => return None,
|
||||
};
|
||||
find_good_method_for_matches_macro(cx, arms, path_left, expected_item_left, should_be_left, should_be_right)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -490,7 +477,7 @@ fn is_pat_variant(cx: &LateContext<'_>, pat: &Pat<'_>, path: &QPath<'_>, expecte
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn find_good_method_for_match<'a, 'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
arms: &'tcx [Arm<'tcx>],
|
||||
arms: &'tcx [Arm<'tcx>; 2],
|
||||
path_left: &QPath<'_>,
|
||||
path_right: &QPath<'_>,
|
||||
expected_item_left: Item,
|
||||
@@ -525,7 +512,7 @@ fn find_good_method_for_match<'a, 'tcx>(
|
||||
|
||||
fn find_good_method_for_matches_macro<'a, 'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
arms: &'tcx [Arm<'tcx>],
|
||||
arms: &'tcx [Arm<'tcx>; 2],
|
||||
path_left: &QPath<'_>,
|
||||
expected_item_left: Item,
|
||||
should_be_left: &'a str,
|
||||
|
||||
@@ -22,17 +22,16 @@
|
||||
/// span, e.g. a string literal `"//"`, but we know that this isn't the case for empty
|
||||
/// match arms.
|
||||
fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool {
|
||||
if let Some(ff) = span.get_source_range(cx)
|
||||
&& let Some(text) = ff.as_str()
|
||||
{
|
||||
text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
span.check_source_text(cx, |text| text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*"))
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>, contains_comments: bool) {
|
||||
pub(crate) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ex: &'tcx Expr<'_>,
|
||||
arms: &'tcx [Arm<'_>],
|
||||
expr: &'tcx Expr<'_>,
|
||||
contains_comments: bool,
|
||||
) {
|
||||
if let [arm1, arm2] = arms
|
||||
&& !arms.iter().any(|arm| arm.guard.is_some() || arm.pat.span.from_expansion())
|
||||
&& !expr.span.from_expansion()
|
||||
@@ -224,13 +223,13 @@ enum PatState<'a> {
|
||||
Wild,
|
||||
/// A std enum we know won't be extended. Tracks the states of each variant separately.
|
||||
///
|
||||
/// This is not used for `Option` since it uses the current pattern to track it's state.
|
||||
/// This is not used for `Option` since it uses the current pattern to track its state.
|
||||
StdEnum(&'a mut [PatState<'a>]),
|
||||
/// Either the initial state for a pattern or a non-std enum. There is currently no need to
|
||||
/// distinguish these cases.
|
||||
///
|
||||
/// For non-std enums there's no need to track the state of sub-patterns as the state of just
|
||||
/// this pattern on it's own is enough for linting. Consider two cases:
|
||||
/// this pattern on its own is enough for linting. Consider two cases:
|
||||
/// * This enum has no wild match. This case alone is enough to determine we can lint.
|
||||
/// * This enum has a wild match and therefore all sub-patterns also have a wild match.
|
||||
///
|
||||
@@ -378,7 +377,11 @@ fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool {
|
||||
self.add_pat(cx, pat)
|
||||
},
|
||||
PatKind::Tuple([sub_pat], pos)
|
||||
if pos.as_opt_usize().is_none() || cx.typeck.pat_ty(pat).tuple_fields().len() == 1 =>
|
||||
// `pat` looks like `(sub_pat)`, without a `..` -- has only one sub-pattern
|
||||
if pos.as_opt_usize().is_none()
|
||||
// `pat` looks like `(sub_pat, ..)` or `(.., sub_pat)`, but its type is a unary tuple,
|
||||
// so it still only has one sub-pattern
|
||||
|| cx.typeck.pat_ty(pat).tuple_fields().len() == 1 =>
|
||||
{
|
||||
self.add_pat(cx, sub_pat)
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_non_aggregate_primitive_type;
|
||||
use clippy_utils::{
|
||||
@@ -269,14 +269,11 @@ fn check_replace_with_default(
|
||||
),
|
||||
|diag| {
|
||||
if !expr.span.from_expansion() {
|
||||
let suggestion = format!("{top_crate}::mem::take({})", snippet(cx, dest.span, ""));
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (dest_snip, _) = snippet_with_context(cx, dest.span, expr.span.ctxt(), "", &mut applicability);
|
||||
let suggestion = format!("{top_crate}::mem::take({dest_snip})");
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"consider using",
|
||||
suggestion,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.span_suggestion(expr.span, "consider using", suggestion, applicability);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -10,51 +10,68 @@
|
||||
|
||||
use super::FILTER_NEXT;
|
||||
|
||||
/// lint use of `filter().next()` for `Iterators`
|
||||
#[derive(Copy, Clone)]
|
||||
pub(super) enum Direction {
|
||||
Forward,
|
||||
Backward,
|
||||
}
|
||||
|
||||
/// lint use of `filter().next()` for `Iterator` and `filter().next_back()` for
|
||||
/// `DoubleEndedIterator`
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'_>,
|
||||
recv: &'tcx hir::Expr<'_>,
|
||||
filter_arg: &'tcx hir::Expr<'_>,
|
||||
direction: Direction,
|
||||
) {
|
||||
// lint if caller of `.filter().next()` is an Iterator
|
||||
let recv_impls_iterator = cx
|
||||
// lint if caller of `.filter().next()` is an Iterator or `.filter().next_back()` is a
|
||||
// DoubleEndedIterator
|
||||
let (required_trait, next_method, find_method) = match direction {
|
||||
Direction::Forward => (sym::Iterator, "next", "find"),
|
||||
Direction::Backward => (sym::DoubleEndedIterator, "next_back", "rfind"),
|
||||
};
|
||||
if !cx
|
||||
.tcx
|
||||
.get_diagnostic_item(sym::Iterator)
|
||||
.is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[]));
|
||||
if recv_impls_iterator {
|
||||
let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
|
||||
`.find(..)` instead";
|
||||
let filter_snippet = snippet(cx, filter_arg.span, "..");
|
||||
if filter_snippet.lines().count() <= 1 {
|
||||
let iter_snippet = snippet(cx, recv.span, "..");
|
||||
// add note if not multi-line
|
||||
span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| {
|
||||
let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv)
|
||||
&& let hir::Node::Pat(pat) = cx.tcx.hir_node(id)
|
||||
&& let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind
|
||||
{
|
||||
(Applicability::Unspecified, Some((pat.span, ident)))
|
||||
} else {
|
||||
(Applicability::MachineApplicable, None)
|
||||
};
|
||||
.get_diagnostic_item(required_trait)
|
||||
.is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let msg = format!(
|
||||
"called `filter(..).{next_method}()` on an `{}`. This is more succinctly expressed by calling \
|
||||
`.{find_method}(..)` instead",
|
||||
required_trait.as_str()
|
||||
);
|
||||
let filter_snippet = snippet(cx, filter_arg.span, "..");
|
||||
if filter_snippet.lines().count() <= 1 {
|
||||
let iter_snippet = snippet(cx, recv.span, "..");
|
||||
// add note if not multi-line
|
||||
span_lint_and_then(cx, FILTER_NEXT, expr.span, msg, |diag| {
|
||||
let (applicability, pat) = if let Some(id) = path_to_local_with_projections(recv)
|
||||
&& let hir::Node::Pat(pat) = cx.tcx.hir_node(id)
|
||||
&& let hir::PatKind::Binding(BindingMode(_, Mutability::Not), _, ident, _) = pat.kind
|
||||
{
|
||||
(Applicability::Unspecified, Some((pat.span, ident)))
|
||||
} else {
|
||||
(Applicability::MachineApplicable, None)
|
||||
};
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"try",
|
||||
format!("{iter_snippet}.find({filter_snippet})"),
|
||||
applicability,
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"try",
|
||||
format!("{iter_snippet}.{find_method}({filter_snippet})"),
|
||||
applicability,
|
||||
);
|
||||
|
||||
if let Some((pat_span, ident)) = pat {
|
||||
diag.span_help(
|
||||
pat_span,
|
||||
format!("you will also need to make `{ident}` mutable, because `{find_method}` takes `&mut self`"),
|
||||
);
|
||||
|
||||
if let Some((pat_span, ident)) = pat {
|
||||
diag.span_help(
|
||||
pat_span,
|
||||
format!("you will also need to make `{ident}` mutable, because `find` takes `&mut self`"),
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
span_lint(cx, FILTER_NEXT, expr.span, msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
span_lint(cx, FILTER_NEXT, expr.span, msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_lang_item, peel_and_count_ty_refs};
|
||||
use rustc_errors::Applicability;
|
||||
@@ -16,6 +17,7 @@ pub fn check(
|
||||
method_name: Symbol,
|
||||
receiver: &hir::Expr<'_>,
|
||||
args: &[hir::Expr<'_>],
|
||||
msrv: Msrv,
|
||||
) {
|
||||
if args.is_empty()
|
||||
&& method_name == sym::to_string
|
||||
@@ -26,6 +28,8 @@ pub fn check(
|
||||
&& let self_ty = args.type_at(0)
|
||||
&& let (deref_self_ty, deref_count, _) = peel_and_count_ty_refs(self_ty)
|
||||
&& deref_count >= 1
|
||||
// Since Rust 1.82, the specialized `ToString` is properly called
|
||||
&& !msrv.meets(cx, msrvs::SPECIALIZED_TO_STRING_FOR_REFS)
|
||||
&& specializes_tostring(cx, deref_self_ty)
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
||||
@@ -17,10 +17,12 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
|
||||
cx.tcx.get_diagnostic_name(func_def_id),
|
||||
Some(sym::Ipv4Addr | sym::Ipv6Addr)
|
||||
)
|
||||
&& let ecx = ConstEvalCtxt::new(cx)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& let Some(args) = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ConstEvalCtxt::new(cx).eval(arg) {
|
||||
if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ecx.eval_local(arg, ctxt) {
|
||||
u8::try_from(constant).ok()
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix) {
|
||||
if let Some(radix_val) = ConstEvalCtxt::new(cx).eval_full_int(radix, expr.span.ctxt()) {
|
||||
let (num, replacement) = match radix_val {
|
||||
FullInt::S(10) | FullInt::U(10) => (10, "is_ascii_digit"),
|
||||
FullInt::S(16) | FullInt::U(16) => (16, "is_ascii_hexdigit"),
|
||||
|
||||
@@ -14,7 +14,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
|
||||
if let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id))
|
||||
&& let def_id = item.owner_id.to_def_id()
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval(arg)
|
||||
&& let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt())
|
||||
&& !is_lang_item_or_ctor(cx, def_id, LangItem::IteratorNext)
|
||||
{
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg_expr: &Expr<'_>) {
|
||||
if !expr.span.from_expansion()
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let Some(arg) = ConstEvalCtxt::new(cx).eval(arg_expr).and_then(|constant| {
|
||||
if let Constant::Int(arg) = constant {
|
||||
Some(arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
&& let Some(arg) = ConstEvalCtxt::new(cx)
|
||||
.eval_local(arg_expr, expr.span.ctxt())
|
||||
.and_then(|constant| {
|
||||
if let Constant::Int(arg) = constant {
|
||||
Some(arg)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
&& arg == 0
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use clippy_utils::sym;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use rustc_hir::Mutability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(super) enum SelfKind {
|
||||
Value,
|
||||
Ref,
|
||||
RefMut,
|
||||
No, // When we want the first argument type to be different than `Self`
|
||||
}
|
||||
|
||||
impl SelfKind {
|
||||
pub(super) fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
if ty == parent_ty {
|
||||
true
|
||||
} else if let Some(boxed_ty) = ty.boxed_ty() {
|
||||
boxed_ty == parent_ty
|
||||
} else if let ty::Adt(adt_def, args) = ty.kind()
|
||||
&& matches!(cx.tcx.get_diagnostic_name(adt_def.did()), Some(sym::Rc | sym::Arc))
|
||||
{
|
||||
args.types().next() == Some(parent_ty)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_ref<'a>(cx: &LateContext<'a>, mutability: Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
if let ty::Ref(_, t, m) = *ty.kind() {
|
||||
return m == mutability && t == parent_ty;
|
||||
}
|
||||
|
||||
let trait_sym = match mutability {
|
||||
Mutability::Not => sym::AsRef,
|
||||
Mutability::Mut => sym::AsMut,
|
||||
};
|
||||
|
||||
let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else {
|
||||
return false;
|
||||
};
|
||||
implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
|
||||
}
|
||||
|
||||
fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
!matches_value(cx, parent_ty, ty)
|
||||
&& !matches_ref(cx, Mutability::Not, parent_ty, ty)
|
||||
&& !matches_ref(cx, Mutability::Mut, parent_ty, ty)
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Value => matches_value(cx, parent_ty, ty),
|
||||
Self::Ref => matches_ref(cx, Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
|
||||
Self::RefMut => matches_ref(cx, Mutability::Mut, parent_ty, ty),
|
||||
Self::No => matches_none(cx, parent_ty, ty),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(super) fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Value => "`self` by value",
|
||||
Self::Ref => "`self` by reference",
|
||||
Self::RefMut => "`self` by mutable reference",
|
||||
Self::No => "no `self`",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{is_expr_untyped_identity_function, is_mutable, is_trait_method, path_to_local_with_projections};
|
||||
@@ -27,48 +27,46 @@ pub(super) fn check(
|
||||
&& is_expr_untyped_identity_function(cx, map_arg)
|
||||
&& let Some(call_span) = expr.span.trim_start(caller.span)
|
||||
{
|
||||
let main_sugg = (call_span, String::new());
|
||||
let mut app = if is_copy(cx, caller_ty) {
|
||||
// there is technically a behavioral change here for `Copy` iterators, where
|
||||
// `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and
|
||||
// changing it to `iter.next()` mutates iter directly
|
||||
Applicability::Unspecified
|
||||
} else {
|
||||
Applicability::MachineApplicable
|
||||
};
|
||||
span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| {
|
||||
let main_sugg = (call_span, String::new());
|
||||
let mut app = if is_copy(cx, caller_ty) {
|
||||
// there is technically a behavioral change here for `Copy` iterators, where
|
||||
// `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and
|
||||
// changing it to `iter.next()` mutates iter directly
|
||||
Applicability::Unspecified
|
||||
} else {
|
||||
Applicability::MachineApplicable
|
||||
};
|
||||
|
||||
let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr();
|
||||
if needs_to_be_mutable && !is_mutable(cx, caller) {
|
||||
if let Some(hir_id) = path_to_local_with_projections(caller)
|
||||
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
|
||||
&& let PatKind::Binding(_, _, ident, _) = pat.kind
|
||||
{
|
||||
// We can reach the binding -- suggest making it mutable
|
||||
let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))];
|
||||
let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr();
|
||||
if needs_to_be_mutable && !is_mutable(cx, caller) {
|
||||
if let Some(hir_id) = path_to_local_with_projections(caller)
|
||||
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
|
||||
&& let PatKind::Binding(_, _, ident, _) = pat.kind
|
||||
{
|
||||
// We can reach the binding -- suggest making it mutable
|
||||
let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))];
|
||||
|
||||
let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app);
|
||||
let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app);
|
||||
|
||||
span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| {
|
||||
diag.multipart_suggestion(
|
||||
format!("remove the call to `{name}`, and make `{ident}` mutable"),
|
||||
suggs,
|
||||
app,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// If we can't make the binding mutable, prevent the suggestion from being automatically applied,
|
||||
// and add a complementary help message.
|
||||
app = Applicability::Unspecified;
|
||||
|
||||
let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id)
|
||||
&& let ExprKind::MethodCall(method, ..) = expr.kind
|
||||
{
|
||||
Some(method.ident)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// If we can't make the binding mutable, prevent the suggestion from being automatically applied,
|
||||
// and add a complementary help message.
|
||||
app = Applicability::Unspecified;
|
||||
|
||||
let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id)
|
||||
&& let ExprKind::MethodCall(method, ..) = expr.kind
|
||||
{
|
||||
Some(method.ident)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| {
|
||||
diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app);
|
||||
|
||||
let note = if let Some(method_requiring_mut) = method_requiring_mut {
|
||||
@@ -77,18 +75,10 @@ pub(super) fn check(
|
||||
"this must be made mutable".to_string()
|
||||
};
|
||||
diag.span_note(caller.span, note);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app);
|
||||
}
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MAP_IDENTITY,
|
||||
main_sugg.0,
|
||||
MSG,
|
||||
format!("remove the call to `{name}`"),
|
||||
main_sugg.1,
|
||||
app,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+58
-290
@@ -55,6 +55,7 @@
|
||||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod lib;
|
||||
mod manual_c_str_literals;
|
||||
mod manual_contains;
|
||||
mod manual_inspect;
|
||||
@@ -79,6 +80,7 @@
|
||||
mod needless_collect;
|
||||
mod needless_option_as_deref;
|
||||
mod needless_option_take;
|
||||
mod new_ret_no_self;
|
||||
mod no_effect_replace;
|
||||
mod obfuscated_if_else;
|
||||
mod ok_expect;
|
||||
@@ -102,9 +104,8 @@
|
||||
mod search_is_some;
|
||||
mod seek_from_current;
|
||||
mod seek_to_start_instead_of_rewind;
|
||||
mod should_implement_trait;
|
||||
mod single_char_add_str;
|
||||
mod single_char_insert_string;
|
||||
mod single_char_push_string;
|
||||
mod skip_while_next;
|
||||
mod sliced_string_as_bytes;
|
||||
mod stable_sort_primitive;
|
||||
@@ -148,20 +149,16 @@
|
||||
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
||||
use clippy_utils::macros::FormatArgsStorage;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty, sym};
|
||||
use clippy_utils::{contains_return, is_trait_method, iter_input_pats, peel_blocks, sym};
|
||||
pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
|
||||
use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::{self, TraitRef, Ty};
|
||||
use rustc_middle::ty::TraitRef;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::{Span, Symbol, kw};
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -477,6 +474,9 @@
|
||||
/// ### What it does
|
||||
/// Checks for usage of `ok().expect(..)`.
|
||||
///
|
||||
/// Note: This lint only triggers for code marked compatible
|
||||
/// with versions of the compiler older than Rust 1.82.0.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Because you usually call `expect()` on the `Result`
|
||||
/// directly to get a better error message.
|
||||
@@ -1080,9 +1080,9 @@
|
||||
/// `T` implements `ToString` directly (like `&&str` or `&&String`).
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This bypasses the specialized implementation of
|
||||
/// `ToString` and instead goes through the more expensive string formatting
|
||||
/// facilities.
|
||||
/// In versions of the compiler before Rust 1.82.0, this bypasses the specialized
|
||||
/// implementation of`ToString` and instead goes through the more expensive string
|
||||
/// formatting facilities.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@@ -4462,7 +4462,7 @@
|
||||
/// Checks for calls to `Read::bytes` on types which don't implement `BufRead`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`.
|
||||
/// The default implementation calls `read` for each byte, which can be very inefficient for data that's not in memory, such as `File`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@@ -4856,7 +4856,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
},
|
||||
ExprKind::MethodCall(method_call, receiver, args, _) => {
|
||||
let method_span = method_call.ident.span;
|
||||
or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args);
|
||||
or_fun_call::check(cx, expr, method_span, method_call.ident.name, receiver, args, self.msrv);
|
||||
expect_fun_call::check(
|
||||
cx,
|
||||
&self.format_args,
|
||||
@@ -4868,7 +4868,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
);
|
||||
clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args);
|
||||
clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args);
|
||||
inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);
|
||||
inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args, self.msrv);
|
||||
single_char_add_str::check(cx, expr, receiver, args);
|
||||
into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver);
|
||||
unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv);
|
||||
@@ -4891,48 +4891,17 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::Impl
|
||||
if impl_item.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
let name = impl_item.ident.name;
|
||||
let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id;
|
||||
let item = cx.tcx.hir_expect_item(parent);
|
||||
let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
|
||||
let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
|
||||
if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind {
|
||||
let parent = cx.tcx.hir_get_parent_item(impl_item.hir_id()).def_id;
|
||||
let item = cx.tcx.hir_expect_item(parent);
|
||||
let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
|
||||
|
||||
let method_sig = cx.tcx.fn_sig(impl_item.owner_id).instantiate_identity();
|
||||
let method_sig = cx.tcx.instantiate_bound_regions_with_erased(method_sig);
|
||||
let first_arg_ty_opt = method_sig.inputs().iter().next().copied();
|
||||
// if this impl block implements a trait, lint in trait definition instead
|
||||
if !implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) {
|
||||
// check missing trait implementations
|
||||
for method_config in &TRAIT_METHODS {
|
||||
if name == method_config.method_name
|
||||
&& sig.decl.inputs.len() == method_config.param_count
|
||||
&& method_config.output_type.matches(&sig.decl.output)
|
||||
// in case there is no first arg, since we already have checked the number of arguments
|
||||
// it's should be always true
|
||||
&& first_arg_ty_opt.is_none_or(|first_arg_ty| method_config
|
||||
.self_kind.matches(cx, self_ty, first_arg_ty)
|
||||
)
|
||||
&& fn_header_equals(method_config.fn_header, sig.header)
|
||||
&& method_config.lifetime_param_cond(impl_item)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SHOULD_IMPLEMENT_TRAIT,
|
||||
impl_item.span,
|
||||
format!(
|
||||
"method `{}` can be confused for the standard trait method `{}::{}`",
|
||||
method_config.method_name, method_config.trait_name, method_config.method_name
|
||||
),
|
||||
None,
|
||||
format!(
|
||||
"consider implementing the trait `{}` or choosing a less ambiguous method name",
|
||||
method_config.trait_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
should_implement_trait::check_impl_item(cx, impl_item, self_ty, implements_trait, first_arg_ty_opt, sig);
|
||||
|
||||
if sig.decl.implicit_self.has_implicit_self()
|
||||
&& !(self.avoid_breaking_exported_api
|
||||
@@ -4942,7 +4911,7 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::Impl
|
||||
{
|
||||
wrong_self_convention::check(
|
||||
cx,
|
||||
name,
|
||||
impl_item.ident.name,
|
||||
self_ty,
|
||||
first_arg_ty,
|
||||
first_arg.pat.span,
|
||||
@@ -4950,28 +4919,8 @@ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::Impl
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if this impl block implements a trait, lint in trait definition instead
|
||||
if implements_trait {
|
||||
return;
|
||||
}
|
||||
|
||||
if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
|
||||
let ret_ty = return_ty(cx, impl_item.owner_id);
|
||||
|
||||
if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if name == sym::new && ret_ty != self_ty {
|
||||
span_lint(
|
||||
cx,
|
||||
NEW_RET_NO_SELF,
|
||||
impl_item.span,
|
||||
"methods called `new` usually return `Self`",
|
||||
);
|
||||
}
|
||||
new_ret_no_self::check_impl_item(cx, impl_item, self_ty, implements_trait);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4980,41 +4929,30 @@ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>
|
||||
return;
|
||||
}
|
||||
|
||||
if let TraitItemKind::Fn(ref sig, _) = item.kind
|
||||
&& sig.decl.implicit_self.has_implicit_self()
|
||||
&& let Some(first_arg_hir_ty) = sig.decl.inputs.first()
|
||||
&& let Some(&first_arg_ty) = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.inputs()
|
||||
.skip_binder()
|
||||
.first()
|
||||
{
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
|
||||
wrong_self_convention::check(
|
||||
cx,
|
||||
item.ident.name,
|
||||
self_ty,
|
||||
first_arg_ty,
|
||||
first_arg_hir_ty.span,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
}
|
||||
if let TraitItemKind::Fn(ref sig, _) = item.kind {
|
||||
if sig.decl.implicit_self.has_implicit_self()
|
||||
&& let Some(first_arg_hir_ty) = sig.decl.inputs.first()
|
||||
&& let Some(&first_arg_ty) = cx
|
||||
.tcx
|
||||
.fn_sig(item.owner_id)
|
||||
.instantiate_identity()
|
||||
.inputs()
|
||||
.skip_binder()
|
||||
.first()
|
||||
{
|
||||
let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty();
|
||||
wrong_self_convention::check(
|
||||
cx,
|
||||
item.ident.name,
|
||||
self_ty,
|
||||
first_arg_ty,
|
||||
first_arg_hir_ty.span,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if item.ident.name == sym::new
|
||||
&& let TraitItemKind::Fn(_, _) = item.kind
|
||||
&& let ret_ty = return_ty(cx, item.owner_id)
|
||||
&& let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()).self_ty()
|
||||
&& !ret_ty.contains(self_ty)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
NEW_RET_NO_SELF,
|
||||
item.span,
|
||||
"methods called `new` usually return `Self`",
|
||||
);
|
||||
new_ret_no_self::check_trait_item(cx, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5369,7 +5307,9 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
iter_overeager_cloned::Op::LaterCloned,
|
||||
false,
|
||||
),
|
||||
(sym::filter, [arg]) => filter_next::check(cx, expr, recv2, arg),
|
||||
(sym::filter, [arg]) => {
|
||||
filter_next::check(cx, expr, recv2, arg, filter_next::Direction::Forward);
|
||||
},
|
||||
(sym::filter_map, [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
|
||||
(sym::iter, []) => iter_next_slice::check(cx, expr, recv2),
|
||||
(sym::skip, [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
|
||||
@@ -5379,6 +5319,14 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
}
|
||||
}
|
||||
},
|
||||
(sym::next_back, []) => {
|
||||
if let Some((name2, recv2, args2, _, _)) = method_call(recv)
|
||||
&& let (sym::filter, [arg]) = (name2, args2)
|
||||
&& self.msrv.meets(cx, msrvs::DOUBLE_ENDED_ITERATOR_RFIND)
|
||||
{
|
||||
filter_next::check(cx, expr, recv2, arg, filter_next::Direction::Backward);
|
||||
}
|
||||
},
|
||||
(sym::nth, [n_arg]) => match method_call(recv) {
|
||||
Some((sym::bytes, recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
|
||||
Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check(
|
||||
@@ -5712,183 +5660,3 @@ macro_rules! lint_with_both_lhs_and_rhs {
|
||||
lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
|
||||
lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
|
||||
}
|
||||
|
||||
const FN_HEADER: hir::FnHeader = hir::FnHeader {
|
||||
safety: hir::HeaderSafety::Normal(hir::Safety::Safe),
|
||||
constness: hir::Constness::NotConst,
|
||||
asyncness: hir::IsAsync::NotAsync,
|
||||
abi: ExternAbi::Rust,
|
||||
};
|
||||
|
||||
struct ShouldImplTraitCase {
|
||||
trait_name: &'static str,
|
||||
method_name: Symbol,
|
||||
param_count: usize,
|
||||
fn_header: hir::FnHeader,
|
||||
// implicit self kind expected (none, self, &self, ...)
|
||||
self_kind: SelfKind,
|
||||
// checks against the output type
|
||||
output_type: OutType,
|
||||
// certain methods with explicit lifetimes can't implement the equivalent trait method
|
||||
lint_explicit_lifetime: bool,
|
||||
}
|
||||
impl ShouldImplTraitCase {
|
||||
const fn new(
|
||||
trait_name: &'static str,
|
||||
method_name: Symbol,
|
||||
param_count: usize,
|
||||
fn_header: hir::FnHeader,
|
||||
self_kind: SelfKind,
|
||||
output_type: OutType,
|
||||
lint_explicit_lifetime: bool,
|
||||
) -> ShouldImplTraitCase {
|
||||
ShouldImplTraitCase {
|
||||
trait_name,
|
||||
method_name,
|
||||
param_count,
|
||||
fn_header,
|
||||
self_kind,
|
||||
output_type,
|
||||
lint_explicit_lifetime,
|
||||
}
|
||||
}
|
||||
|
||||
fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
|
||||
self.lint_explicit_lifetime
|
||||
|| !impl_item.generics.params.iter().any(|p| {
|
||||
matches!(
|
||||
p.kind,
|
||||
hir::GenericParamKind::Lifetime {
|
||||
kind: hir::LifetimeParamKind::Explicit
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
|
||||
ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, FN_HEADER, SelfKind::No, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
|
||||
ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
|
||||
ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, FN_HEADER, SelfKind::No, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, FN_HEADER, SelfKind::No, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
|
||||
ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
|
||||
ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum SelfKind {
|
||||
Value,
|
||||
Ref,
|
||||
RefMut,
|
||||
No, // When we want the first argument type to be different than `Self`
|
||||
}
|
||||
|
||||
impl SelfKind {
|
||||
fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
if ty == parent_ty {
|
||||
true
|
||||
} else if let Some(boxed_ty) = ty.boxed_ty() {
|
||||
boxed_ty == parent_ty
|
||||
} else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
|
||||
if let ty::Adt(_, args) = ty.kind() {
|
||||
args.types().next() == Some(parent_ty)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
if let ty::Ref(_, t, m) = *ty.kind() {
|
||||
return m == mutability && t == parent_ty;
|
||||
}
|
||||
|
||||
let trait_sym = match mutability {
|
||||
hir::Mutability::Not => sym::AsRef,
|
||||
hir::Mutability::Mut => sym::AsMut,
|
||||
};
|
||||
|
||||
let Some(trait_def_id) = cx.tcx.get_diagnostic_item(trait_sym) else {
|
||||
return false;
|
||||
};
|
||||
implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
|
||||
}
|
||||
|
||||
fn matches_none<'a>(cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
|
||||
!matches_value(cx, parent_ty, ty)
|
||||
&& !matches_ref(cx, hir::Mutability::Not, parent_ty, ty)
|
||||
&& !matches_ref(cx, hir::Mutability::Mut, parent_ty, ty)
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Value => matches_value(cx, parent_ty, ty),
|
||||
Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
|
||||
Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
|
||||
Self::No => matches_none(cx, parent_ty, ty),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Value => "`self` by value",
|
||||
Self::Ref => "`self` by reference",
|
||||
Self::RefMut => "`self` by mutable reference",
|
||||
Self::No => "no `self`",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum OutType {
|
||||
Unit,
|
||||
Bool,
|
||||
Any,
|
||||
Ref,
|
||||
}
|
||||
|
||||
impl OutType {
|
||||
fn matches(self, ty: &hir::FnRetTy<'_>) -> bool {
|
||||
let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
|
||||
match (self, ty) {
|
||||
(Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
|
||||
(Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true,
|
||||
(Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true,
|
||||
(Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true,
|
||||
(Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
|
||||
expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::return_ty;
|
||||
use clippy_utils::ty::contains_ty_adt_constructor_opaque;
|
||||
use rustc_hir::{ImplItem, TraitItem};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::NEW_RET_NO_SELF;
|
||||
|
||||
pub(super) fn check_impl_item<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
impl_item: &'tcx ImplItem<'_>,
|
||||
self_ty: Ty<'tcx>,
|
||||
implements_trait: bool,
|
||||
) {
|
||||
// if this impl block implements a trait, lint in trait definition instead
|
||||
if !implements_trait
|
||||
&& impl_item.ident.name == sym::new
|
||||
&& let ret_ty = return_ty(cx, impl_item.owner_id)
|
||||
&& ret_ty != self_ty
|
||||
&& !contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
NEW_RET_NO_SELF,
|
||||
impl_item.span,
|
||||
"methods called `new` usually return `Self`",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'tcx>) {
|
||||
if trait_item.ident.name == sym::new
|
||||
&& let ret_ty = return_ty(cx, trait_item.owner_id)
|
||||
&& let self_ty = ty::TraitRef::identity(cx.tcx, trait_item.owner_id.to_def_id()).self_ty()
|
||||
&& !ret_ty.contains(self_ty)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
NEW_RET_NO_SELF,
|
||||
trait_item.span,
|
||||
"methods called `new` usually return `Self`",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
|
||||
use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
@@ -18,7 +19,6 @@
|
||||
use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT};
|
||||
|
||||
/// Checks for the `OR_FUN_CALL` lint.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &hir::Expr<'_>,
|
||||
@@ -26,185 +26,8 @@ pub(super) fn check<'tcx>(
|
||||
name: Symbol,
|
||||
receiver: &'tcx hir::Expr<'_>,
|
||||
args: &'tcx [hir::Expr<'_>],
|
||||
msrv: Msrv,
|
||||
) {
|
||||
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
|
||||
/// `or_insert(T::new())` or `or_insert(T::default())`.
|
||||
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
|
||||
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
|
||||
fn check_unwrap_or_default(
|
||||
cx: &LateContext<'_>,
|
||||
name: Symbol,
|
||||
receiver: &hir::Expr<'_>,
|
||||
fun: &hir::Expr<'_>,
|
||||
call_expr: Option<&hir::Expr<'_>>,
|
||||
span: Span,
|
||||
method_span: Span,
|
||||
) -> bool {
|
||||
if !expr_type_is_certain(cx, receiver) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let is_new = |fun: &hir::Expr<'_>| {
|
||||
if let hir::ExprKind::Path(ref qpath) = fun.kind {
|
||||
let path = last_path_segment(qpath).ident.name;
|
||||
matches!(path, sym::new)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let output_type_implements_default = |fun| {
|
||||
let fun_ty = cx.typeck_results().expr_ty(fun);
|
||||
if let ty::FnDef(def_id, args) = fun_ty.kind() {
|
||||
let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output();
|
||||
cx.tcx
|
||||
.get_diagnostic_item(sym::Default)
|
||||
.is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[]))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let sugg = match (name, call_expr.is_some()) {
|
||||
(sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default,
|
||||
(sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs();
|
||||
let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| {
|
||||
cx.tcx
|
||||
.inherent_impls(adt_def.did())
|
||||
.iter()
|
||||
.flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg))
|
||||
.find_map(|assoc| {
|
||||
if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1
|
||||
{
|
||||
Some(assoc.def_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
let in_sugg_method_implementation = {
|
||||
matches!(
|
||||
suggested_method_def_id.as_local(),
|
||||
Some(local_def_id) if local_def_id == cx.tcx.hir_get_parent_item(receiver.hir_id).def_id
|
||||
)
|
||||
};
|
||||
if in_sugg_method_implementation {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a
|
||||
// non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases.
|
||||
if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// needs to target Default::default in particular or be *::new and have a Default impl
|
||||
// available
|
||||
if (is_new(fun) && output_type_implements_default(fun))
|
||||
|| match call_expr {
|
||||
Some(call_expr) => is_default_equivalent(cx, call_expr),
|
||||
None => is_default_equivalent_call(cx, fun, None) || closure_body_returns_empty_to_string(cx, fun),
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNWRAP_OR_DEFAULT,
|
||||
method_span.with_hi(span.hi()),
|
||||
format!("use of `{name}` to construct default value"),
|
||||
"try",
|
||||
format!("{sugg}()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `*or(foo())`.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn check_or_fn_call<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
name: Symbol,
|
||||
method_span: Span,
|
||||
self_expr: &hir::Expr<'_>,
|
||||
arg: &'tcx hir::Expr<'_>,
|
||||
// `Some` if fn has second argument
|
||||
second_arg: Option<&hir::Expr<'_>>,
|
||||
span: Span,
|
||||
// None if lambda is required
|
||||
fun_span: Option<Span>,
|
||||
) -> bool {
|
||||
// (path, fn_has_argument, methods, suffix)
|
||||
const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [
|
||||
(sym::BTreeEntry, false, &[sym::or_insert], "with"),
|
||||
(sym::HashMapEntry, false, &[sym::or_insert], "with"),
|
||||
(
|
||||
sym::Option,
|
||||
false,
|
||||
&[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or],
|
||||
"else",
|
||||
),
|
||||
(sym::Option, false, &[sym::get_or_insert], "with"),
|
||||
(sym::Option, true, &[sym::and], "then"),
|
||||
(sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"),
|
||||
(sym::Result, true, &[sym::and], "then"),
|
||||
];
|
||||
|
||||
if KNOW_TYPES.iter().any(|k| k.2.contains(&name))
|
||||
&& switch_to_lazy_eval(cx, arg)
|
||||
&& !contains_return(arg)
|
||||
&& let self_ty = cx.typeck_results().expr_ty(self_expr)
|
||||
&& let Some(&(_, fn_has_arguments, _, suffix)) = KNOW_TYPES
|
||||
.iter()
|
||||
.find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0) && i.2.contains(&name))
|
||||
{
|
||||
let ctxt = span.ctxt();
|
||||
let mut app = Applicability::HasPlaceholders;
|
||||
let sugg = {
|
||||
let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
|
||||
(false, Some(fun_span)) => (fun_span, false),
|
||||
_ => (arg.span, true),
|
||||
};
|
||||
|
||||
let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0;
|
||||
let snip = if use_lambda {
|
||||
let l_arg = if fn_has_arguments { "_" } else { "" };
|
||||
format!("|{l_arg}| {snip}")
|
||||
} else {
|
||||
snip.into_owned()
|
||||
};
|
||||
|
||||
if let Some(f) = second_arg {
|
||||
let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0;
|
||||
format!("{snip}, {f}")
|
||||
} else {
|
||||
snip
|
||||
}
|
||||
};
|
||||
let span_replace_word = method_span.with_hi(span.hi());
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OR_FUN_CALL,
|
||||
span_replace_word,
|
||||
format!("function call inside of `{name}`"),
|
||||
"try",
|
||||
format!("{name}_{suffix}({sugg})"),
|
||||
app,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
if let [arg] = args {
|
||||
let inner_arg = peel_blocks(arg);
|
||||
for_each_expr(cx, inner_arg, |ex| {
|
||||
@@ -224,11 +47,11 @@ fn check_or_fn_call<'tcx>(
|
||||
};
|
||||
(!inner_fun_has_args
|
||||
&& !is_nested_expr
|
||||
&& check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span))
|
||||
&& check_unwrap_or_default(cx, name, receiver, fun, Some(ex), expr.span, method_span, msrv))
|
||||
|| check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, fun_span)
|
||||
},
|
||||
hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) if !is_nested_expr => {
|
||||
check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span)
|
||||
check_unwrap_or_default(cx, name, receiver, ex, None, expr.span, method_span, msrv)
|
||||
},
|
||||
hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
|
||||
check_or_fn_call(cx, name, method_span, receiver, arg, None, expr.span, None)
|
||||
@@ -272,6 +95,191 @@ fn check_or_fn_call<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`,
|
||||
/// `or_insert(T::new())` or `or_insert(T::default())`.
|
||||
/// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`,
|
||||
/// `or_insert_with(T::new)` or `or_insert_with(T::default)`.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn check_unwrap_or_default(
|
||||
cx: &LateContext<'_>,
|
||||
name: Symbol,
|
||||
receiver: &hir::Expr<'_>,
|
||||
fun: &hir::Expr<'_>,
|
||||
call_expr: Option<&hir::Expr<'_>>,
|
||||
span: Span,
|
||||
method_span: Span,
|
||||
msrv: Msrv,
|
||||
) -> bool {
|
||||
let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs();
|
||||
|
||||
// Check MSRV, but only for `Result::unwrap_or_default`
|
||||
if is_type_diagnostic_item(cx, receiver_ty, sym::Result) && !msrv.meets(cx, msrvs::RESULT_UNWRAP_OR_DEFAULT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !expr_type_is_certain(cx, receiver) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let is_new = |fun: &hir::Expr<'_>| {
|
||||
if let hir::ExprKind::Path(ref qpath) = fun.kind {
|
||||
let path = last_path_segment(qpath).ident.name;
|
||||
matches!(path, sym::new)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let output_type_implements_default = |fun| {
|
||||
let fun_ty = cx.typeck_results().expr_ty(fun);
|
||||
if let ty::FnDef(def_id, args) = fun_ty.kind() {
|
||||
let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output();
|
||||
cx.tcx
|
||||
.get_diagnostic_item(sym::Default)
|
||||
.is_some_and(|default_trait_id| implements_trait(cx, output_ty, default_trait_id, &[]))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let sugg = match (name, call_expr.is_some()) {
|
||||
(sym::unwrap_or, true) | (sym::unwrap_or_else, false) => sym::unwrap_or_default,
|
||||
(sym::or_insert, true) | (sym::or_insert_with, false) => sym::or_default,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| {
|
||||
cx.tcx
|
||||
.inherent_impls(adt_def.did())
|
||||
.iter()
|
||||
.flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg))
|
||||
.find_map(|assoc| {
|
||||
if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 {
|
||||
Some(assoc.def_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
let in_sugg_method_implementation = {
|
||||
matches!(
|
||||
suggested_method_def_id.as_local(),
|
||||
Some(local_def_id) if local_def_id == cx.tcx.hir_get_parent_item(receiver.hir_id).def_id
|
||||
)
|
||||
};
|
||||
if in_sugg_method_implementation {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a
|
||||
// non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases.
|
||||
if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// needs to target Default::default in particular or be *::new and have a Default impl
|
||||
// available
|
||||
if (is_new(fun) && output_type_implements_default(fun))
|
||||
|| match call_expr {
|
||||
Some(call_expr) => is_default_equivalent(cx, call_expr),
|
||||
None => is_default_equivalent_call(cx, fun, None) || closure_body_returns_empty_to_string(cx, fun),
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNWRAP_OR_DEFAULT,
|
||||
method_span.with_hi(span.hi()),
|
||||
format!("use of `{name}` to construct default value"),
|
||||
"try",
|
||||
format!("{sugg}()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `*or(foo())`.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn check_or_fn_call<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
name: Symbol,
|
||||
method_span: Span,
|
||||
self_expr: &hir::Expr<'_>,
|
||||
arg: &'tcx hir::Expr<'_>,
|
||||
// `Some` if fn has second argument
|
||||
second_arg: Option<&hir::Expr<'_>>,
|
||||
span: Span,
|
||||
// None if lambda is required
|
||||
fun_span: Option<Span>,
|
||||
) -> bool {
|
||||
// (path, fn_has_argument, methods, suffix)
|
||||
const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [
|
||||
(sym::BTreeEntry, false, &[sym::or_insert], "with"),
|
||||
(sym::HashMapEntry, false, &[sym::or_insert], "with"),
|
||||
(
|
||||
sym::Option,
|
||||
false,
|
||||
&[sym::map_or, sym::ok_or, sym::or, sym::unwrap_or],
|
||||
"else",
|
||||
),
|
||||
(sym::Option, false, &[sym::get_or_insert], "with"),
|
||||
(sym::Option, true, &[sym::and], "then"),
|
||||
(sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"),
|
||||
(sym::Result, true, &[sym::and], "then"),
|
||||
];
|
||||
|
||||
if KNOW_TYPES.iter().any(|k| k.2.contains(&name))
|
||||
&& switch_to_lazy_eval(cx, arg)
|
||||
&& !contains_return(arg)
|
||||
&& let self_ty = cx.typeck_results().expr_ty(self_expr)
|
||||
&& let Some(&(_, fn_has_arguments, _, suffix)) = KNOW_TYPES
|
||||
.iter()
|
||||
.find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0) && i.2.contains(&name))
|
||||
{
|
||||
let ctxt = span.ctxt();
|
||||
let mut app = Applicability::HasPlaceholders;
|
||||
let sugg = {
|
||||
let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
|
||||
(false, Some(fun_span)) => (fun_span, false),
|
||||
_ => (arg.span, true),
|
||||
};
|
||||
|
||||
let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0;
|
||||
let snip = if use_lambda {
|
||||
let l_arg = if fn_has_arguments { "_" } else { "" };
|
||||
format!("|{l_arg}| {snip}")
|
||||
} else {
|
||||
snip.into_owned()
|
||||
};
|
||||
|
||||
if let Some(f) = second_arg {
|
||||
let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0;
|
||||
format!("{snip}, {f}")
|
||||
} else {
|
||||
snip
|
||||
}
|
||||
};
|
||||
let span_replace_word = method_span.with_hi(span.hi());
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
OR_FUN_CALL,
|
||||
span_replace_word,
|
||||
format!("function call inside of `{name}`"),
|
||||
"try",
|
||||
format!("{name}_{suffix}({sugg})"),
|
||||
app,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind {
|
||||
let body = cx.tcx.hir_body(body);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::{SpanlessEq, higher, is_integer_const, is_trait_method};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{SpanRangeExt as _, snippet_with_applicability};
|
||||
use clippy_utils::{SpanlessEq, get_parent_expr, higher, is_integer_const, is_trait_method, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_hir::{Expr, ExprKind, Node, Pat, PatKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::RANGE_ZIP_WITH_LEN;
|
||||
|
||||
@@ -21,14 +20,93 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'
|
||||
&& let ExprKind::Path(QPath::Resolved(_, len_path)) = len_recv.kind
|
||||
&& SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
RANGE_ZIP_WITH_LEN,
|
||||
expr.span,
|
||||
"using `.zip()` with a range and `.len()`",
|
||||
"try",
|
||||
format!("{}.iter().enumerate()", snippet(cx, recv.span, "_")),
|
||||
Applicability::MachineApplicable,
|
||||
|diag| {
|
||||
// If the iterator content is consumed by a pattern with exactly two elements, swap
|
||||
// the order of those elements. Otherwise, the suggestion will be marked as
|
||||
// `Applicability::MaybeIncorrect` (because it will be), and a note will be added
|
||||
// to the diagnostic to underline the swapping of the index and the content.
|
||||
let pat = methods_pattern(cx, expr).or_else(|| for_loop_pattern(cx, expr));
|
||||
let invert_bindings = if let Some(pat) = pat
|
||||
&& pat.span.eq_ctxt(expr.span)
|
||||
&& let PatKind::Tuple([first, second], _) = pat.kind
|
||||
{
|
||||
Some((first.span, second.span))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let mut suggestions = vec![(
|
||||
expr.span,
|
||||
format!(
|
||||
"{}.iter().enumerate()",
|
||||
snippet_with_applicability(cx, recv.span, "_", &mut app)
|
||||
),
|
||||
)];
|
||||
if let Some((left, right)) = invert_bindings
|
||||
&& let Some(snip_left) = left.get_source_text(cx)
|
||||
&& let Some(snip_right) = right.get_source_text(cx)
|
||||
{
|
||||
suggestions.extend([(left, snip_right.to_string()), (right, snip_left.to_string())]);
|
||||
} else {
|
||||
app = Applicability::MaybeIncorrect;
|
||||
}
|
||||
diag.multipart_suggestion("use", suggestions, app);
|
||||
if app != Applicability::MachineApplicable {
|
||||
diag.note("the order of the element and the index will be swapped");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// If `expr` is the argument of a `for` loop, return the loop pattern.
|
||||
fn for_loop_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> {
|
||||
cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| {
|
||||
if let Node::Expr(ancestor_expr) = node
|
||||
&& let Some(for_loop) = higher::ForLoop::hir(ancestor_expr)
|
||||
&& for_loop.arg.hir_id == expr.hir_id
|
||||
{
|
||||
Some(for_loop.pat)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// If `expr` is the receiver of an `Iterator` method which consumes the iterator elements and feed
|
||||
/// them to a closure, return the pattern of the closure.
|
||||
fn methods_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Pat<'tcx>> {
|
||||
if let Some(parent_expr) = get_parent_expr(cx, expr)
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
&& let ExprKind::MethodCall(method, recv, [arg], _) = parent_expr.kind
|
||||
&& recv.hir_id == expr.hir_id
|
||||
&& matches!(
|
||||
method.ident.name,
|
||||
sym::all
|
||||
| sym::any
|
||||
| sym::filter_map
|
||||
| sym::find_map
|
||||
| sym::flat_map
|
||||
| sym::for_each
|
||||
| sym::is_partitioned
|
||||
| sym::is_sorted_by_key
|
||||
| sym::map
|
||||
| sym::map_while
|
||||
| sym::position
|
||||
| sym::rposition
|
||||
| sym::try_for_each
|
||||
)
|
||||
&& let ExprKind::Closure(closure) = arg.kind
|
||||
&& let body = cx.tcx.hir_body(closure.body)
|
||||
&& let [param] = body.params
|
||||
{
|
||||
Some(param.pat)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
||||
recv: &'tcx Expr<'_>,
|
||||
repeat_arg: &'tcx Expr<'_>,
|
||||
) {
|
||||
if ConstEvalCtxt::new(cx).eval(repeat_arg) == Some(Constant::Int(1)) {
|
||||
if ConstEvalCtxt::new(cx).eval_local(repeat_arg, expr.span.ctxt()) == Some(Constant::Int(1)) {
|
||||
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
if ty.is_str() {
|
||||
span_lint_and_sugg(
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{is_bool, sym};
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_hir::{self as hir, FnRetTy, FnSig, GenericParamKind, ImplItem, LifetimeParamKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::edition::Edition::{self, Edition2015, Edition2021};
|
||||
use rustc_span::{Symbol, kw};
|
||||
|
||||
use super::SHOULD_IMPLEMENT_TRAIT;
|
||||
use super::lib::SelfKind;
|
||||
|
||||
pub(super) fn check_impl_item<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
impl_item: &'tcx ImplItem<'_>,
|
||||
self_ty: Ty<'tcx>,
|
||||
impl_implements_trait: bool,
|
||||
first_arg_ty_opt: Option<Ty<'tcx>>,
|
||||
sig: &FnSig<'_>,
|
||||
) {
|
||||
// if this impl block implements a trait, lint in trait definition instead
|
||||
if !impl_implements_trait && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)
|
||||
// check missing trait implementations
|
||||
&& let Some(method_config) = TRAIT_METHODS.iter().find(|case| case.method_name == impl_item.ident.name)
|
||||
&& sig.decl.inputs.len() == method_config.param_count
|
||||
&& method_config.output_type.matches(&sig.decl.output)
|
||||
// in case there is no first arg, since we already have checked the number of arguments
|
||||
// it's should be always true
|
||||
&& first_arg_ty_opt
|
||||
.is_none_or(|first_arg_ty| method_config.self_kind.matches(cx, self_ty, first_arg_ty))
|
||||
&& sig.header.is_safe()
|
||||
&& !sig.header.is_const()
|
||||
&& !sig.header.is_async()
|
||||
&& sig.header.abi == ExternAbi::Rust
|
||||
&& method_config.lifetime_param_cond(impl_item)
|
||||
&& method_config.in_prelude_since <= cx.tcx.sess.edition()
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SHOULD_IMPLEMENT_TRAIT,
|
||||
impl_item.span,
|
||||
format!(
|
||||
"method `{}` can be confused for the standard trait method `{}::{}`",
|
||||
method_config.method_name, method_config.trait_name, method_config.method_name
|
||||
),
|
||||
None,
|
||||
format!(
|
||||
"consider implementing the trait `{}` or choosing a less ambiguous method name",
|
||||
method_config.trait_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct ShouldImplTraitCase {
|
||||
trait_name: &'static str,
|
||||
method_name: Symbol,
|
||||
param_count: usize,
|
||||
// implicit self kind expected (none, self, &self, ...)
|
||||
self_kind: SelfKind,
|
||||
// checks against the output type
|
||||
output_type: OutType,
|
||||
// certain methods with explicit lifetimes can't implement the equivalent trait method
|
||||
lint_explicit_lifetime: bool,
|
||||
in_prelude_since: Edition,
|
||||
}
|
||||
|
||||
impl ShouldImplTraitCase {
|
||||
const fn new(
|
||||
trait_name: &'static str,
|
||||
method_name: Symbol,
|
||||
param_count: usize,
|
||||
self_kind: SelfKind,
|
||||
output_type: OutType,
|
||||
lint_explicit_lifetime: bool,
|
||||
in_prelude_since: Edition,
|
||||
) -> ShouldImplTraitCase {
|
||||
ShouldImplTraitCase {
|
||||
trait_name,
|
||||
method_name,
|
||||
param_count,
|
||||
self_kind,
|
||||
output_type,
|
||||
lint_explicit_lifetime,
|
||||
in_prelude_since,
|
||||
}
|
||||
}
|
||||
|
||||
fn lifetime_param_cond(&self, impl_item: &ImplItem<'_>) -> bool {
|
||||
self.lint_explicit_lifetime
|
||||
|| !impl_item.generics.params.iter().any(|p| {
|
||||
matches!(
|
||||
p.kind,
|
||||
GenericParamKind::Lifetime {
|
||||
kind: LifetimeParamKind::Explicit
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
|
||||
ShouldImplTraitCase::new("std::ops::Add", sym::add, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::convert::AsMut", sym::as_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::convert::AsRef", sym::as_ref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::BitAnd", sym::bitand, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::BitOr", sym::bitor, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::BitXor", sym::bitxor, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::borrow::Borrow", sym::borrow, 1, SelfKind::Ref, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::borrow::BorrowMut", sym::borrow_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::clone::Clone", sym::clone, 1, SelfKind::Ref, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::cmp::Ord", sym::cmp, 2, SelfKind::Ref, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::default::Default", kw::Default, 0, SelfKind::No, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Deref", sym::deref, 1, SelfKind::Ref, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::DerefMut", sym::deref_mut, 1, SelfKind::RefMut, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Div", sym::div, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Drop", sym::drop, 1, SelfKind::RefMut, OutType::Unit, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::cmp::PartialEq", sym::eq, 2, SelfKind::Ref, OutType::Bool, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::iter::FromIterator", sym::from_iter, 1, SelfKind::No, OutType::Any, true, Edition2021),
|
||||
ShouldImplTraitCase::new("std::str::FromStr", sym::from_str, 1, SelfKind::No, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::hash::Hash", sym::hash, 2, SelfKind::Ref, OutType::Unit, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Index", sym::index, 2, SelfKind::Ref, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::IndexMut", sym::index_mut, 2, SelfKind::RefMut, OutType::Ref, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::iter::IntoIterator", sym::into_iter, 1, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Mul", sym::mul, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Neg", sym::neg, 1, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::iter::Iterator", sym::next, 1, SelfKind::RefMut, OutType::Any, false, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Not", sym::not, 1, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Rem", sym::rem, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Shl", sym::shl, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Shr", sym::shr, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
ShouldImplTraitCase::new("std::ops::Sub", sym::sub, 2, SelfKind::Value, OutType::Any, true, Edition2015),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum OutType {
|
||||
Unit,
|
||||
Bool,
|
||||
Any,
|
||||
Ref,
|
||||
}
|
||||
|
||||
impl OutType {
|
||||
fn matches(self, ty: &FnRetTy<'_>) -> bool {
|
||||
let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
|
||||
match (self, ty) {
|
||||
(Self::Unit, &FnRetTy::DefaultReturn(_)) => true,
|
||||
(Self::Unit, &FnRetTy::Return(ty)) if is_unit(ty) => true,
|
||||
(Self::Bool, &FnRetTy::Return(ty)) if is_bool(ty) => true,
|
||||
(Self::Any, &FnRetTy::Return(ty)) if !is_unit(ty) => true,
|
||||
(Self::Ref, &FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Ref(_, _)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,80 @@
|
||||
use crate::methods::{single_char_insert_string, single_char_push_string};
|
||||
use rustc_hir as hir;
|
||||
use super::SINGLE_CHAR_ADD_STR;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
|
||||
use rustc_ast::BorrowKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
match cx.tcx.get_diagnostic_name(fn_def_id) {
|
||||
Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args),
|
||||
Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args),
|
||||
_ => {},
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (short_name, arg, extra) = match cx.tcx.get_diagnostic_name(fn_def_id) {
|
||||
Some(sym::string_insert_str) => (
|
||||
"insert",
|
||||
&args[1],
|
||||
Some(|applicability| {
|
||||
format!(
|
||||
"{}, ",
|
||||
snippet_with_applicability(cx, args[0].span, "..", applicability)
|
||||
)
|
||||
}),
|
||||
),
|
||||
Some(sym::string_push_str) => ("push", &args[0], None),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if let Some(extension_string) = str_literal_to_char_literal(cx, arg, &mut applicability, false) {
|
||||
let base_string_snippet =
|
||||
snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
expr.span,
|
||||
format!("calling `{short_name}_str()` using a single-character string literal"),
|
||||
format!("consider using `{short_name}` with a character literal"),
|
||||
format!(
|
||||
"{base_string_snippet}.{short_name}({}{extension_string})",
|
||||
extra.map_or(String::new(), |f| f(&mut applicability))
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
} else if let ExprKind::AddrOf(BorrowKind::Ref, _, inner) = arg.kind
|
||||
&& let ExprKind::MethodCall(path_segment, method_arg, [], _) = inner.kind
|
||||
&& path_segment.ident.name == sym::to_string
|
||||
&& (is_ref_char(cx, method_arg) || is_char(cx, method_arg))
|
||||
{
|
||||
let base_string_snippet =
|
||||
snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability);
|
||||
let extension_string = match (
|
||||
snippet_with_applicability(cx, method_arg.span.source_callsite(), "_", &mut applicability),
|
||||
is_ref_char(cx, method_arg),
|
||||
) {
|
||||
(snippet, false) => snippet,
|
||||
(snippet, true) => format!("*{snippet}").into(),
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
expr.span,
|
||||
format!("calling `{short_name}_str()` using a single-character converted to string"),
|
||||
format!("consider using `{short_name}` without `to_string()`"),
|
||||
format!(
|
||||
"{base_string_snippet}.{short_name}({}{extension_string})",
|
||||
extra.map_or(String::new(), |f| f(&mut applicability))
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ref_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
matches!(cx.typeck_results().expr_ty(expr).kind(), ty::Ref(_, ty, _) if ty.is_char())
|
||||
}
|
||||
|
||||
fn is_char(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
cx.typeck_results().expr_ty(expr).is_char()
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
|
||||
use rustc_ast::BorrowKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::SINGLE_CHAR_ADD_STR;
|
||||
|
||||
/// lint for length-1 `str`s as argument for `insert_str`
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
if let Some(extension_string) = str_literal_to_char_literal(cx, &args[1], &mut applicability, false) {
|
||||
let base_string_snippet =
|
||||
snippet_with_applicability(cx, receiver.span.source_callsite(), "_", &mut applicability);
|
||||
let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
|
||||
let sugg = format!("{base_string_snippet}.insert({pos_arg}, {extension_string})");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
expr.span,
|
||||
"calling `insert_str()` using a single-character string literal",
|
||||
"consider using `insert` with a character literal",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[1].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))
|
||||
{
|
||||
let base_string_snippet =
|
||||
snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
|
||||
let extension_string =
|
||||
snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability);
|
||||
let pos_arg = snippet_with_applicability(cx, args[0].span, "..", &mut applicability);
|
||||
let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" };
|
||||
|
||||
let sugg = format!("{base_string_snippet}.insert({pos_arg}, {deref_string}{extension_string})");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
expr.span,
|
||||
"calling `insert_str()` using a single-character converted to string",
|
||||
"consider using `insert` without `to_string()`",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
if cx.typeck_results().expr_ty(expr).is_ref()
|
||||
&& let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind()
|
||||
&& ty.is_char()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
cx.typeck_results().expr_ty(expr).is_char()
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{snippet_with_applicability, str_literal_to_char_literal};
|
||||
use rustc_ast::BorrowKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::SINGLE_CHAR_ADD_STR;
|
||||
|
||||
/// lint for length-1 `str`s as argument for `push_str`
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
if let Some(extension_string) = str_literal_to_char_literal(cx, &args[0], &mut applicability, false) {
|
||||
let base_string_snippet =
|
||||
snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
|
||||
let sugg = format!("{base_string_snippet}.push({extension_string})");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
expr.span,
|
||||
"calling `push_str()` using a single-character string literal",
|
||||
"consider using `push` with a character literal",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, arg) = &args[0].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))
|
||||
{
|
||||
let base_string_snippet =
|
||||
snippet_with_applicability(cx, receiver.span.source_callsite(), "..", &mut applicability);
|
||||
let extension_string =
|
||||
snippet_with_applicability(cx, method_arg.span.source_callsite(), "..", &mut applicability);
|
||||
let deref_string = if is_ref_char(cx, method_arg) { "*" } else { "" };
|
||||
|
||||
let sugg = format!("{base_string_snippet}.push({deref_string}{extension_string})");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_ADD_STR,
|
||||
expr.span,
|
||||
"calling `push_str()` using a single-character converted to string",
|
||||
"consider using `push` without `to_string()`",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ref_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
if cx.typeck_results().expr_ty(expr).is_ref()
|
||||
&& let rustc_middle::ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(expr).kind()
|
||||
&& ty.is_char()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn is_char(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
cx.typeck_results().expr_ty(expr).is_char()
|
||||
}
|
||||
@@ -304,7 +304,7 @@ fn parse_iter_usage<'tcx>(
|
||||
};
|
||||
},
|
||||
(sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_assoc(did) == Some(iter_id) => {
|
||||
if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval(idx_expr) {
|
||||
if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval_local(idx_expr, ctxt) {
|
||||
let span = if name.ident.as_str() == "nth" {
|
||||
e.span
|
||||
} else if let Some((_, Node::Expr(next_expr))) = iter.next()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::UNNECESSARY_MIN_OR_MAX;
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant, ConstantSource, FullInt};
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
|
||||
@@ -25,8 +25,9 @@ pub(super) fn check<'tcx>(
|
||||
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(id)
|
||||
&& matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max)
|
||||
{
|
||||
if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv)
|
||||
&& let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg)
|
||||
let ctxt = expr.span.ctxt();
|
||||
if let Some(left) = ecx.eval_local(recv, ctxt)
|
||||
&& let Some(right) = ecx.eval_local(arg, ctxt)
|
||||
{
|
||||
let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else {
|
||||
return;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::methods::SelfKind;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use itertools::Itertools;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::fmt;
|
||||
|
||||
use super::WRONG_SELF_CONVENTION;
|
||||
use super::lib::SelfKind;
|
||||
|
||||
#[rustfmt::skip]
|
||||
const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
|
||||
@@ -61,20 +62,20 @@ fn check<'tcx>(
|
||||
impl fmt::Display for Convention {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Self::Eq(this) => format!("`{this}`").fmt(f),
|
||||
Self::StartsWith(this) => format!("`{this}*`").fmt(f),
|
||||
Self::EndsWith(this) => format!("`*{this}`").fmt(f),
|
||||
Self::NotEndsWith(this) => format!("`~{this}`").fmt(f),
|
||||
Self::Eq(this) => write!(f, "`{this}`"),
|
||||
Self::StartsWith(this) => write!(f, "`{this}*`"),
|
||||
Self::EndsWith(this) => write!(f, "`*{this}`"),
|
||||
Self::NotEndsWith(this) => write!(f, "`~{this}`"),
|
||||
Self::IsSelfTypeCopy(is_true) => {
|
||||
format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f)
|
||||
write!(f, "`self` type is{} `Copy`", if is_true { "" } else { " not" })
|
||||
},
|
||||
Self::ImplementsTrait(is_true) => {
|
||||
let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") };
|
||||
format!("method{negation} implement{s_suffix} a trait").fmt(f)
|
||||
write!(f, "method{negation} implement{s_suffix} a trait")
|
||||
},
|
||||
Self::IsTraitItem(is_true) => {
|
||||
let suffix = if is_true { " is" } else { " is not" };
|
||||
format!("method{suffix} a trait item").fmt(f)
|
||||
write!(f, "method{suffix} a trait item")
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -115,18 +116,9 @@ pub(super) fn check<'tcx>(
|
||||
|
||||
let s = conventions
|
||||
.iter()
|
||||
.filter_map(|conv| {
|
||||
if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))
|
||||
|| matches!(conv, Convention::ImplementsTrait(_))
|
||||
|| matches!(conv, Convention::IsTraitItem(_))
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(conv.to_string())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" and ");
|
||||
.filter(|conv| !(cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_))))
|
||||
.filter(|conv| !matches!(conv, Convention::ImplementsTrait(_) | Convention::IsTraitItem(_)))
|
||||
.format(" and ");
|
||||
|
||||
format!("methods with the following characteristics: ({s})")
|
||||
} else {
|
||||
@@ -140,11 +132,7 @@ pub(super) fn check<'tcx>(
|
||||
first_arg_span,
|
||||
format!(
|
||||
"{suggestion} usually take {}",
|
||||
&self_kinds
|
||||
.iter()
|
||||
.map(|k| k.description())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or ")
|
||||
self_kinds.iter().map(|k| k.description()).format(" or ")
|
||||
),
|
||||
None,
|
||||
"consider choosing a less ambiguous name",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::SyntaxContext;
|
||||
use std::cmp::Ordering::{Equal, Greater, Less};
|
||||
|
||||
declare_clippy_lint! {
|
||||
@@ -60,7 +61,7 @@ enum MinMax {
|
||||
Max,
|
||||
}
|
||||
|
||||
fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> {
|
||||
fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
|
||||
match expr.kind {
|
||||
ExprKind::Call(path, args) => {
|
||||
if let ExprKind::Path(ref qpath) = path.kind {
|
||||
@@ -68,8 +69,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
|
||||
.qpath_res(qpath, path.hir_id)
|
||||
.opt_def_id()
|
||||
.and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) {
|
||||
Some(sym::cmp_min) => fetch_const(cx, None, args, MinMax::Min),
|
||||
Some(sym::cmp_max) => fetch_const(cx, None, args, MinMax::Max),
|
||||
Some(sym::cmp_min) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Min),
|
||||
Some(sym::cmp_max) => fetch_const(cx, expr.span.ctxt(), None, args, MinMax::Max),
|
||||
_ => None,
|
||||
})
|
||||
} else {
|
||||
@@ -79,8 +80,8 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
|
||||
ExprKind::MethodCall(path, receiver, args @ [_], _) => {
|
||||
if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) {
|
||||
match path.ident.name {
|
||||
sym::max => fetch_const(cx, Some(receiver), args, MinMax::Max),
|
||||
sym::min => fetch_const(cx, Some(receiver), args, MinMax::Min),
|
||||
sym::max => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Max),
|
||||
sym::min => fetch_const(cx, expr.span.ctxt(), Some(receiver), args, MinMax::Min),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
@@ -91,12 +92,13 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_const<'a, 'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
fn fetch_const<'a>(
|
||||
cx: &LateContext<'_>,
|
||||
ctxt: SyntaxContext,
|
||||
receiver: Option<&'a Expr<'a>>,
|
||||
args: &'a [Expr<'a>],
|
||||
m: MinMax,
|
||||
) -> Option<(MinMax, Constant<'tcx>, &'a Expr<'a>)> {
|
||||
) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
|
||||
let mut args = receiver.into_iter().chain(args);
|
||||
let first_arg = args.next()?;
|
||||
let second_arg = args.next()?;
|
||||
@@ -104,7 +106,7 @@ fn fetch_const<'a, 'tcx>(
|
||||
return None;
|
||||
}
|
||||
let ecx = ConstEvalCtxt::new(cx);
|
||||
match (ecx.eval_simple(first_arg), ecx.eval_simple(second_arg)) {
|
||||
match (ecx.eval_local(first_arg, ctxt), ecx.eval_local(second_arg, ctxt)) {
|
||||
(Some(c), None) => Some((m, c, second_arg)),
|
||||
(None, Some(c)) => Some((m, c, first_arg)),
|
||||
// otherwise ignore
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_ast::ast;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
|
||||
use rustc_ast::ast::{self, Inline, ItemKind, ModKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::LOCAL_CRATE;
|
||||
use rustc_span::{FileName, SourceFile, Span, SyntaxContext};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Component, Path};
|
||||
use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -60,107 +59,97 @@
|
||||
/// mod.rs
|
||||
/// lib.rs
|
||||
/// ```
|
||||
|
||||
#[clippy::version = "1.57.0"]
|
||||
pub SELF_NAMED_MODULE_FILES,
|
||||
restriction,
|
||||
"checks that module layout is consistent"
|
||||
}
|
||||
|
||||
pub struct ModStyle;
|
||||
|
||||
impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
|
||||
|
||||
pub struct ModState {
|
||||
contains_external: bool,
|
||||
has_path_attr: bool,
|
||||
mod_file: Arc<SourceFile>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ModStyle {
|
||||
working_dir: Option<PathBuf>,
|
||||
module_stack: Vec<ModState>,
|
||||
}
|
||||
|
||||
impl EarlyLintPass for ModStyle {
|
||||
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
|
||||
self.working_dir = cx.sess().opts.working_dir.local_path().map(Path::to_path_buf);
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
|
||||
if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow
|
||||
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, mod_spans, ..)) = &item.kind {
|
||||
let has_path_attr = item.attrs.iter().any(|attr| attr.has_name(sym::path));
|
||||
if !has_path_attr && let Some(current) = self.module_stack.last_mut() {
|
||||
current.contains_external = true;
|
||||
}
|
||||
let mod_file = cx.sess().source_map().lookup_source_file(mod_spans.inner_span.lo());
|
||||
self.module_stack.push(ModState {
|
||||
contains_external: false,
|
||||
has_path_attr,
|
||||
mod_file,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item_post(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
|
||||
if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow
|
||||
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let files = cx.sess().source_map().files();
|
||||
|
||||
let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
|
||||
// `[path, to]` but not foo
|
||||
let mut folder_segments = FxIndexSet::default();
|
||||
// `mod_folders` is all the unique folder names that contain a mod.rs file
|
||||
let mut mod_folders = FxHashSet::default();
|
||||
// `file_map` maps file names to the full path including the file name
|
||||
// `{ foo => path/to/foo.rs, .. }
|
||||
let mut file_map = FxHashMap::default();
|
||||
for file in files.iter() {
|
||||
if let FileName::Real(name) = &file.name
|
||||
&& let Some(lp) = name.local_path()
|
||||
&& file.cnum == LOCAL_CRATE
|
||||
{
|
||||
// [#8887](https://github.com/rust-lang/rust-clippy/issues/8887)
|
||||
// Only check files in the current crate.
|
||||
// Fix false positive that crate dependency in workspace sub directory
|
||||
// is checked unintentionally.
|
||||
let path = if lp.is_relative() {
|
||||
lp
|
||||
} else if let Ok(relative) = lp.strip_prefix(trim_to_src) {
|
||||
relative
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(stem) = path.file_stem() {
|
||||
file_map.insert(stem, (file, path));
|
||||
}
|
||||
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
|
||||
check_self_named_mod_exists(cx, path, file);
|
||||
}
|
||||
}
|
||||
|
||||
for folder in &folder_segments {
|
||||
if !mod_folders.contains(folder)
|
||||
&& let Some((file, path)) = file_map.get(folder)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
SELF_NAMED_MODULE_FILES,
|
||||
Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
|
||||
format!("`mod.rs` files are required, found `{}`", path.display()),
|
||||
|diag| {
|
||||
let mut correct = path.to_path_buf();
|
||||
correct.pop();
|
||||
correct.push(folder);
|
||||
correct.push("mod.rs");
|
||||
diag.help(format!("move `{}` to `{}`", path.display(), correct.display(),));
|
||||
},
|
||||
);
|
||||
if let ItemKind::Mod(.., ModKind::Loaded(_, Inline::No { .. }, ..)) = &item.kind
|
||||
&& let Some(current) = self.module_stack.pop()
|
||||
&& !current.has_path_attr
|
||||
{
|
||||
let Some(path) = self
|
||||
.working_dir
|
||||
.as_ref()
|
||||
.and_then(|src| try_trim_file_path_prefix(¤t.mod_file, src))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if current.contains_external {
|
||||
check_self_named_module(cx, path, ¤t.mod_file);
|
||||
}
|
||||
check_mod_module(cx, path, ¤t.mod_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For each `path` we add each folder component to `folder_segments` and if the file name
|
||||
/// is `mod.rs` we add it's parent folder to `mod_folders`.
|
||||
fn process_paths_for_mod_files<'a>(
|
||||
path: &'a Path,
|
||||
folder_segments: &mut FxIndexSet<&'a OsStr>,
|
||||
mod_folders: &mut FxHashSet<&'a OsStr>,
|
||||
) {
|
||||
let mut comp = path.components().rev().peekable();
|
||||
let _: Option<_> = comp.next();
|
||||
if path.ends_with("mod.rs") {
|
||||
mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
|
||||
fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
|
||||
if !path.ends_with("mod.rs") {
|
||||
let mut mod_folder = path.with_extension("");
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
SELF_NAMED_MODULE_FILES,
|
||||
Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
|
||||
format!("`mod.rs` files are required, found `{}`", path.display()),
|
||||
|diag| {
|
||||
mod_folder.push("mod.rs");
|
||||
diag.help(format!("move `{}` to `{}`", path.display(), mod_folder.display()));
|
||||
},
|
||||
);
|
||||
}
|
||||
let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
|
||||
folder_segments.extend(folders);
|
||||
}
|
||||
|
||||
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
|
||||
/// We should not emit a lint for test modules in the presence of `mod.rs`.
|
||||
/// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test)
|
||||
/// for code-sharing between tests.
|
||||
fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
|
||||
fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
|
||||
if path.ends_with("mod.rs") && !path.starts_with("tests") {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
@@ -177,3 +166,17 @@ fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &Source
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_trim_file_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> {
|
||||
if let FileName::Real(name) = &file.name
|
||||
&& let Some(mut path) = name.local_path()
|
||||
&& file.cnum == LOCAL_CRATE
|
||||
{
|
||||
if !path.is_relative() {
|
||||
path = path.strip_prefix(prefix).ok()?;
|
||||
}
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
+111
-24
@@ -1,31 +1,51 @@
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_hir};
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::higher;
|
||||
use rustc_hir::{self as hir, AmbigArg, intravisit};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, AmbigArg, BorrowKind, Expr, ExprKind, HirId, Mutability, TyKind, intravisit};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_session::impl_lint_pass;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for instances of `mut mut` references.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Multiple `mut`s don't add anything meaningful to the
|
||||
/// source. This is either a copy'n'paste error, or it shows a fundamental
|
||||
/// misunderstanding of references.
|
||||
/// This is usually just a typo or a misunderstanding of how references work.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # let mut y = 1;
|
||||
/// let x = &mut &mut y;
|
||||
/// let x = &mut &mut 1;
|
||||
///
|
||||
/// let mut x = &mut 1;
|
||||
/// let y = &mut x;
|
||||
///
|
||||
/// fn foo(x: &mut &mut u32) {}
|
||||
/// ```
|
||||
/// Use instead
|
||||
/// ```no_run
|
||||
/// let x = &mut 1;
|
||||
///
|
||||
/// let mut x = &mut 1;
|
||||
/// let y = &mut *x; // reborrow
|
||||
///
|
||||
/// fn foo(x: &mut u32) {}
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub MUT_MUT,
|
||||
pedantic,
|
||||
"usage of double-mut refs, e.g., `&mut &mut ...`"
|
||||
"usage of double mut-refs, e.g., `&mut &mut ...`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(MutMut => [MUT_MUT]);
|
||||
impl_lint_pass!(MutMut => [MUT_MUT]);
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MutMut {
|
||||
seen_tys: FxHashSet<HirId>,
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for MutMut {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
|
||||
@@ -33,17 +53,48 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
|
||||
}
|
||||
|
||||
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_, AmbigArg>) {
|
||||
if let hir::TyKind::Ref(_, mty) = ty.kind
|
||||
&& mty.mutbl == hir::Mutability::Mut
|
||||
&& let hir::TyKind::Ref(_, mty) = mty.ty.kind
|
||||
&& mty.mutbl == hir::Mutability::Mut
|
||||
if let TyKind::Ref(_, mty) = ty.kind
|
||||
&& mty.mutbl == Mutability::Mut
|
||||
&& let TyKind::Ref(_, mty2) = mty.ty.kind
|
||||
&& mty2.mutbl == Mutability::Mut
|
||||
&& !ty.span.in_external_macro(cx.sess().source_map())
|
||||
{
|
||||
span_lint(
|
||||
if self.seen_tys.contains(&ty.hir_id) {
|
||||
// we have 2+ `&mut`s, e.g., `&mut &mut &mut x`
|
||||
// and we have already flagged on the outermost `&mut &mut (&mut x)`,
|
||||
// so don't flag the inner `&mut &mut (x)`
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off
|
||||
// all extra ones at once
|
||||
let (mut t, mut t2) = (mty.ty, mty2.ty);
|
||||
let mut many_muts = false;
|
||||
loop {
|
||||
// this should allow us to remember all the nested types, so that the `contains`
|
||||
// above fails faster
|
||||
self.seen_tys.insert(t.hir_id);
|
||||
if let TyKind::Ref(_, next) = t2.kind
|
||||
&& next.mutbl == Mutability::Mut
|
||||
{
|
||||
(t, t2) = (t2, next.ty);
|
||||
many_muts = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let sugg = snippet_with_applicability(cx.sess(), t.span, "..", &mut applicability);
|
||||
let suffix = if many_muts { "s" } else { "" };
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MUT_MUT,
|
||||
ty.span,
|
||||
"generally you want to avoid `&mut &mut _` if possible",
|
||||
"a type of form `&mut &mut _`",
|
||||
format!("remove the extra `&mut`{suffix}"),
|
||||
sugg.to_string(),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +105,7 @@ pub struct MutVisitor<'a, 'tcx> {
|
||||
}
|
||||
|
||||
impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if expr.span.in_external_macro(self.cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
@@ -68,24 +119,60 @@ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
// Let's ignore the generated code.
|
||||
intravisit::walk_expr(self, arg);
|
||||
intravisit::walk_expr(self, body);
|
||||
} else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind {
|
||||
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind {
|
||||
span_lint_hir(
|
||||
} else if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e) = expr.kind {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e2) = e.kind {
|
||||
if !expr.span.eq_ctxt(e.span) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is an even longer chain, like `&mut &mut &mut x`, suggest peeling off
|
||||
// all extra ones at once
|
||||
let (mut e, mut e2) = (e, e2);
|
||||
let mut many_muts = false;
|
||||
loop {
|
||||
if !e.span.eq_ctxt(e2.span) {
|
||||
return;
|
||||
}
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, next) = e2.kind {
|
||||
(e, e2) = (e2, next);
|
||||
many_muts = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability);
|
||||
let suffix = if many_muts { "s" } else { "" };
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
MUT_MUT,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
"generally you want to avoid `&mut &mut _` if possible",
|
||||
"an expression of form `&mut &mut _`",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
format!("remove the extra `&mut`{suffix}"),
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind()
|
||||
} else if let ty::Ref(_, ty, Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind()
|
||||
&& ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env())
|
||||
{
|
||||
span_lint_hir(
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let sugg = Sugg::hir_with_applicability(self.cx, e, "..", &mut applicability).mut_addr_deref();
|
||||
span_lint_hir_and_then(
|
||||
self.cx,
|
||||
MUT_MUT,
|
||||
expr.hir_id,
|
||||
expr.span,
|
||||
"this expression mutably borrows a mutable reference. Consider reborrowing",
|
||||
"this expression mutably borrows a mutable reference",
|
||||
|diag| {
|
||||
diag.span_suggestion(expr.span, "reborrow instead", sugg, applicability);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::ptr::get_spans;
|
||||
use clippy_utils::source::{SpanRangeExt, snippet};
|
||||
use clippy_utils::ty::{
|
||||
implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
|
||||
};
|
||||
use clippy_utils::{is_self, peel_hir_ty_options};
|
||||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use clippy_utils::{is_self, path_to_local_id, peel_hir_ty_options, strip_pat_refs, sym};
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
Attribute, BindingMode, Body, FnDecl, GenericArg, HirId, HirIdSet, Impl, ItemKind, LangItem, Mutability, Node,
|
||||
PatKind, QPath, TyKind,
|
||||
Attribute, BindingMode, Body, ExprKind, FnDecl, GenericArg, HirId, HirIdSet, Impl, ItemKind, LangItem, Mutability,
|
||||
Node, PatKind, QPath, TyKind,
|
||||
};
|
||||
use rustc_hir_typeck::expr_use_visitor as euv;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@@ -19,10 +19,13 @@
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{Span, sym};
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_trait_selection::traits;
|
||||
use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for functions taking arguments by value, but not
|
||||
@@ -217,7 +220,7 @@ fn check_fn(
|
||||
}
|
||||
|
||||
if is_type_diagnostic_item(cx, ty, sym::Vec)
|
||||
&& let Some(clone_spans) = get_spans(cx, Some(body.id()), idx, &[(sym::clone, ".to_owned()")])
|
||||
&& let Some(clone_spans) = get_spans(cx, body, idx, &[(sym::clone, ".to_owned()")])
|
||||
&& let TyKind::Path(QPath::Resolved(_, path)) = input.kind
|
||||
&& let Some(elem_ty) = path
|
||||
.segments
|
||||
@@ -260,12 +263,8 @@ fn check_fn(
|
||||
}
|
||||
|
||||
if is_type_lang_item(cx, ty, LangItem::String)
|
||||
&& let Some(clone_spans) = get_spans(
|
||||
cx,
|
||||
Some(body.id()),
|
||||
idx,
|
||||
&[(sym::clone, ".to_string()"), (sym::as_str, "")],
|
||||
)
|
||||
&& let Some(clone_spans) =
|
||||
get_spans(cx, body, idx, &[(sym::clone, ".to_string()"), (sym::as_str, "")])
|
||||
{
|
||||
diag.span_suggestion(
|
||||
input.span,
|
||||
@@ -340,3 +339,43 @@ fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {}
|
||||
|
||||
fn fake_read(&mut self, _: &rustc_hir_typeck::expr_use_visitor::PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
|
||||
}
|
||||
|
||||
fn get_spans<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
body: &'tcx Body<'_>,
|
||||
idx: usize,
|
||||
replacements: &[(Symbol, &'static str)],
|
||||
) -> Option<Vec<(Span, Cow<'static, str>)>> {
|
||||
if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind {
|
||||
extract_clone_suggestions(cx, binding_id, replacements, body)
|
||||
} else {
|
||||
Some(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_clone_suggestions<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
id: HirId,
|
||||
replace: &[(Symbol, &'static str)],
|
||||
body: &'tcx Body<'_>,
|
||||
) -> Option<Vec<(Span, Cow<'static, str>)>> {
|
||||
let mut spans = Vec::new();
|
||||
for_each_expr_without_closures(body, |e| {
|
||||
if let ExprKind::MethodCall(seg, recv, [], _) = e.kind
|
||||
&& path_to_local_id(recv, id)
|
||||
{
|
||||
if seg.ident.name == sym::capacity {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
for &(fn_name, suffix) in replace {
|
||||
if seg.ident.name == fn_name {
|
||||
spans.push((e.span, snippet(cx, recv.span, "_") + suffix));
|
||||
return ControlFlow::Continue(Descend::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
})
|
||||
.is_none()
|
||||
.then_some(spans)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::return_ty;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::DiagExt;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
@@ -58,116 +58,132 @@ pub struct NewWithoutDefault {
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
|
||||
if let hir::ItemKind::Impl(hir::Impl {
|
||||
let hir::ItemKind::Impl(hir::Impl {
|
||||
of_trait: None,
|
||||
generics,
|
||||
self_ty: impl_self_ty,
|
||||
..
|
||||
}) = item.kind
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
for assoc_item in cx
|
||||
.tcx
|
||||
.associated_items(item.owner_id.def_id)
|
||||
.filter_by_name_unhygienic(sym::new)
|
||||
{
|
||||
for assoc_item in cx
|
||||
.tcx
|
||||
.associated_items(item.owner_id.def_id)
|
||||
.filter_by_name_unhygienic(sym::new)
|
||||
if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind
|
||||
&& let assoc_item_hir_id = cx.tcx.local_def_id_to_hir_id(assoc_item.def_id.expect_local())
|
||||
&& let impl_item = cx.tcx.hir_node(assoc_item_hir_id).expect_impl_item()
|
||||
&& !impl_item.span.in_external_macro(cx.sess().source_map())
|
||||
&& let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind
|
||||
&& let id = impl_item.owner_id
|
||||
// can't be implemented for unsafe new
|
||||
&& !sig.header.is_unsafe()
|
||||
// shouldn't be implemented when it is hidden in docs
|
||||
&& !cx.tcx.is_doc_hidden(impl_item.owner_id.def_id)
|
||||
// when the result of `new()` depends on a parameter we should not require
|
||||
// an impl of `Default`
|
||||
&& impl_item.generics.params.is_empty()
|
||||
&& sig.decl.inputs.is_empty()
|
||||
&& cx.effective_visibilities.is_exported(impl_item.owner_id.def_id)
|
||||
&& let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& self_ty == return_ty(cx, impl_item.owner_id)
|
||||
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
|
||||
{
|
||||
if let AssocKind::Fn { has_self: false, .. } = assoc_item.kind {
|
||||
let impl_item = cx
|
||||
.tcx
|
||||
.hir_node_by_def_id(assoc_item.def_id.expect_local())
|
||||
.expect_impl_item();
|
||||
if impl_item.span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind {
|
||||
let id = impl_item.owner_id;
|
||||
if sig.header.is_unsafe() {
|
||||
// can't be implemented for unsafe new
|
||||
return;
|
||||
}
|
||||
if cx.tcx.is_doc_hidden(impl_item.owner_id.def_id) {
|
||||
// shouldn't be implemented when it is hidden in docs
|
||||
return;
|
||||
}
|
||||
if !impl_item.generics.params.is_empty() {
|
||||
// when the result of `new()` depends on a parameter we should not require
|
||||
// an impl of `Default`
|
||||
return;
|
||||
}
|
||||
if sig.decl.inputs.is_empty()
|
||||
&& cx.effective_visibilities.is_reachable(impl_item.owner_id.def_id)
|
||||
&& let self_ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& self_ty == return_ty(cx, impl_item.owner_id)
|
||||
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
|
||||
if self.impling_types.is_none() {
|
||||
let mut impls = HirIdSet::default();
|
||||
for &d in cx.tcx.local_trait_impls(default_trait_id) {
|
||||
let ty = cx.tcx.type_of(d).instantiate_identity();
|
||||
if let Some(ty_def) = ty.ty_adt_def()
|
||||
&& let Some(local_def_id) = ty_def.did().as_local()
|
||||
{
|
||||
if self.impling_types.is_none() {
|
||||
let mut impls = HirIdSet::default();
|
||||
for &d in cx.tcx.local_trait_impls(default_trait_id) {
|
||||
let ty = cx.tcx.type_of(d).instantiate_identity();
|
||||
if let Some(ty_def) = ty.ty_adt_def()
|
||||
&& let Some(local_def_id) = ty_def.did().as_local()
|
||||
{
|
||||
impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id));
|
||||
}
|
||||
}
|
||||
self.impling_types = Some(impls);
|
||||
}
|
||||
|
||||
// Check if a Default implementation exists for the Self type, regardless of
|
||||
// generics
|
||||
if let Some(ref impling_types) = self.impling_types
|
||||
&& let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& let Some(self_def) = self_def.ty_adt_def()
|
||||
&& let Some(self_local_did) = self_def.did().as_local()
|
||||
&& let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did)
|
||||
&& impling_types.contains(&self_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let generics_sugg = snippet(cx, generics.span, "");
|
||||
let where_clause_sugg = if generics.has_where_clause_predicates {
|
||||
format!("\n{}\n", snippet(cx, generics.where_clause_span, ""))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let self_ty_fmt = self_ty.to_string();
|
||||
let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt);
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEW_WITHOUT_DEFAULT,
|
||||
id.into(),
|
||||
impl_item.span,
|
||||
format!("you should consider adding a `Default` implementation for `{self_type_snip}`"),
|
||||
|diag| {
|
||||
diag.suggest_prepend_item(
|
||||
cx,
|
||||
item.span,
|
||||
"try adding this",
|
||||
&create_new_without_default_suggest_msg(
|
||||
&self_type_snip,
|
||||
&generics_sugg,
|
||||
&where_clause_sugg,
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id));
|
||||
}
|
||||
}
|
||||
self.impling_types = Some(impls);
|
||||
}
|
||||
|
||||
// Check if a Default implementation exists for the Self type, regardless of
|
||||
// generics
|
||||
if let Some(ref impling_types) = self.impling_types
|
||||
&& let self_def = cx.tcx.type_of(item.owner_id).instantiate_identity()
|
||||
&& let Some(self_def) = self_def.ty_adt_def()
|
||||
&& let Some(self_local_did) = self_def.did().as_local()
|
||||
&& let self_id = cx.tcx.local_def_id_to_hir_id(self_local_did)
|
||||
&& impling_types.contains(&self_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let attrs_sugg = {
|
||||
let mut sugg = String::new();
|
||||
for attr in cx.tcx.hir_attrs(assoc_item_hir_id) {
|
||||
if !attr.has_name(sym::cfg_trace) {
|
||||
// This might be some other attribute that the `impl Default` ought to inherit.
|
||||
// But it could also be one of the many attributes that:
|
||||
// - can't be put on an impl block -- like `#[inline]`
|
||||
// - we can't even build a suggestion for, since `Attribute::span` may panic.
|
||||
//
|
||||
// Because of all that, remain on the safer side -- don't inherit this attr, and just
|
||||
// reduce the applicability
|
||||
app = Applicability::MaybeIncorrect;
|
||||
continue;
|
||||
}
|
||||
|
||||
sugg.push_str(&snippet_with_applicability(cx.sess(), attr.span(), "_", &mut app));
|
||||
sugg.push('\n');
|
||||
}
|
||||
sugg
|
||||
};
|
||||
let generics_sugg = snippet_with_applicability(cx, generics.span, "", &mut app);
|
||||
let where_clause_sugg = if generics.has_where_clause_predicates {
|
||||
format!(
|
||||
"\n{}\n",
|
||||
snippet_with_applicability(cx, generics.where_clause_span, "", &mut app)
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let self_ty_fmt = self_ty.to_string();
|
||||
let self_type_snip = snippet_with_applicability(cx, impl_self_ty.span, &self_ty_fmt, &mut app);
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEW_WITHOUT_DEFAULT,
|
||||
id.into(),
|
||||
impl_item.span,
|
||||
format!("you should consider adding a `Default` implementation for `{self_type_snip}`"),
|
||||
|diag| {
|
||||
diag.suggest_prepend_item(
|
||||
cx,
|
||||
item.span,
|
||||
"try adding this",
|
||||
&create_new_without_default_suggest_msg(
|
||||
&attrs_sugg,
|
||||
&self_type_snip,
|
||||
&generics_sugg,
|
||||
&where_clause_sugg,
|
||||
),
|
||||
app,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_without_default_suggest_msg(
|
||||
attrs_sugg: &str,
|
||||
self_type_snip: &str,
|
||||
generics_sugg: &str,
|
||||
where_clause_sugg: &str,
|
||||
) -> String {
|
||||
#[rustfmt::skip]
|
||||
format!(
|
||||
"impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
|
||||
"{attrs_sugg}impl{generics_sugg} Default for {self_type_snip}{where_clause_sugg} {{
|
||||
fn default() -> Self {{
|
||||
Self::new()
|
||||
}}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp};
|
||||
use rustc_hir::{Block, Body, Expr, ExprKind, ImplItem, ImplItemKind, Item, LangItem, Node, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty::EarlyBinder;
|
||||
use rustc_middle::ty::{EarlyBinder, TraitRef};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::kw;
|
||||
@@ -112,142 +112,148 @@
|
||||
declare_lint_pass!(NonCanonicalImpls => [NON_CANONICAL_CLONE_IMPL, NON_CANONICAL_PARTIAL_ORD_IMPL]);
|
||||
|
||||
impl LateLintPass<'_> for NonCanonicalImpls {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_impl_item<'tcx>(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'tcx>) {
|
||||
let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id()) else {
|
||||
return;
|
||||
};
|
||||
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
|
||||
return;
|
||||
};
|
||||
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
|
||||
return;
|
||||
}
|
||||
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir_impl_item(impl_item.impl_item_id()).kind else {
|
||||
return;
|
||||
};
|
||||
let body = cx.tcx.hir_body(impl_item_id);
|
||||
let ExprKind::Block(block, ..) = body.value.kind else {
|
||||
return;
|
||||
};
|
||||
if block.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, impl_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id);
|
||||
if trait_name == Some(sym::Clone)
|
||||
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
|
||||
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
|
||||
if let ImplItemKind::Fn(_, impl_item_id) = impl_item.kind
|
||||
&& let Node::Item(item) = cx.tcx.parent_hir_node(impl_item.hir_id())
|
||||
&& let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder)
|
||||
&& let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id)
|
||||
// NOTE: check this early to avoid expensive checks that come after this one
|
||||
&& matches!(trait_name, Some(sym::Clone | sym::PartialOrd))
|
||||
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
|
||||
&& let body = cx.tcx.hir_body(impl_item_id)
|
||||
&& let ExprKind::Block(block, ..) = body.value.kind
|
||||
&& !block.span.in_external_macro(cx.sess().source_map())
|
||||
&& !is_from_proc_macro(cx, impl_item)
|
||||
{
|
||||
if impl_item.ident.name == sym::clone {
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
|
||||
&& let ExprKind::Path(qpath) = deref.kind
|
||||
&& last_path_segment(&qpath).ident.name == kw::SelfLower
|
||||
{
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NON_CANONICAL_CLONE_IMPL,
|
||||
block.span,
|
||||
"non-canonical implementation of `clone` on a `Copy` type",
|
||||
"change this to",
|
||||
"{ *self }".to_owned(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if impl_item.ident.name == sym::clone_from {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NON_CANONICAL_CLONE_IMPL,
|
||||
impl_item.span,
|
||||
"unnecessary implementation of `clone_from` on a `Copy` type",
|
||||
"remove it",
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
} else if trait_name == Some(sym::PartialOrd)
|
||||
&& impl_item.ident.name == sym::partial_cmp
|
||||
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
|
||||
{
|
||||
// If the `cmp` call likely needs to be fully qualified in the suggestion
|
||||
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
|
||||
// access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
|
||||
let mut needs_fully_qualified = false;
|
||||
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
&& expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified)
|
||||
if trait_name == Some(sym::Clone)
|
||||
&& let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy)
|
||||
&& implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[])
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Fix #12683, allow [`needless_return`] here
|
||||
else if block.expr.is_none()
|
||||
&& let Some(stmt) = block.stmts.first()
|
||||
&& let rustc_hir::StmtKind::Semi(Expr {
|
||||
kind: ExprKind::Ret(Some(ret)),
|
||||
..
|
||||
}) = stmt.kind
|
||||
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
|
||||
check_clone_on_copy(cx, impl_item, block);
|
||||
} else if trait_name == Some(sym::PartialOrd)
|
||||
&& impl_item.ident.name == sym::partial_cmp
|
||||
&& let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
&& implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[])
|
||||
{
|
||||
return;
|
||||
check_partial_ord_on_ord(cx, impl_item, item, &trait_impl, body, block);
|
||||
}
|
||||
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
|
||||
// suggestion tons more complex.
|
||||
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
|
||||
&& lhs != rhs
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NON_CANONICAL_PARTIAL_ORD_IMPL,
|
||||
item.span,
|
||||
"non-canonical implementation of `partial_cmp` on an `Ord` type",
|
||||
|diag| {
|
||||
let [_, other] = body.params else {
|
||||
return;
|
||||
};
|
||||
let Some(std_or_core) = std_or_core(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
|
||||
(Some(other_ident), true) => vec![(
|
||||
block.span,
|
||||
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
|
||||
)],
|
||||
(Some(other_ident), false) => {
|
||||
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
|
||||
},
|
||||
(None, true) => vec![
|
||||
(
|
||||
block.span,
|
||||
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
|
||||
),
|
||||
(other.pat.span, "other".to_owned()),
|
||||
],
|
||||
(None, false) => vec![
|
||||
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
|
||||
(other.pat.span, "other".to_owned()),
|
||||
],
|
||||
};
|
||||
|
||||
diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_clone_on_copy(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, block: &Block<'_>) {
|
||||
if impl_item.ident.name == sym::clone {
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
&& let ExprKind::Unary(UnOp::Deref, deref) = expr.kind
|
||||
&& let ExprKind::Path(qpath) = deref.kind
|
||||
&& last_path_segment(&qpath).ident.name == kw::SelfLower
|
||||
{
|
||||
// this is the canonical implementation, `fn clone(&self) -> Self { *self }`
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NON_CANONICAL_CLONE_IMPL,
|
||||
block.span,
|
||||
"non-canonical implementation of `clone` on a `Copy` type",
|
||||
"change this to",
|
||||
"{ *self }".to_owned(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
if impl_item.ident.name == sym::clone_from {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NON_CANONICAL_CLONE_IMPL,
|
||||
impl_item.span,
|
||||
"unnecessary implementation of `clone_from` on a `Copy` type",
|
||||
"remove it",
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_partial_ord_on_ord<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
impl_item: &ImplItem<'_>,
|
||||
item: &Item<'_>,
|
||||
trait_impl: &TraitRef<'_>,
|
||||
body: &Body<'_>,
|
||||
block: &Block<'tcx>,
|
||||
) {
|
||||
// If the `cmp` call likely needs to be fully qualified in the suggestion
|
||||
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
|
||||
// access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
|
||||
|
||||
let mut needs_fully_qualified = false;
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
&& expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Fix #12683, allow [`needless_return`] here
|
||||
else if block.expr.is_none()
|
||||
&& let Some(stmt) = block.stmts.first()
|
||||
&& let rustc_hir::StmtKind::Semi(Expr {
|
||||
kind: ExprKind::Ret(Some(ret)),
|
||||
..
|
||||
}) = stmt.kind
|
||||
&& expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
|
||||
// suggestion tons more complex.
|
||||
else if let [lhs, rhs, ..] = trait_impl.args.as_slice()
|
||||
&& lhs != rhs
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NON_CANONICAL_PARTIAL_ORD_IMPL,
|
||||
item.span,
|
||||
"non-canonical implementation of `partial_cmp` on an `Ord` type",
|
||||
|diag| {
|
||||
let [_, other] = body.params else {
|
||||
return;
|
||||
};
|
||||
let Some(std_or_core) = std_or_core(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
|
||||
(Some(other_ident), true) => vec![(
|
||||
block.span,
|
||||
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
|
||||
)],
|
||||
(Some(other_ident), false) => {
|
||||
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
|
||||
},
|
||||
(None, true) => vec![
|
||||
(
|
||||
block.span,
|
||||
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
|
||||
),
|
||||
(other.pat.span, "other".to_owned()),
|
||||
],
|
||||
(None, false) => vec![
|
||||
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
|
||||
(other.pat.span, "other".to_owned()),
|
||||
],
|
||||
};
|
||||
|
||||
diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Return true if `expr_kind` is a `cmp` call.
|
||||
fn expr_is_cmp<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
@@ -256,26 +262,27 @@ fn expr_is_cmp<'tcx>(
|
||||
needs_fully_qualified: &mut bool,
|
||||
) -> bool {
|
||||
let impl_item_did = impl_item.owner_id.def_id;
|
||||
if let ExprKind::Call(
|
||||
Expr {
|
||||
kind: ExprKind::Path(some_path),
|
||||
hir_id: some_hir_id,
|
||||
..
|
||||
},
|
||||
[cmp_expr],
|
||||
) = expr.kind
|
||||
{
|
||||
is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
|
||||
match expr.kind {
|
||||
ExprKind::Call(
|
||||
Expr {
|
||||
kind: ExprKind::Path(some_path),
|
||||
hir_id: some_hir_id,
|
||||
..
|
||||
},
|
||||
[cmp_expr],
|
||||
) => {
|
||||
is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
|
||||
// Fix #11178, allow `Self::cmp(self, ..)` too
|
||||
&& self_cmp_call(cx, cmp_expr, impl_item_did, needs_fully_qualified)
|
||||
} else if let ExprKind::MethodCall(_, recv, [], _) = expr.kind {
|
||||
cx.tcx
|
||||
.typeck(impl_item_did)
|
||||
.type_dependent_def_id(expr.hir_id)
|
||||
.is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into))
|
||||
&& self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified)
|
||||
} else {
|
||||
false
|
||||
},
|
||||
ExprKind::MethodCall(_, recv, [], _) => {
|
||||
cx.tcx
|
||||
.typeck(impl_item_did)
|
||||
.type_dependent_def_id(expr.hir_id)
|
||||
.is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into))
|
||||
&& self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
/// Checks that common macros are used with consistent bracing.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is mostly a consistency lint although using () or []
|
||||
/// doesn't give you a semicolon in item position, which can be unexpected.
|
||||
/// Having non-conventional braces on well-stablished macros can be confusing
|
||||
/// when debugging, and they bring incosistencies with the rest of the ecosystem.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
@@ -33,8 +33,12 @@
|
||||
"check consistent use of braces in macro"
|
||||
}
|
||||
|
||||
/// The (callsite span, (open brace, close brace), source snippet)
|
||||
type MacroInfo = (Span, (char, char), SourceText);
|
||||
struct MacroInfo {
|
||||
callsite_span: Span,
|
||||
callsite_snippet: SourceText,
|
||||
old_open_brace: char,
|
||||
braces: (char, char),
|
||||
}
|
||||
|
||||
pub struct MacroBraces {
|
||||
macro_braces: FxHashMap<String, (char, char)>,
|
||||
@@ -54,30 +58,58 @@ pub fn new(conf: &'static Conf) -> Self {
|
||||
|
||||
impl EarlyLintPass for MacroBraces {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
|
||||
if let Some((span, braces, snip)) = is_offending_macro(cx, item.span, self) {
|
||||
emit_help(cx, &snip, braces, span);
|
||||
self.done.insert(span);
|
||||
if let Some(MacroInfo {
|
||||
callsite_span,
|
||||
callsite_snippet,
|
||||
braces,
|
||||
..
|
||||
}) = is_offending_macro(cx, item.span, self)
|
||||
{
|
||||
emit_help(cx, &callsite_snippet, braces, callsite_span, false);
|
||||
self.done.insert(callsite_span);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
|
||||
if let Some((span, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
|
||||
emit_help(cx, &snip, braces, span);
|
||||
self.done.insert(span);
|
||||
if let Some(MacroInfo {
|
||||
callsite_span,
|
||||
callsite_snippet,
|
||||
braces,
|
||||
old_open_brace,
|
||||
}) = is_offending_macro(cx, stmt.span, self)
|
||||
{
|
||||
// if we turn `macro!{}` into `macro!()`/`macro![]`, we'll no longer get the implicit
|
||||
// trailing semicolon, see #9913
|
||||
// NOTE: `stmt.kind != StmtKind::MacCall` because `EarlyLintPass` happens after macro expansion
|
||||
let add_semi = matches!(stmt.kind, ast::StmtKind::Expr(..)) && old_open_brace == '{';
|
||||
emit_help(cx, &callsite_snippet, braces, callsite_span, add_semi);
|
||||
self.done.insert(callsite_span);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
|
||||
if let Some((span, braces, snip)) = is_offending_macro(cx, expr.span, self) {
|
||||
emit_help(cx, &snip, braces, span);
|
||||
self.done.insert(span);
|
||||
if let Some(MacroInfo {
|
||||
callsite_span,
|
||||
callsite_snippet,
|
||||
braces,
|
||||
..
|
||||
}) = is_offending_macro(cx, expr.span, self)
|
||||
{
|
||||
emit_help(cx, &callsite_snippet, braces, callsite_span, false);
|
||||
self.done.insert(callsite_span);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
|
||||
if let Some((span, braces, snip)) = is_offending_macro(cx, ty.span, self) {
|
||||
emit_help(cx, &snip, braces, span);
|
||||
self.done.insert(span);
|
||||
if let Some(MacroInfo {
|
||||
callsite_span,
|
||||
braces,
|
||||
callsite_snippet,
|
||||
..
|
||||
}) = is_offending_macro(cx, ty.span, self)
|
||||
{
|
||||
emit_help(cx, &callsite_snippet, braces, callsite_span, false);
|
||||
self.done.insert(callsite_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,39 +122,44 @@ fn is_offending_macro(cx: &EarlyContext<'_>, span: Span, mac_braces: &MacroBrace
|
||||
.last()
|
||||
.is_some_and(|e| e.macro_def_id.is_some_and(DefId::is_local))
|
||||
};
|
||||
let span_call_site = span.ctxt().outer_expn_data().call_site;
|
||||
let callsite_span = span.ctxt().outer_expn_data().call_site;
|
||||
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind
|
||||
&& let name = mac_name.as_str()
|
||||
&& let Some(&braces) = mac_braces.macro_braces.get(name)
|
||||
&& let Some(snip) = span_call_site.get_source_text(cx)
|
||||
&& let Some(snip) = callsite_span.get_source_text(cx)
|
||||
// we must check only invocation sites
|
||||
// https://github.com/rust-lang/rust-clippy/issues/7422
|
||||
&& snip.starts_with(&format!("{name}!"))
|
||||
&& let Some(macro_args_str) = snip.strip_prefix(name).and_then(|snip| snip.strip_prefix('!'))
|
||||
&& let Some(old_open_brace @ ('{' | '(' | '[')) = macro_args_str.trim_start().chars().next()
|
||||
&& old_open_brace != braces.0
|
||||
&& unnested_or_local()
|
||||
// make formatting consistent
|
||||
&& let c = snip.replace(' ', "")
|
||||
&& !c.starts_with(&format!("{name}!{}", braces.0))
|
||||
&& !mac_braces.done.contains(&span_call_site)
|
||||
&& !mac_braces.done.contains(&callsite_span)
|
||||
{
|
||||
Some((span_call_site, braces, snip))
|
||||
Some(MacroInfo {
|
||||
callsite_span,
|
||||
callsite_snippet: snip,
|
||||
old_open_brace,
|
||||
braces,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span) {
|
||||
fn emit_help(cx: &EarlyContext<'_>, snip: &str, (open, close): (char, char), span: Span, add_semi: bool) {
|
||||
let semi = if add_semi { ";" } else { "" };
|
||||
if let Some((macro_name, macro_args_str)) = snip.split_once('!') {
|
||||
let mut macro_args = macro_args_str.trim().to_string();
|
||||
// now remove the wrong braces
|
||||
macro_args.remove(0);
|
||||
macro_args.pop();
|
||||
macro_args.remove(0);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NONSTANDARD_MACRO_BRACES,
|
||||
span,
|
||||
format!("use of irregular braces for `{macro_name}!` macro"),
|
||||
"consider writing",
|
||||
format!("{macro_name}!{open}{macro_args}{close}"),
|
||||
format!("{macro_name}!{open}{macro_args}{close}{semi}"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,33 @@
|
||||
/// the calculations have no side-effects (function calls or mutating dereference)
|
||||
/// and the assigned variables are also only in recursion, it is useless.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn f(a: usize, b: usize) -> usize {
|
||||
/// if a == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// f(a - 1, b + 1)
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// # print!("{}", f(1, 1));
|
||||
/// # }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn f(a: usize) -> usize {
|
||||
/// if a == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// f(a - 1)
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// # print!("{}", f(1));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Too many code paths in the linting code are currently untested and prone to produce false
|
||||
/// positives or are prone to have performance implications.
|
||||
@@ -51,39 +78,90 @@
|
||||
/// - struct pattern binding
|
||||
///
|
||||
/// Also, when you recurse the function name with path segments, it is not possible to detect.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn f(a: usize, b: usize) -> usize {
|
||||
/// if a == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// f(a - 1, b + 1)
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// # print!("{}", f(1, 1));
|
||||
/// # }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn f(a: usize) -> usize {
|
||||
/// if a == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// f(a - 1)
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// # print!("{}", f(1));
|
||||
/// # }
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub ONLY_USED_IN_RECURSION,
|
||||
complexity,
|
||||
"arguments that is only used in recursion can be removed"
|
||||
}
|
||||
impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `self` receiver that is only used in recursion with no side-effects.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// It may be possible to remove the `self` argument, allowing the function to be
|
||||
/// used without an object of type `Self`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// struct Foo;
|
||||
/// impl Foo {
|
||||
/// fn f(&self, n: u32) -> u32 {
|
||||
/// if n == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// n * self.f(n - 1)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// # print!("{}", Foo.f(10));
|
||||
/// # }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// struct Foo;
|
||||
/// impl Foo {
|
||||
/// fn f(n: u32) -> u32 {
|
||||
/// if n == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// n * Self::f(n - 1)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {
|
||||
/// # print!("{}", Foo::f(10));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Too many code paths in the linting code are currently untested and prone to produce false
|
||||
/// positives or are prone to have performance implications.
|
||||
///
|
||||
/// In some cases, this would not catch all useless arguments.
|
||||
///
|
||||
/// ```no_run
|
||||
/// struct Foo;
|
||||
/// impl Foo {
|
||||
/// fn foo(&self, a: usize) -> usize {
|
||||
/// let f = |x| x;
|
||||
///
|
||||
/// if a == 0 {
|
||||
/// 1
|
||||
/// } else {
|
||||
/// f(self).foo(a)
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For example, here `self` is only used in recursion, but the lint would not catch it.
|
||||
///
|
||||
/// List of some examples that can not be caught:
|
||||
/// - binary operation of non-primitive types
|
||||
/// - closure usage
|
||||
/// - some `break` relative operations
|
||||
/// - struct pattern binding
|
||||
///
|
||||
/// Also, when you recurse the function name with path segments, it is not possible to detect.
|
||||
#[clippy::version = "1.92.0"]
|
||||
pub SELF_ONLY_USED_IN_RECURSION,
|
||||
pedantic,
|
||||
"self receiver only used to recursively call method can be removed"
|
||||
}
|
||||
impl_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION, SELF_ONLY_USED_IN_RECURSION]);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FnKind {
|
||||
@@ -357,26 +435,39 @@ fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) {
|
||||
self.params.flag_for_linting();
|
||||
for param in &self.params.params {
|
||||
if param.apply_lint.get() {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
ONLY_USED_IN_RECURSION,
|
||||
param.ident.span,
|
||||
"parameter is only used in recursion",
|
||||
|diag| {
|
||||
if param.ident.name != kw::SelfLower {
|
||||
if param.ident.name == kw::SelfLower {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
SELF_ONLY_USED_IN_RECURSION,
|
||||
param.ident.span,
|
||||
"`self` is only used in recursion",
|
||||
|diag| {
|
||||
diag.span_note(
|
||||
param.uses.iter().map(|x| x.span).collect::<Vec<_>>(),
|
||||
"`self` used here",
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
ONLY_USED_IN_RECURSION,
|
||||
param.ident.span,
|
||||
"parameter is only used in recursion",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
param.ident.span,
|
||||
"if this is intentional, prefix it with an underscore",
|
||||
format!("_{}", param.ident.name),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
diag.span_note(
|
||||
param.uses.iter().map(|x| x.span).collect::<Vec<_>>(),
|
||||
"parameter used here",
|
||||
);
|
||||
},
|
||||
);
|
||||
diag.span_note(
|
||||
param.uses.iter().map(|x| x.span).collect::<Vec<_>>(),
|
||||
"parameter used here",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.params.clear();
|
||||
|
||||
@@ -190,7 +190,7 @@ fn manage_bin_ops<'tcx>(
|
||||
lhs: &'tcx hir::Expr<'_>,
|
||||
rhs: &'tcx hir::Expr<'_>,
|
||||
) {
|
||||
if ConstEvalCtxt::new(cx).eval_simple(expr).is_some() {
|
||||
if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_some() {
|
||||
return;
|
||||
}
|
||||
if !matches!(
|
||||
@@ -283,7 +283,7 @@ fn manage_method_call<'tcx>(
|
||||
let Some(arg) = args.first() else {
|
||||
return;
|
||||
};
|
||||
if ConstEvalCtxt::new(cx).eval_simple(receiver).is_some() {
|
||||
if ConstEvalCtxt::new(cx).eval_local(receiver, expr.span.ctxt()).is_some() {
|
||||
return;
|
||||
}
|
||||
let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver);
|
||||
|
||||
@@ -22,7 +22,7 @@ fn comparison_to_const<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
typeck: &'tcx TypeckResults<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> {
|
||||
) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant, Ty<'tcx>)> {
|
||||
if let ExprKind::Binary(operator, left, right) = expr.kind
|
||||
&& let Ok(cmp_op) = CmpOp::try_from(operator.node)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>(
|
||||
if op == BinOpKind::Div
|
||||
&& let ExprKind::MethodCall(method_path, self_arg, [], _) = left.kind
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration)
|
||||
&& let Some(Constant::Int(divisor)) = ConstEvalCtxt::new(cx).eval(right)
|
||||
&& let Some(Constant::Int(divisor)) = ConstEvalCtxt::new(cx).eval_local(right, expr.span.ctxt())
|
||||
{
|
||||
let suggested_fn = match (method_path.ident.name, divisor) {
|
||||
(sym::subsec_micros, 1_000) | (sym::subsec_nanos, 1_000_000) => "subsec_millis",
|
||||
|
||||
@@ -39,7 +39,9 @@ fn check_op<'tcx>(
|
||||
other: &Expr<'tcx>,
|
||||
parent: &Expr<'tcx>,
|
||||
) {
|
||||
if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_simple(op) == Some(Constant::Int(0)) {
|
||||
if ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), tck).eval_local(op, parent.span.ctxt())
|
||||
== Some(Constant::Int(0))
|
||||
{
|
||||
if different_types(tck, other, parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,13 @@ pub(crate) fn check<'tcx>(
|
||||
) {
|
||||
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && is_float(cx, left) {
|
||||
let ecx = ConstEvalCtxt::new(cx);
|
||||
let left_is_local = match ecx.eval_with_source(left) {
|
||||
let ctxt = expr.span.ctxt();
|
||||
let left_is_local = match ecx.eval_with_source(left, ctxt) {
|
||||
Some((c, s)) if !is_allowed(&c) => s.is_local(),
|
||||
Some(_) => return,
|
||||
None => true,
|
||||
};
|
||||
let right_is_local = match ecx.eval_with_source(right) {
|
||||
let right_is_local = match ecx.eval_with_source(right, ctxt) {
|
||||
Some((c, s)) if !is_allowed(&c) => s.is_local(),
|
||||
Some(_) => return,
|
||||
None => true,
|
||||
@@ -84,7 +85,7 @@ fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static
|
||||
}
|
||||
}
|
||||
|
||||
fn is_allowed(val: &Constant<'_>) -> bool {
|
||||
fn is_allowed(val: &Constant) -> bool {
|
||||
match val {
|
||||
// FIXME(f16_f128): add when equality check is available on all platforms
|
||||
&Constant::F32(f) => f == 0.0 || f.is_infinite(),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::{Span, kw};
|
||||
use rustc_span::{Span, SyntaxContext, kw};
|
||||
|
||||
use super::IDENTITY_OP;
|
||||
|
||||
@@ -41,42 +41,43 @@ pub(crate) fn check<'tcx>(
|
||||
(span, is_coerced)
|
||||
};
|
||||
|
||||
let ctxt = expr.span.ctxt();
|
||||
match op {
|
||||
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
|
||||
if is_redundant_op(cx, left, 0) {
|
||||
if is_redundant_op(cx, left, 0, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, right);
|
||||
span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
|
||||
} else if is_redundant_op(cx, right, 0) {
|
||||
} else if is_redundant_op(cx, right, 0, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, left);
|
||||
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
|
||||
}
|
||||
},
|
||||
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
|
||||
if is_redundant_op(cx, right, 0) {
|
||||
if is_redundant_op(cx, right, 0, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, left);
|
||||
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
|
||||
}
|
||||
},
|
||||
BinOpKind::Mul => {
|
||||
if is_redundant_op(cx, left, 1) {
|
||||
if is_redundant_op(cx, left, 1, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, right);
|
||||
span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
|
||||
} else if is_redundant_op(cx, right, 1) {
|
||||
} else if is_redundant_op(cx, right, 1, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, left);
|
||||
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
|
||||
}
|
||||
},
|
||||
BinOpKind::Div => {
|
||||
if is_redundant_op(cx, right, 1) {
|
||||
if is_redundant_op(cx, right, 1, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, left);
|
||||
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
|
||||
}
|
||||
},
|
||||
BinOpKind::BitAnd => {
|
||||
if is_redundant_op(cx, left, -1) {
|
||||
if is_redundant_op(cx, left, -1, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, right);
|
||||
span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value);
|
||||
} else if is_redundant_op(cx, right, -1) {
|
||||
} else if is_redundant_op(cx, right, -1, ctxt) {
|
||||
let paren = needs_parenthesis(cx, expr, left);
|
||||
span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value);
|
||||
}
|
||||
@@ -184,14 +185,17 @@ fn is_allowed<'tcx>(
|
||||
|
||||
// This lint applies to integers and their references
|
||||
cx.typeck_results().expr_ty(left).peel_refs().is_integral()
|
||||
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
|
||||
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
|
||||
// `1 << 0` is a common pattern in bit manipulation code
|
||||
&& !(cmp == BinOpKind::Shl && is_zero_integer_const(cx, right) && integer_const(cx, left) == Some(1))
|
||||
&& !(cmp == BinOpKind::Shl
|
||||
&& is_zero_integer_const(cx, right, expr.span.ctxt())
|
||||
&& integer_const(cx, left, expr.span.ctxt()) == Some(1))
|
||||
}
|
||||
|
||||
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
|
||||
let ecx = ConstEvalCtxt::new(cx);
|
||||
if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) {
|
||||
let ctxt = span.ctxt();
|
||||
if match (ecx.eval_full_int(left, ctxt), ecx.eval_full_int(right, ctxt)) {
|
||||
(Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(),
|
||||
(Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
|
||||
_ => return,
|
||||
@@ -200,8 +204,8 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
|
||||
}
|
||||
}
|
||||
|
||||
fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool {
|
||||
if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) {
|
||||
fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, ctxt: SyntaxContext) -> bool {
|
||||
if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_local(e, ctxt).map(Constant::peel_refs) {
|
||||
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
|
||||
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
|
||||
ty::Uint(uty) => clip(cx.tcx, !0, uty),
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &Expr<'_>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'tcx>,
|
||||
rhs: &'tcx Expr<'tcx>,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
if msrv.meets(cx, msrvs::UNSIGNED_IS_MULTIPLE_OF)
|
||||
&& let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs)
|
||||
&& let Some(operand) = uint_compare_to_zero(cx, expr, op, lhs, rhs)
|
||||
&& let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind
|
||||
&& operand_op.node == BinOpKind::Rem
|
||||
&& matches!(
|
||||
@@ -57,18 +57,19 @@ pub(super) fn check<'tcx>(
|
||||
// If we have a `x == 0`, `x != 0` or `x > 0` (or the reverted ones), return the non-zero operand
|
||||
fn uint_compare_to_zero<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'tcx>,
|
||||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'tcx>,
|
||||
rhs: &'tcx Expr<'tcx>,
|
||||
) -> Option<&'tcx Expr<'tcx>> {
|
||||
let operand = if matches!(lhs.kind, ExprKind::Binary(..))
|
||||
&& matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Gt)
|
||||
&& is_zero_integer_const(cx, rhs)
|
||||
&& is_zero_integer_const(cx, rhs, e.span.ctxt())
|
||||
{
|
||||
lhs
|
||||
} else if matches!(rhs.kind, ExprKind::Binary(..))
|
||||
&& matches!(op, BinOpKind::Eq | BinOpKind::Ne | BinOpKind::Lt)
|
||||
&& is_zero_integer_const(cx, lhs)
|
||||
&& is_zero_integer_const(cx, lhs, e.span.ctxt())
|
||||
{
|
||||
rhs
|
||||
} else {
|
||||
|
||||
@@ -854,7 +854,7 @@
|
||||
/// println!("{a} is divisible by {b}");
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.89.0"]
|
||||
#[clippy::version = "1.90.0"]
|
||||
pub MANUAL_IS_MULTIPLE_OF,
|
||||
complexity,
|
||||
"manual implementation of `.is_multiple_of()`"
|
||||
|
||||
@@ -39,7 +39,9 @@ fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
&& let BinOpKind::Eq | BinOpKind::Ne = op.node
|
||||
{
|
||||
let ecx = ConstEvalCtxt::new(cx);
|
||||
matches!(ecx.eval(lhs), Some(Constant::Int(0))) || matches!(ecx.eval(rhs), Some(Constant::Int(0)))
|
||||
let ctxt = expr.span.ctxt();
|
||||
matches!(ecx.eval_local(lhs, ctxt), Some(Constant::Int(0)))
|
||||
|| matches!(ecx.eval_local(rhs, ctxt), Some(Constant::Int(0)))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -55,7 +57,7 @@ fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) ->
|
||||
match ConstEvalCtxt::new(cx).eval(operand)? {
|
||||
Constant::Int(v) => match *cx.typeck_results().expr_ty(expr).kind() {
|
||||
ty::Int(ity) => {
|
||||
let value = sext(cx.tcx, v, ity);
|
||||
let value: i128 = sext(cx.tcx, v, ity);
|
||||
Some(OperandInfo {
|
||||
string_representation: Some(value.to_string()),
|
||||
is_negative: value < 0,
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn check_negate<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Ex
|
||||
return;
|
||||
}
|
||||
let ty = cx.typeck_results().expr_ty(arg);
|
||||
if ConstEvalCtxt::new(cx).eval_simple(expr).is_none() && ty.is_floating_point() {
|
||||
if ConstEvalCtxt::new(cx).eval_local(expr, expr.span.ctxt()).is_none() && ty.is_floating_point() {
|
||||
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::ty::{implements_trait, is_copy, is_type_diagnostic_item};
|
||||
use clippy_utils::usage::local_used_after_expr;
|
||||
use clippy_utils::{
|
||||
eq_expr_value, fn_def_id_with_node_args, higher, is_else_clause, is_in_const_context, is_lint_allowed,
|
||||
is_path_lang_item, is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id,
|
||||
@@ -483,6 +484,13 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr:
|
||||
.filter(|e| *e)
|
||||
.is_none()
|
||||
{
|
||||
if !is_copy(cx, caller_ty)
|
||||
&& let Some(hir_id) = path_to_local(let_expr)
|
||||
&& local_used_after_expr(cx, hir_id, expr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
|
||||
let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_));
|
||||
|
||||
@@ -299,8 +299,8 @@ fn check_possible_range_contains(
|
||||
}
|
||||
}
|
||||
|
||||
struct RangeBounds<'a, 'tcx> {
|
||||
val: Constant<'tcx>,
|
||||
struct RangeBounds<'a> {
|
||||
val: Constant,
|
||||
expr: &'a Expr<'a>,
|
||||
id: HirId,
|
||||
name_span: Span,
|
||||
@@ -312,7 +312,7 @@ struct RangeBounds<'a, 'tcx> {
|
||||
// Takes a binary expression such as x <= 2 as input
|
||||
// Breaks apart into various pieces, such as the value of the number,
|
||||
// hir id of the variable, and direction/inclusiveness of the operator
|
||||
fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> Option<RangeBounds<'a, 'tcx>> {
|
||||
fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<RangeBounds<'a>> {
|
||||
if let ExprKind::Binary(ref op, l, r) = ex.kind {
|
||||
let (inclusive, ordering) = match op.node {
|
||||
BinOpKind::Gt => (false, Ordering::Greater),
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{is_path_diagnostic_item, ty};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints subtraction between `Instant::now()` and another `Instant`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
|
||||
/// as `Instant` subtraction saturates.
|
||||
///
|
||||
/// `prev_instant.elapsed()` also more clearly signals intention.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// use std::time::Instant;
|
||||
/// let prev_instant = Instant::now();
|
||||
/// let duration = Instant::now() - prev_instant;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// use std::time::Instant;
|
||||
/// let prev_instant = Instant::now();
|
||||
/// let duration = prev_instant.elapsed();
|
||||
/// ```
|
||||
#[clippy::version = "1.65.0"]
|
||||
pub MANUAL_INSTANT_ELAPSED,
|
||||
pedantic,
|
||||
"subtraction between `Instant::now()` and previous `Instant`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints subtraction between an `Instant` and a `Duration`, or between two `Duration` values.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Unchecked subtraction could cause underflow on certain platforms, leading to
|
||||
/// unintentional panics.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::time::{Instant, Duration};
|
||||
/// let time_passed = Instant::now() - Duration::from_secs(5);
|
||||
/// let dur1 = Duration::from_secs(3);
|
||||
/// let dur2 = Duration::from_secs(5);
|
||||
/// let diff = dur1 - dur2;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::time::{Instant, Duration};
|
||||
/// let time_passed = Instant::now().checked_sub(Duration::from_secs(5));
|
||||
/// let dur1 = Duration::from_secs(3);
|
||||
/// let dur2 = Duration::from_secs(5);
|
||||
/// let diff = dur1.checked_sub(dur2);
|
||||
/// ```
|
||||
#[clippy::version = "1.67.0"]
|
||||
pub UNCHECKED_TIME_SUBTRACTION,
|
||||
pedantic,
|
||||
"finds unchecked subtraction involving 'Duration' or 'Instant'"
|
||||
}
|
||||
|
||||
pub struct UncheckedTimeSubtraction {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl UncheckedTimeSubtraction {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(UncheckedTimeSubtraction => [MANUAL_INSTANT_ELAPSED, UNCHECKED_TIME_SUBTRACTION]);
|
||||
|
||||
impl LateLintPass<'_> for UncheckedTimeSubtraction {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
node: BinOpKind::Sub, ..
|
||||
},
|
||||
lhs,
|
||||
rhs,
|
||||
) = expr.kind
|
||||
{
|
||||
let typeck = cx.typeck_results();
|
||||
let lhs_ty = typeck.expr_ty(lhs);
|
||||
let rhs_ty = typeck.expr_ty(rhs);
|
||||
|
||||
if ty::is_type_diagnostic_item(cx, lhs_ty, sym::Instant) {
|
||||
// Instant::now() - instant
|
||||
if is_instant_now_call(cx, lhs)
|
||||
&& ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant)
|
||||
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
|
||||
{
|
||||
print_manual_instant_elapsed_sugg(cx, expr, sugg);
|
||||
}
|
||||
// instant - duration
|
||||
else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration)
|
||||
&& !expr.span.from_expansion()
|
||||
&& self.msrv.meets(cx, msrvs::TRY_FROM)
|
||||
{
|
||||
// For chained subtraction like (instant - dur1) - dur2, avoid suggestions
|
||||
if is_chained_time_subtraction(cx, lhs) {
|
||||
span_lint(
|
||||
cx,
|
||||
UNCHECKED_TIME_SUBTRACTION,
|
||||
expr.span,
|
||||
"unchecked subtraction of a 'Duration' from an 'Instant'",
|
||||
);
|
||||
} else {
|
||||
// instant - duration
|
||||
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
|
||||
}
|
||||
}
|
||||
} else if ty::is_type_diagnostic_item(cx, lhs_ty, sym::Duration)
|
||||
&& ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration)
|
||||
&& !expr.span.from_expansion()
|
||||
&& self.msrv.meets(cx, msrvs::TRY_FROM)
|
||||
{
|
||||
// For chained subtraction like (dur1 - dur2) - dur3, avoid suggestions
|
||||
if is_chained_time_subtraction(cx, lhs) {
|
||||
span_lint(
|
||||
cx,
|
||||
UNCHECKED_TIME_SUBTRACTION,
|
||||
expr.span,
|
||||
"unchecked subtraction between 'Duration' values",
|
||||
);
|
||||
} else {
|
||||
// duration - duration
|
||||
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
|
||||
if let ExprKind::Call(fn_expr, []) = expr_block.kind
|
||||
&& is_path_diagnostic_item(cx, fn_expr, sym::instant_now)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this subtraction is part of a chain like `(a - b) - c`
|
||||
fn is_chained_time_subtraction(cx: &LateContext<'_>, lhs: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(op, inner_lhs, inner_rhs) = &lhs.kind
|
||||
&& matches!(op.node, BinOpKind::Sub)
|
||||
{
|
||||
let typeck = cx.typeck_results();
|
||||
let left_ty = typeck.expr_ty(inner_lhs);
|
||||
let right_ty = typeck.expr_ty(inner_rhs);
|
||||
is_time_type(cx, left_ty) && is_time_type(cx, right_ty)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the type is Duration or Instant
|
||||
fn is_time_type(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||
ty::is_type_diagnostic_item(cx, ty, sym::Duration) || ty::is_type_diagnostic_item(cx, ty, sym::Instant)
|
||||
}
|
||||
|
||||
fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_INSTANT_ELAPSED,
|
||||
expr.span,
|
||||
"manual implementation of `Instant::elapsed`",
|
||||
"try",
|
||||
format!("{}.elapsed()", sugg.maybe_paren()),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
fn print_unchecked_duration_subtraction_sugg(
|
||||
cx: &LateContext<'_>,
|
||||
left_expr: &Expr<'_>,
|
||||
right_expr: &Expr<'_>,
|
||||
expr: &Expr<'_>,
|
||||
) {
|
||||
let typeck = cx.typeck_results();
|
||||
let left_ty = typeck.expr_ty(left_expr);
|
||||
|
||||
let lint_msg = if ty::is_type_diagnostic_item(cx, left_ty, sym::Instant) {
|
||||
"unchecked subtraction of a 'Duration' from an 'Instant'"
|
||||
} else {
|
||||
"unchecked subtraction between 'Duration' values"
|
||||
};
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "<left>", &mut applicability);
|
||||
let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "<right>", &mut applicability);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNCHECKED_TIME_SUBTRACTION,
|
||||
expr.span,
|
||||
lint_msg,
|
||||
"try",
|
||||
format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node};
|
||||
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_context};
|
||||
use clippy_utils::source::{snippet_indent, walk_span_to_context};
|
||||
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_ast::{FormatArgs, FormatArgumentKind};
|
||||
@@ -74,10 +74,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag
|
||||
"this let-binding has unit value",
|
||||
|diag| {
|
||||
let mut suggestions = Vec::new();
|
||||
let init_new_span = walk_span_to_context(init.span, local.span.ctxt()).unwrap();
|
||||
|
||||
// Suggest omitting the `let` binding
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_context(cx, init.span, local.span.ctxt(), "()", &mut app).0;
|
||||
let app = Applicability::MachineApplicable;
|
||||
|
||||
// If this is a binding pattern, we need to add suggestions to remove any usages
|
||||
// of the variable
|
||||
@@ -89,35 +89,49 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, format_args: &FormatArgsStorag
|
||||
walk_body(&mut visitor, body);
|
||||
|
||||
let mut has_in_format_capture = false;
|
||||
suggestions.extend(visitor.spans.iter().filter_map(|span| match span {
|
||||
MaybeInFormatCapture::Yes => {
|
||||
suggestions.extend(visitor.spans.into_iter().filter_map(|span| match span {
|
||||
VariableUsage::FormatCapture => {
|
||||
has_in_format_capture = true;
|
||||
None
|
||||
},
|
||||
MaybeInFormatCapture::No(span) => Some((*span, "()".to_string())),
|
||||
VariableUsage::Normal(span) => Some((span, "()".to_string())),
|
||||
VariableUsage::FieldShorthand(span) => Some((span.shrink_to_hi(), ": ()".to_string())),
|
||||
}));
|
||||
|
||||
if has_in_format_capture {
|
||||
// In a case like this:
|
||||
// ```
|
||||
// let unit = returns_unit();
|
||||
// eprintln!("{unit}");
|
||||
// ```
|
||||
// we can't remove the `unit` binding and replace its uses with a `()`,
|
||||
// because the `eprintln!` would break.
|
||||
//
|
||||
// So do the following instead:
|
||||
// ```
|
||||
// let unit = ();
|
||||
// returns_unit();
|
||||
// eprintln!("{unit}");
|
||||
// ```
|
||||
// TODO: find a less awkward way to do this
|
||||
suggestions.push((
|
||||
init.span,
|
||||
format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))),
|
||||
init_new_span.shrink_to_lo(),
|
||||
format!("();\n{}", snippet_indent(cx, local.span).as_deref().unwrap_or("")),
|
||||
));
|
||||
diag.multipart_suggestion(
|
||||
"replace variable usages with `()`",
|
||||
suggestions,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
diag.multipart_suggestion_verbose("replace variable usages with `()`", suggestions, app);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
suggestions.push((local.span, format!("{snip};")));
|
||||
// let local = returns_unit();
|
||||
// ^^^^^^^^^^^^ remove this
|
||||
suggestions.push((local.span.until(init_new_span), String::new()));
|
||||
let message = if suggestions.len() == 1 {
|
||||
"omit the `let` binding"
|
||||
} else {
|
||||
"omit the `let` binding and replace variable usages with `()`"
|
||||
};
|
||||
diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable);
|
||||
diag.multipart_suggestion_verbose(message, suggestions, app);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -128,13 +142,30 @@ struct UnitVariableCollector<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
format_args: &'a FormatArgsStorage,
|
||||
id: HirId,
|
||||
spans: Vec<MaybeInFormatCapture>,
|
||||
spans: Vec<VariableUsage>,
|
||||
macro_call: Option<&'a FormatArgs>,
|
||||
}
|
||||
|
||||
enum MaybeInFormatCapture {
|
||||
Yes,
|
||||
No(Span),
|
||||
/// How the unit variable is used
|
||||
enum VariableUsage {
|
||||
Normal(Span),
|
||||
/// Captured in a `format!`:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let unit = ();
|
||||
/// eprintln!("{unit}");
|
||||
/// ```
|
||||
FormatCapture,
|
||||
/// In a field shorthand init:
|
||||
///
|
||||
/// ```ignore
|
||||
/// struct Foo {
|
||||
/// unit: (),
|
||||
/// }
|
||||
/// let unit = ();
|
||||
/// Foo { unit };
|
||||
/// ```
|
||||
FieldShorthand(Span),
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> {
|
||||
@@ -174,9 +205,17 @@ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
|
||||
matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some()
|
||||
})
|
||||
{
|
||||
self.spans.push(MaybeInFormatCapture::Yes);
|
||||
self.spans.push(VariableUsage::FormatCapture);
|
||||
} else {
|
||||
self.spans.push(MaybeInFormatCapture::No(path.span));
|
||||
let parent = self.cx.tcx.parent_hir_node(ex.hir_id);
|
||||
match parent {
|
||||
Node::ExprField(expr_field) if expr_field.is_shorthand => {
|
||||
self.spans.push(VariableUsage::FieldShorthand(ex.span));
|
||||
},
|
||||
_ => {
|
||||
self.spans.push(VariableUsage::Normal(path.span));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
@@ -87,16 +87,33 @@ fn check_arguments<'tcx>(
|
||||
if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind()
|
||||
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr();
|
||||
span_lint_and_sugg(
|
||||
let applicability = Applicability::MachineApplicable;
|
||||
|
||||
let span_to_remove = {
|
||||
let span_until_arg = argument.span.until(arg.span);
|
||||
if let Some(Some(ref_pos)) = span_until_arg.with_source_text(cx, |src| {
|
||||
src
|
||||
// we don't use `strip_prefix` here, because `argument` might be enclosed in parens, in
|
||||
// which case `&` is no longer the prefix
|
||||
.find('&')
|
||||
// just a sanity check, in case some proc-macro messes up the spans
|
||||
.filter(|ref_pos| src[*ref_pos..].contains("mut"))
|
||||
}) && let Ok(lo) = u32::try_from(ref_pos + '&'.len_utf8())
|
||||
{
|
||||
span_until_arg.split_at(lo).1
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNNECESSARY_MUT_PASSED,
|
||||
argument.span,
|
||||
format!("the {fn_kind} `{name}` doesn't need a mutable reference"),
|
||||
"remove this `mut`",
|
||||
sugg.to_string(),
|
||||
applicability,
|
||||
|diag| {
|
||||
diag.span_suggestion_verbose(span_to_remove, "remove this `mut`", String::new(), applicability);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,8 @@ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
|
||||
)
|
||||
&& cx.typeck_results().expr_ty(expr).is_unit()
|
||||
// if a stmt has attrs, then turning it into an expr will break the code, since attrs aren't allowed on exprs
|
||||
&& cx.tcx.hir_attrs(stmt.hir_id).is_empty()
|
||||
// -- unless the corresponding feature is enabled
|
||||
&& (cx.tcx.hir_attrs(stmt.hir_id).is_empty() || cx.tcx.features().stmt_expr_attributes())
|
||||
{
|
||||
if let Some(block_is_unit) = self.is_last_in_block(stmt) {
|
||||
if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) {
|
||||
|
||||
+32
-18
@@ -1,7 +1,9 @@
|
||||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::ty::get_type_diagnostic_name;
|
||||
use clippy_utils::usage::is_potentially_local_place;
|
||||
use clippy_utils::{higher, path_to_local, sym};
|
||||
use clippy_utils::{can_use_if_let_chains, higher, path_to_local, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
|
||||
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp};
|
||||
@@ -10,7 +12,7 @@
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
@@ -72,10 +74,21 @@
|
||||
"checks for calls of `unwrap[_err]()` that will always fail"
|
||||
}
|
||||
|
||||
pub(crate) struct Unwrap {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl Unwrap {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self { msrv: conf.msrv }
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor that keeps track of which variables are unwrappable.
|
||||
struct UnwrappableVariablesVisitor<'a, 'tcx> {
|
||||
unwrappables: Vec<UnwrapInfo<'tcx>>,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
/// What kind of unwrappable this is.
|
||||
@@ -133,12 +146,14 @@ fn collect_unwrap_info<'tcx>(
|
||||
invert: bool,
|
||||
is_entire_condition: bool,
|
||||
) -> Vec<UnwrapInfo<'tcx>> {
|
||||
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
|
||||
is_type_diagnostic_item(cx, ty, sym::Option) && matches!(method_name, sym::is_none | sym::is_some)
|
||||
}
|
||||
|
||||
fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
|
||||
is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok)
|
||||
fn option_or_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<(UnwrappableKind, bool)> {
|
||||
match (get_type_diagnostic_name(cx, ty)?, method_name) {
|
||||
(sym::Option, sym::is_some) => Some((UnwrappableKind::Option, true)),
|
||||
(sym::Option, sym::is_none) => Some((UnwrappableKind::Option, false)),
|
||||
(sym::Result, sym::is_ok) => Some((UnwrappableKind::Result, true)),
|
||||
(sym::Result, sym::is_err) => Some((UnwrappableKind::Result, false)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
match expr.kind {
|
||||
@@ -157,15 +172,9 @@ fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol
|
||||
if let Some(local_id) = path_to_local(receiver)
|
||||
&& let ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let name = method_name.ident.name
|
||||
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) =>
|
||||
&& let Some((kind, unwrappable)) = option_or_result_call(cx, ty, name) =>
|
||||
{
|
||||
let unwrappable = matches!(name, sym::is_some | sym::is_ok);
|
||||
let safe_to_unwrap = unwrappable != invert;
|
||||
let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
|
||||
UnwrappableKind::Option
|
||||
} else {
|
||||
UnwrappableKind::Result
|
||||
};
|
||||
|
||||
vec![UnwrapInfo {
|
||||
local_id,
|
||||
@@ -357,7 +366,11 @@ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
);
|
||||
} else {
|
||||
diag.span_label(unwrappable.check.span, "the check is happening here");
|
||||
diag.help("try using `if let` or `match`");
|
||||
if can_use_if_let_chains(self.cx, self.msrv) {
|
||||
diag.help("try using `if let` or `match`");
|
||||
} else {
|
||||
diag.help("try using `match`");
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -383,7 +396,7 @@ fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]);
|
||||
impl_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Unwrap {
|
||||
fn check_fn(
|
||||
@@ -402,6 +415,7 @@ fn check_fn(
|
||||
let mut v = UnwrappableVariablesVisitor {
|
||||
unwrappables: Vec::new(),
|
||||
cx,
|
||||
msrv: self.msrv,
|
||||
};
|
||||
|
||||
walk_fn(&mut v, kind, decl, body.id(), fn_id);
|
||||
|
||||
@@ -37,8 +37,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// That's probably fine for this lint - it's pretty unlikely that someone would
|
||||
// do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too.
|
||||
&& let ecx = ConstEvalCtxt::new(cx)
|
||||
&& let Some(lhs_value) = ecx.eval_simple(left)
|
||||
&& let Some(rhs_value) = ecx.eval_simple(right)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& let Some(lhs_value) = ecx.eval_local(left, ctxt)
|
||||
&& let Some(rhs_value) = ecx.eval_local(right, ctxt)
|
||||
// FIXME(f16_f128): add these types when eq is available on all platforms
|
||||
&& (Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value)
|
||||
&& (Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value)
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_data_structures::packed::Pu128;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{ConstArgKind, ExprKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for array or vec initializations which call a function or method,
|
||||
/// Checks for array or vec initializations which contain an expression with side effects,
|
||||
/// but which have a repeat count of zero.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
@@ -73,89 +70,43 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &rustc_hir::Expr<'_>) {
|
||||
|
||||
fn inner_check(cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>, inner_expr: &'_ rustc_hir::Expr<'_>, is_vec: bool) {
|
||||
// check if expr is a call or has a call inside it
|
||||
if for_each_expr_without_closures(inner_expr, |x| {
|
||||
if let ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _) = x.kind {
|
||||
std::ops::ControlFlow::Break(())
|
||||
} else {
|
||||
std::ops::ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
if inner_expr.can_have_side_effects() {
|
||||
let parent_hir_node = cx.tcx.parent_hir_node(expr.hir_id);
|
||||
let return_type = cx.typeck_results().expr_ty(expr);
|
||||
|
||||
if let Node::LetStmt(l) = parent_hir_node {
|
||||
array_span_lint(
|
||||
cx,
|
||||
let inner_expr = snippet(cx, inner_expr.span.source_callsite(), "..");
|
||||
let vec = if is_vec { "vec!" } else { "" };
|
||||
|
||||
let (span, sugg) = match parent_hir_node {
|
||||
Node::LetStmt(l) => (
|
||||
l.span,
|
||||
inner_expr.span,
|
||||
l.pat.span,
|
||||
Some(return_type),
|
||||
is_vec,
|
||||
false,
|
||||
);
|
||||
} else if let Node::Expr(x) = parent_hir_node
|
||||
&& let ExprKind::Assign(l, _, _) = x.kind
|
||||
{
|
||||
array_span_lint(cx, x.span, inner_expr.span, l.span, Some(return_type), is_vec, true);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ZERO_REPEAT_SIDE_EFFECTS,
|
||||
expr.span.source_callsite(),
|
||||
"function or method calls as the initial value in zero-sized array initializers may cause side effects",
|
||||
"consider using",
|
||||
format!(
|
||||
"{{ {}; {}[] as {return_type} }}",
|
||||
snippet(cx, inner_expr.span.source_callsite(), ".."),
|
||||
if is_vec { "vec!" } else { "" },
|
||||
"{inner_expr}; let {var_name}: {return_type} = {vec}[];",
|
||||
var_name = snippet(cx, l.pat.span.source_callsite(), "..")
|
||||
),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
),
|
||||
Node::Expr(x) if let ExprKind::Assign(l, _, _) = x.kind => (
|
||||
x.span,
|
||||
format!(
|
||||
"{inner_expr}; {var_name} = {vec}[] as {return_type}",
|
||||
var_name = snippet(cx, l.span.source_callsite(), "..")
|
||||
),
|
||||
),
|
||||
_ => (expr.span, format!("{{ {inner_expr}; {vec}[] as {return_type} }}")),
|
||||
};
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
ZERO_REPEAT_SIDE_EFFECTS,
|
||||
span.source_callsite(),
|
||||
"expression with side effects as the initial value in a zero-sized array initializer",
|
||||
|diag| {
|
||||
diag.span_suggestion_verbose(
|
||||
span.source_callsite(),
|
||||
"consider performing the side effect separately",
|
||||
sugg,
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn array_span_lint(
|
||||
cx: &LateContext<'_>,
|
||||
expr_span: Span,
|
||||
func_call_span: Span,
|
||||
variable_name_span: Span,
|
||||
expr_ty: Option<Ty<'_>>,
|
||||
is_vec: bool,
|
||||
is_assign: bool,
|
||||
) {
|
||||
let has_ty = expr_ty.is_some();
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ZERO_REPEAT_SIDE_EFFECTS,
|
||||
expr_span.source_callsite(),
|
||||
"function or method calls as the initial value in zero-sized array initializers may cause side effects",
|
||||
"consider using",
|
||||
format!(
|
||||
"{}; {}{}{} = {}[]{}{}",
|
||||
snippet(cx, func_call_span.source_callsite(), ".."),
|
||||
if has_ty && !is_assign { "let " } else { "" },
|
||||
snippet(cx, variable_name_span.source_callsite(), ".."),
|
||||
if let Some(ty) = expr_ty
|
||||
&& !is_assign
|
||||
{
|
||||
format!(": {ty}")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
if is_vec { "vec!" } else { "" },
|
||||
if let Some(ty) = expr_ty
|
||||
&& is_assign
|
||||
{
|
||||
format!(" as {ty}")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
if is_assign { "" } else { ";" }
|
||||
),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
||||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2025-09-18
|
||||
nightly-2025-10-06
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
||||
+342
-184
@@ -5,25 +5,21 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
use crate::source::{SpanRangeExt, walk_span_to_context};
|
||||
use crate::{clip, is_direct_expn_of, sext, unsext};
|
||||
use crate::{clip, is_direct_expn_of, paths, sext, sym, unsext};
|
||||
|
||||
use rustc_abi::Size;
|
||||
use rustc_apfloat::Float;
|
||||
use rustc_apfloat::ieee::{Half, Quad};
|
||||
use rustc_ast::ast::{LitFloatType, LitKind};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{
|
||||
BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, QPath, UnOp,
|
||||
};
|
||||
use rustc_hir::{BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, PatExpr, PatExprKind, QPath, TyKind, UnOp};
|
||||
use rustc_lexer::{FrontmatterAllowed, tokenize};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::ConstValue;
|
||||
use rustc_middle::mir::interpret::{Scalar, alloc_range};
|
||||
use rustc_middle::ty::{self, FloatTy, IntTy, ScalarInt, Ty, TyCtxt, TypeckResults, UintTy};
|
||||
use rustc_middle::{bug, mir, span_bug};
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{SyntaxContext, sym};
|
||||
use rustc_span::{Symbol, SyntaxContext};
|
||||
use std::cell::Cell;
|
||||
use std::cmp::Ordering;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@@ -31,8 +27,8 @@
|
||||
|
||||
/// A `LitKind`-like enum to fold constant `Expr`s into.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Constant<'tcx> {
|
||||
Adt(mir::Const<'tcx>),
|
||||
pub enum Constant {
|
||||
Adt(ConstValue),
|
||||
/// A `String` (e.g., "abc").
|
||||
Str(String),
|
||||
/// A binary string (e.g., `b"abc"`).
|
||||
@@ -54,15 +50,15 @@ pub enum Constant<'tcx> {
|
||||
/// `true` or `false`.
|
||||
Bool(bool),
|
||||
/// An array of constants.
|
||||
Vec(Vec<Constant<'tcx>>),
|
||||
Vec(Vec<Constant>),
|
||||
/// Also an array, but with only one constant, repeated N times.
|
||||
Repeat(Box<Constant<'tcx>>, u64),
|
||||
Repeat(Box<Constant>, u64),
|
||||
/// A tuple of constants.
|
||||
Tuple(Vec<Constant<'tcx>>),
|
||||
Tuple(Vec<Constant>),
|
||||
/// A raw pointer.
|
||||
RawPtr(u128),
|
||||
/// A reference
|
||||
Ref(Box<Constant<'tcx>>),
|
||||
Ref(Box<Constant>),
|
||||
/// A literal with syntax error.
|
||||
Err,
|
||||
}
|
||||
@@ -124,7 +120,7 @@ fn bits(self) -> Self::Output {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Constant<'_> {
|
||||
impl PartialEq for Constant {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Str(ls), Self::Str(rs)) => ls == rs,
|
||||
@@ -132,16 +128,12 @@ fn eq(&self, other: &Self) -> bool {
|
||||
(&Self::Char(l), &Self::Char(r)) => l == r,
|
||||
(&Self::Int(l), &Self::Int(r)) => l == r,
|
||||
(&Self::F64(l), &Self::F64(r)) => {
|
||||
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
|
||||
// `Fw32 == Fw64`, so don’t compare them.
|
||||
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
|
||||
l.to_bits() == r.to_bits()
|
||||
// `to_bits` is required to catch non-matching `0.0` and `-0.0`.
|
||||
l.to_bits() == r.to_bits() && !l.is_nan()
|
||||
},
|
||||
(&Self::F32(l), &Self::F32(r)) => {
|
||||
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
|
||||
// `Fw32 == Fw64`, so don’t compare them.
|
||||
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
|
||||
f64::from(l).to_bits() == f64::from(r).to_bits()
|
||||
// `to_bits` is required to catch non-matching `0.0` and `-0.0`.
|
||||
l.to_bits() == r.to_bits() && !l.is_nan()
|
||||
},
|
||||
(&Self::Bool(l), &Self::Bool(r)) => l == r,
|
||||
(&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r,
|
||||
@@ -153,7 +145,7 @@ fn eq(&self, other: &Self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Constant<'_> {
|
||||
impl Hash for Constant {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
@@ -209,7 +201,7 @@ fn hash<H>(&self, state: &mut H)
|
||||
}
|
||||
}
|
||||
|
||||
impl Constant<'_> {
|
||||
impl Constant {
|
||||
pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option<Ordering> {
|
||||
match (left, right) {
|
||||
(Self::Str(ls), Self::Str(rs)) => Some(ls.cmp(rs)),
|
||||
@@ -297,10 +289,129 @@ fn parse_f128(s: &str) -> Self {
|
||||
let f: Quad = s.parse().unwrap();
|
||||
Self::F128(f.to_bits())
|
||||
}
|
||||
|
||||
pub fn new_numeric_min<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Self> {
|
||||
match *ty.kind() {
|
||||
ty::Uint(_) => Some(Self::Int(0)),
|
||||
ty::Int(ty) => {
|
||||
let val = match ty.normalize(tcx.sess.target.pointer_width) {
|
||||
IntTy::I8 => i128::from(i8::MIN),
|
||||
IntTy::I16 => i128::from(i16::MIN),
|
||||
IntTy::I32 => i128::from(i32::MIN),
|
||||
IntTy::I64 => i128::from(i64::MIN),
|
||||
IntTy::I128 => i128::MIN,
|
||||
IntTy::Isize => return None,
|
||||
};
|
||||
Some(Self::Int(val.cast_unsigned()))
|
||||
},
|
||||
ty::Char => Some(Self::Char(char::MIN)),
|
||||
ty::Float(FloatTy::F32) => Some(Self::F32(f32::NEG_INFINITY)),
|
||||
ty::Float(FloatTy::F64) => Some(Self::F64(f64::NEG_INFINITY)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_numeric_max<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Self> {
|
||||
match *ty.kind() {
|
||||
ty::Uint(ty) => Some(Self::Int(match ty.normalize(tcx.sess.target.pointer_width) {
|
||||
UintTy::U8 => u128::from(u8::MAX),
|
||||
UintTy::U16 => u128::from(u16::MAX),
|
||||
UintTy::U32 => u128::from(u32::MAX),
|
||||
UintTy::U64 => u128::from(u64::MAX),
|
||||
UintTy::U128 => u128::MAX,
|
||||
UintTy::Usize => return None,
|
||||
})),
|
||||
ty::Int(ty) => {
|
||||
let val = match ty.normalize(tcx.sess.target.pointer_width) {
|
||||
IntTy::I8 => i128::from(i8::MAX),
|
||||
IntTy::I16 => i128::from(i16::MAX),
|
||||
IntTy::I32 => i128::from(i32::MAX),
|
||||
IntTy::I64 => i128::from(i64::MAX),
|
||||
IntTy::I128 => i128::MAX,
|
||||
IntTy::Isize => return None,
|
||||
};
|
||||
Some(Self::Int(val.cast_unsigned()))
|
||||
},
|
||||
ty::Char => Some(Self::Char(char::MAX)),
|
||||
ty::Float(FloatTy::F32) => Some(Self::F32(f32::INFINITY)),
|
||||
ty::Float(FloatTy::F64) => Some(Self::F64(f64::INFINITY)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_numeric_min<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match (self, ty.kind()) {
|
||||
(&Self::Int(x), &ty::Uint(_)) => x == 0,
|
||||
(&Self::Int(x), &ty::Int(ty)) => {
|
||||
let limit = match ty.normalize(tcx.sess.target.pointer_width) {
|
||||
IntTy::I8 => i128::from(i8::MIN),
|
||||
IntTy::I16 => i128::from(i16::MIN),
|
||||
IntTy::I32 => i128::from(i32::MIN),
|
||||
IntTy::I64 => i128::from(i64::MIN),
|
||||
IntTy::I128 => i128::MIN,
|
||||
IntTy::Isize => return false,
|
||||
};
|
||||
x.cast_signed() == limit
|
||||
},
|
||||
(&Self::Char(x), &ty::Char) => x == char::MIN,
|
||||
(&Self::F32(x), &ty::Float(FloatTy::F32)) => x == f32::NEG_INFINITY,
|
||||
(&Self::F64(x), &ty::Float(FloatTy::F64)) => x == f64::NEG_INFINITY,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_numeric_max<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match (self, ty.kind()) {
|
||||
(&Self::Int(x), &ty::Uint(ty)) => {
|
||||
let limit = match ty.normalize(tcx.sess.target.pointer_width) {
|
||||
UintTy::U8 => u128::from(u8::MAX),
|
||||
UintTy::U16 => u128::from(u16::MAX),
|
||||
UintTy::U32 => u128::from(u32::MAX),
|
||||
UintTy::U64 => u128::from(u64::MAX),
|
||||
UintTy::U128 => u128::MAX,
|
||||
UintTy::Usize => return false,
|
||||
};
|
||||
x == limit
|
||||
},
|
||||
(&Self::Int(x), &ty::Int(ty)) => {
|
||||
let limit = match ty.normalize(tcx.sess.target.pointer_width) {
|
||||
IntTy::I8 => i128::from(i8::MAX),
|
||||
IntTy::I16 => i128::from(i16::MAX),
|
||||
IntTy::I32 => i128::from(i32::MAX),
|
||||
IntTy::I64 => i128::from(i64::MAX),
|
||||
IntTy::I128 => i128::MAX,
|
||||
IntTy::Isize => return false,
|
||||
};
|
||||
x.cast_signed() == limit
|
||||
},
|
||||
(&Self::Char(x), &ty::Char) => x == char::MAX,
|
||||
(&Self::F32(x), &ty::Float(FloatTy::F32)) => x == f32::INFINITY,
|
||||
(&Self::F64(x), &ty::Float(FloatTy::F64)) => x == f64::INFINITY,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_pos_infinity(&self) -> bool {
|
||||
match *self {
|
||||
// FIXME(f16_f128): add f16 and f128 when constants are available
|
||||
Constant::F32(x) => x == f32::INFINITY,
|
||||
Constant::F64(x) => x == f64::INFINITY,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_neg_infinity(&self) -> bool {
|
||||
match *self {
|
||||
// FIXME(f16_f128): add f16 and f128 when constants are available
|
||||
Constant::F32(x) => x == f32::NEG_INFINITY,
|
||||
Constant::F64(x) => x == f64::NEG_INFINITY,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a `LitKind` to a `Constant`.
|
||||
pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option<Ty<'tcx>>) -> Constant<'tcx> {
|
||||
pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
|
||||
match *lit {
|
||||
LitKind::Str(ref is, _) => Constant::Str(is.to_string()),
|
||||
LitKind::Byte(b) => Constant::Int(u128::from(b)),
|
||||
@@ -331,10 +442,9 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option<Ty<'tcx>>) -> Constan
|
||||
pub enum ConstantSource {
|
||||
/// The value is determined solely from the expression.
|
||||
Local,
|
||||
/// The value is dependent on a defined constant.
|
||||
Constant,
|
||||
/// The value is dependent on a constant defined in `core` crate.
|
||||
CoreConstant,
|
||||
/// The value is dependent on another definition that may change independently from the local
|
||||
/// expression.
|
||||
NonLocal,
|
||||
}
|
||||
impl ConstantSource {
|
||||
pub fn is_local(self) -> bool {
|
||||
@@ -387,6 +497,7 @@ pub struct ConstEvalCtxt<'tcx> {
|
||||
typing_env: ty::TypingEnv<'tcx>,
|
||||
typeck: &'tcx TypeckResults<'tcx>,
|
||||
source: Cell<ConstantSource>,
|
||||
ctxt: Cell<SyntaxContext>,
|
||||
}
|
||||
|
||||
impl<'tcx> ConstEvalCtxt<'tcx> {
|
||||
@@ -398,6 +509,7 @@ pub fn new(cx: &LateContext<'tcx>) -> Self {
|
||||
typing_env: cx.typing_env(),
|
||||
typeck: cx.typeck_results(),
|
||||
source: Cell::new(ConstantSource::Local),
|
||||
ctxt: Cell::new(SyntaxContext::root()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,38 +520,50 @@ pub fn with_env(tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, typeck: &'tc
|
||||
typing_env,
|
||||
typeck,
|
||||
source: Cell::new(ConstantSource::Local),
|
||||
ctxt: Cell::new(SyntaxContext::root()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to evaluate the expression and returns both the value and whether it's dependant on
|
||||
/// other items.
|
||||
pub fn eval_with_source(&self, e: &Expr<'_>) -> Option<(Constant<'tcx>, ConstantSource)> {
|
||||
pub fn eval_with_source(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<(Constant, ConstantSource)> {
|
||||
self.source.set(ConstantSource::Local);
|
||||
self.ctxt.set(ctxt);
|
||||
self.expr(e).map(|c| (c, self.source.get()))
|
||||
}
|
||||
|
||||
/// Attempts to evaluate the expression.
|
||||
pub fn eval(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
pub fn eval(&self, e: &Expr<'_>) -> Option<Constant> {
|
||||
self.expr(e)
|
||||
}
|
||||
|
||||
/// Attempts to evaluate the expression without accessing other items.
|
||||
pub fn eval_simple(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
match self.eval_with_source(e) {
|
||||
///
|
||||
/// The context argument is the context used to view the evaluated expression. e.g. when
|
||||
/// evaluating the argument in `f(m!(1))` the context of the call expression should be used.
|
||||
/// This is need so the const evaluator can see the `m` macro and marke the evaluation as
|
||||
/// non-local independant of what the macro expands to.
|
||||
pub fn eval_local(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<Constant> {
|
||||
match self.eval_with_source(e, ctxt) {
|
||||
Some((x, ConstantSource::Local)) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to evaluate the expression as an integer without accessing other items.
|
||||
pub fn eval_full_int(&self, e: &Expr<'_>) -> Option<FullInt> {
|
||||
match self.eval_with_source(e) {
|
||||
///
|
||||
/// The context argument is the context used to view the evaluated expression. e.g. when
|
||||
/// evaluating the argument in `f(m!(1))` the context of the call expression should be used.
|
||||
/// This is need so the const evaluator can see the `m` macro and marke the evaluation as
|
||||
/// non-local independant of what the macro expands to.
|
||||
pub fn eval_full_int(&self, e: &Expr<'_>, ctxt: SyntaxContext) -> Option<FullInt> {
|
||||
match self.eval_with_source(e, ctxt) {
|
||||
Some((x, ConstantSource::Local)) => x.int_value(self.tcx, self.typeck.expr_ty(e)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option<Constant<'tcx>> {
|
||||
pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option<Constant> {
|
||||
match &pat_expr.kind {
|
||||
PatExprKind::Lit { lit, negated } => {
|
||||
let ty = self.typeck.node_type_opt(pat_expr.hir_id);
|
||||
@@ -455,39 +579,31 @@ pub fn eval_pat_expr(&self, pat_expr: &PatExpr<'_>) -> Option<Constant<'tcx>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option<Constant<'tcx>> {
|
||||
let is_core_crate = if let Some(def_id) = self.typeck.qpath_res(qpath, hir_id).opt_def_id() {
|
||||
self.tcx.crate_name(def_id.krate) == sym::core
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.fetch_path_and_apply(qpath, hir_id, self.typeck.node_type(hir_id), |self_, result| {
|
||||
let result = mir_to_const(self_.tcx, result)?;
|
||||
// If source is already Constant we wouldn't want to override it with CoreConstant
|
||||
self_.source.set(
|
||||
if is_core_crate && !matches!(self_.source.get(), ConstantSource::Constant) {
|
||||
ConstantSource::CoreConstant
|
||||
} else {
|
||||
ConstantSource::Constant
|
||||
},
|
||||
);
|
||||
Some(result)
|
||||
})
|
||||
fn check_ctxt(&self, ctxt: SyntaxContext) {
|
||||
if self.ctxt.get() != ctxt {
|
||||
self.source.set(ConstantSource::NonLocal);
|
||||
}
|
||||
}
|
||||
|
||||
fn qpath(&self, qpath: &QPath<'_>, hir_id: HirId) -> Option<Constant> {
|
||||
self.fetch_path(qpath, hir_id)
|
||||
.and_then(|c| mir_to_const(self.tcx, c, self.typeck.node_type(hir_id)))
|
||||
}
|
||||
|
||||
/// Simple constant folding: Insert an expression, get a constant or none.
|
||||
fn expr(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
fn expr(&self, e: &Expr<'_>) -> Option<Constant> {
|
||||
self.check_ctxt(e.span.ctxt());
|
||||
match e.kind {
|
||||
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.tcx.hir_body(body).value),
|
||||
ExprKind::DropTemps(e) => self.expr(e),
|
||||
ExprKind::Path(ref qpath) => self.qpath(qpath, e.hir_id),
|
||||
ExprKind::Block(block, _) => self.block(block),
|
||||
ExprKind::Block(block, _) => {
|
||||
self.check_ctxt(block.span.ctxt());
|
||||
self.block(block)
|
||||
},
|
||||
ExprKind::Lit(lit) => {
|
||||
if is_direct_expn_of(e.span, sym::cfg).is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e)))
|
||||
}
|
||||
self.check_ctxt(lit.span.ctxt());
|
||||
Some(lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(e)))
|
||||
},
|
||||
ExprKind::Array(vec) => self.multi(vec).map(Constant::Vec),
|
||||
ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple),
|
||||
@@ -504,7 +620,10 @@ fn expr(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }),
|
||||
}),
|
||||
ExprKind::If(cond, then, ref otherwise) => self.ifthenelse(cond, then, *otherwise),
|
||||
ExprKind::Binary(op, left, right) => self.binop(op.node, left, right),
|
||||
ExprKind::Binary(op, left, right) => {
|
||||
self.check_ctxt(e.span.ctxt());
|
||||
self.binop(op.node, left, right)
|
||||
},
|
||||
ExprKind::Call(callee, []) => {
|
||||
// We only handle a few const functions for now.
|
||||
if let ExprKind::Path(qpath) = &callee.kind
|
||||
@@ -524,17 +643,20 @@ fn expr(&self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
},
|
||||
ExprKind::Index(arr, index, _) => self.index(arr, index),
|
||||
ExprKind::AddrOf(_, _, inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))),
|
||||
ExprKind::Field(local_expr, ref field) => {
|
||||
let result = self.expr(local_expr);
|
||||
if let Some(Constant::Adt(constant)) = &self.expr(local_expr)
|
||||
&& let ty::Adt(adt_def, _) = constant.ty().kind()
|
||||
ExprKind::Field(base, ref field)
|
||||
if let base_ty = self.typeck.expr_ty(base)
|
||||
&& match self.typeck.expr_adjustments(base) {
|
||||
[] => true,
|
||||
[.., a] => a.target == base_ty,
|
||||
}
|
||||
&& let Some(Constant::Adt(constant)) = self.expr(base)
|
||||
&& let ty::Adt(adt_def, _) = *base_ty.kind()
|
||||
&& adt_def.is_struct()
|
||||
&& let Some(desired_field) = field_of_struct(*adt_def, self.tcx, *constant, field)
|
||||
{
|
||||
mir_to_const(self.tcx, desired_field)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
&& let Some((desired_field, ty)) =
|
||||
field_of_struct(adt_def, self.tcx, constant, base_ty, field.name) =>
|
||||
{
|
||||
self.check_ctxt(field.span.ctxt());
|
||||
mir_to_const(self.tcx, desired_field, ty)
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
@@ -547,19 +669,6 @@ pub fn eval_is_empty(&self, e: &Expr<'_>) -> Option<bool> {
|
||||
match e.kind {
|
||||
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.eval_is_empty(self.tcx.hir_body(body).value),
|
||||
ExprKind::DropTemps(e) => self.eval_is_empty(e),
|
||||
ExprKind::Path(ref qpath) => {
|
||||
if !self
|
||||
.typeck
|
||||
.qpath_res(qpath, e.hir_id)
|
||||
.opt_def_id()
|
||||
.is_some_and(DefId::is_local)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
self.fetch_path_and_apply(qpath, e.hir_id, self.typeck.expr_ty(e), |self_, result| {
|
||||
mir_is_empty(self_.tcx, result)
|
||||
})
|
||||
},
|
||||
ExprKind::Lit(lit) => {
|
||||
if is_direct_expn_of(e.span, sym::cfg).is_some() {
|
||||
None
|
||||
@@ -584,7 +693,7 @@ pub fn eval_is_empty(&self, e: &Expr<'_>) -> Option<bool> {
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_wrap)]
|
||||
fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tcx>> {
|
||||
fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
|
||||
use self::Constant::{Bool, Int};
|
||||
match *o {
|
||||
Bool(b) => Some(Bool(!b)),
|
||||
@@ -600,7 +709,7 @@ fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tcx>>
|
||||
}
|
||||
}
|
||||
|
||||
fn constant_negate(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tcx>> {
|
||||
fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option<Constant> {
|
||||
use self::Constant::{F32, F64, Int};
|
||||
match *o {
|
||||
Int(value) => {
|
||||
@@ -626,48 +735,128 @@ fn constant_negate(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tc
|
||||
|
||||
/// Create `Some(Vec![..])` of all constants, unless there is any
|
||||
/// non-constant part.
|
||||
fn multi(&self, vec: &[Expr<'_>]) -> Option<Vec<Constant<'tcx>>> {
|
||||
fn multi(&self, vec: &[Expr<'_>]) -> Option<Vec<Constant>> {
|
||||
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
|
||||
}
|
||||
|
||||
/// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it.
|
||||
fn fetch_path_and_apply<T, F>(&self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&Self, mir::Const<'tcx>) -> Option<T>,
|
||||
{
|
||||
let res = self.typeck.qpath_res(qpath, id);
|
||||
match res {
|
||||
Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
|
||||
// Check if this constant is based on `cfg!(..)`,
|
||||
// which is NOT constant for our purposes.
|
||||
if let Some(node) = self.tcx.hir_get_if_local(def_id)
|
||||
&& let Node::Item(Item {
|
||||
kind: ItemKind::Const(.., body_id),
|
||||
..
|
||||
}) = node
|
||||
&& let Node::Expr(Expr {
|
||||
kind: ExprKind::Lit(_),
|
||||
span,
|
||||
..
|
||||
}) = self.tcx.hir_node(body_id.hir_id)
|
||||
&& is_direct_expn_of(*span, sym::cfg).is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let args = self.typeck.node_args(id);
|
||||
let result = self
|
||||
.tcx
|
||||
.const_eval_resolve(self.typing_env, mir::UnevaluatedConst::new(def_id, args), qpath.span())
|
||||
.ok()
|
||||
.map(|val| mir::Const::from_value(val, ty))?;
|
||||
f(self, result)
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn fetch_path(&self, qpath: &QPath<'_>, id: HirId) -> Option<ConstValue> {
|
||||
// Resolve the path to a constant and check if that constant is known to
|
||||
// not change based on the target.
|
||||
//
|
||||
// This should be replaced with an attribute at some point.
|
||||
let did = match *qpath {
|
||||
QPath::Resolved(None, path)
|
||||
if path.span.ctxt() == self.ctxt.get()
|
||||
&& path.segments.iter().all(|s| self.ctxt.get() == s.ident.span.ctxt())
|
||||
&& let Res::Def(DefKind::Const, did) = path.res
|
||||
&& (matches!(
|
||||
self.tcx.get_diagnostic_name(did),
|
||||
Some(
|
||||
sym::f32_legacy_const_digits
|
||||
| sym::f32_legacy_const_epsilon
|
||||
| sym::f32_legacy_const_infinity
|
||||
| sym::f32_legacy_const_mantissa_dig
|
||||
| sym::f32_legacy_const_max
|
||||
| sym::f32_legacy_const_max_10_exp
|
||||
| sym::f32_legacy_const_max_exp
|
||||
| sym::f32_legacy_const_min
|
||||
| sym::f32_legacy_const_min_10_exp
|
||||
| sym::f32_legacy_const_min_exp
|
||||
| sym::f32_legacy_const_min_positive
|
||||
| sym::f32_legacy_const_nan
|
||||
| sym::f32_legacy_const_neg_infinity
|
||||
| sym::f32_legacy_const_radix
|
||||
| sym::f64_legacy_const_digits
|
||||
| sym::f64_legacy_const_epsilon
|
||||
| sym::f64_legacy_const_infinity
|
||||
| sym::f64_legacy_const_mantissa_dig
|
||||
| sym::f64_legacy_const_max
|
||||
| sym::f64_legacy_const_max_10_exp
|
||||
| sym::f64_legacy_const_max_exp
|
||||
| sym::f64_legacy_const_min
|
||||
| sym::f64_legacy_const_min_10_exp
|
||||
| sym::f64_legacy_const_min_exp
|
||||
| sym::f64_legacy_const_min_positive
|
||||
| sym::f64_legacy_const_nan
|
||||
| sym::f64_legacy_const_neg_infinity
|
||||
| sym::f64_legacy_const_radix
|
||||
| sym::u8_legacy_const_min
|
||||
| sym::u16_legacy_const_min
|
||||
| sym::u32_legacy_const_min
|
||||
| sym::u64_legacy_const_min
|
||||
| sym::u128_legacy_const_min
|
||||
| sym::usize_legacy_const_min
|
||||
| sym::u8_legacy_const_max
|
||||
| sym::u16_legacy_const_max
|
||||
| sym::u32_legacy_const_max
|
||||
| sym::u64_legacy_const_max
|
||||
| sym::u128_legacy_const_max
|
||||
| sym::i8_legacy_const_min
|
||||
| sym::i16_legacy_const_min
|
||||
| sym::i32_legacy_const_min
|
||||
| sym::i64_legacy_const_min
|
||||
| sym::i128_legacy_const_min
|
||||
| sym::i8_legacy_const_max
|
||||
| sym::i16_legacy_const_max
|
||||
| sym::i32_legacy_const_max
|
||||
| sym::i64_legacy_const_max
|
||||
| sym::i128_legacy_const_max
|
||||
)
|
||||
) || self.tcx.opt_parent(did).is_some_and(|parent| {
|
||||
paths::F16_CONSTS.matches(&self.tcx, parent)
|
||||
|| paths::F32_CONSTS.matches(&self.tcx, parent)
|
||||
|| paths::F64_CONSTS.matches(&self.tcx, parent)
|
||||
|| paths::F128_CONSTS.matches(&self.tcx, parent)
|
||||
})) =>
|
||||
{
|
||||
did
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
QPath::TypeRelative(ty, const_name)
|
||||
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind
|
||||
&& let [.., ty_name] = ty_path.segments
|
||||
&& (matches!(
|
||||
ty_name.ident.name,
|
||||
sym::i8
|
||||
| sym::i16
|
||||
| sym::i32
|
||||
| sym::i64
|
||||
| sym::i128
|
||||
| sym::u8
|
||||
| sym::u16
|
||||
| sym::u32
|
||||
| sym::u64
|
||||
| sym::u128
|
||||
| sym::f32
|
||||
| sym::f64
|
||||
| sym::char
|
||||
) || (ty_name.ident.name == sym::usize && const_name.ident.name == sym::MIN))
|
||||
&& const_name.ident.span.ctxt() == self.ctxt.get()
|
||||
&& ty.span.ctxt() == self.ctxt.get()
|
||||
&& ty_name.ident.span.ctxt() == self.ctxt.get()
|
||||
&& matches!(ty_path.res, Res::PrimTy(_))
|
||||
&& let Some((DefKind::AssocConst, did)) = self.typeck.type_dependent_def(id) =>
|
||||
{
|
||||
did
|
||||
},
|
||||
_ if let Res::Def(DefKind::Const | DefKind::AssocConst, did) = self.typeck.qpath_res(qpath, id) => {
|
||||
self.source.set(ConstantSource::NonLocal);
|
||||
did
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
self.tcx
|
||||
.const_eval_resolve(
|
||||
self.typing_env,
|
||||
mir::UnevaluatedConst::new(did, self.typeck.node_args(id)),
|
||||
qpath.span(),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant> {
|
||||
let lhs = self.expr(lhs);
|
||||
let index = self.expr(index);
|
||||
|
||||
@@ -697,7 +886,7 @@ fn index(&self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option<Constant<'tcx>
|
||||
}
|
||||
|
||||
/// A block can only yield a constant if it has exactly one constant expression.
|
||||
fn block(&self, block: &Block<'_>) -> Option<Constant<'tcx>> {
|
||||
fn block(&self, block: &Block<'_>) -> Option<Constant> {
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
{
|
||||
@@ -716,11 +905,11 @@ fn block(&self, block: &Block<'_>) -> Option<Constant<'tcx>> {
|
||||
.filter(|t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi))
|
||||
.eq([OpenBrace])
|
||||
{
|
||||
self.source.set(ConstantSource::Constant);
|
||||
self.source.set(ConstantSource::NonLocal);
|
||||
}
|
||||
} else {
|
||||
// Unable to access the source. Assume a non-local dependency.
|
||||
self.source.set(ConstantSource::Constant);
|
||||
self.source.set(ConstantSource::NonLocal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +919,7 @@ fn block(&self, block: &Block<'_>) -> Option<Constant<'tcx>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option<Constant<'tcx>> {
|
||||
fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option<Constant> {
|
||||
if let Some(Constant::Bool(b)) = self.expr(cond) {
|
||||
if b {
|
||||
self.expr(then)
|
||||
@@ -742,7 +931,7 @@ fn ifthenelse(&self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'
|
||||
}
|
||||
}
|
||||
|
||||
fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option<Constant<'tcx>> {
|
||||
fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option<Constant> {
|
||||
let l = self.expr(left)?;
|
||||
let r = self.expr(right);
|
||||
match (l, r) {
|
||||
@@ -778,6 +967,7 @@ fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option<Cons
|
||||
BinOpKind::BitXor => Some(zext(l ^ r)),
|
||||
BinOpKind::BitOr => Some(zext(l | r)),
|
||||
BinOpKind::BitAnd => Some(zext(l & r)),
|
||||
// FIXME: f32/f64 currently consider `0.0` and `-0.0` as different.
|
||||
BinOpKind::Eq => Some(Constant::Bool(l == r)),
|
||||
BinOpKind::Ne => Some(Constant::Bool(l != r)),
|
||||
BinOpKind::Lt => Some(Constant::Bool(l < r)),
|
||||
@@ -856,14 +1046,10 @@ fn binop(&self, op: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> Option<Cons
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option<Constant<'tcx>> {
|
||||
let mir::Const::Val(val, _) = result else {
|
||||
// We only work on evaluated consts.
|
||||
return None;
|
||||
};
|
||||
match (val, result.ty().kind()) {
|
||||
(ConstValue::Scalar(Scalar::Int(int)), _) => match result.ty().kind() {
|
||||
ty::Adt(adt_def, _) if adt_def.is_struct() => Some(Constant::Adt(result)),
|
||||
pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, val: ConstValue, ty: Ty<'tcx>) -> Option<Constant> {
|
||||
match (val, ty.kind()) {
|
||||
(_, &ty::Adt(adt_def, _)) if adt_def.is_struct() => Some(Constant::Adt(val)),
|
||||
(ConstValue::Scalar(Scalar::Int(int)), _) => match ty.kind() {
|
||||
ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)),
|
||||
ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.to_bits(int.size()))),
|
||||
ty::Float(FloatTy::F16) => Some(Constant::F16(int.into())),
|
||||
@@ -877,7 +1063,6 @@ pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option
|
||||
let data = val.try_get_slice_bytes_for_diagnostics(tcx)?;
|
||||
String::from_utf8(data.to_owned()).ok().map(Constant::Str)
|
||||
},
|
||||
(_, ty::Adt(adt_def, _)) if adt_def.is_struct() => Some(Constant::Adt(result)),
|
||||
(ConstValue::Indirect { alloc_id, offset }, ty::Array(sub_type, len)) => {
|
||||
let alloc = tcx.global_alloc(alloc_id).unwrap_memory().inner();
|
||||
let len = len.try_to_target_usize(tcx)?;
|
||||
@@ -902,64 +1087,32 @@ pub fn mir_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option
|
||||
}
|
||||
}
|
||||
|
||||
fn mir_is_empty<'tcx>(tcx: TyCtxt<'tcx>, result: mir::Const<'tcx>) -> Option<bool> {
|
||||
let mir::Const::Val(val, _) = result else {
|
||||
// We only work on evaluated consts.
|
||||
return None;
|
||||
};
|
||||
match (val, result.ty().kind()) {
|
||||
(_, ty::Ref(_, inner_ty, _)) => match inner_ty.kind() {
|
||||
ty::Str | ty::Slice(_) => {
|
||||
if let ConstValue::Indirect { alloc_id, offset } = val {
|
||||
// Get the length from the slice, using the same formula as
|
||||
// [`ConstValue::try_get_slice_bytes_for_diagnostics`].
|
||||
let a = tcx.global_alloc(alloc_id).unwrap_memory().inner();
|
||||
let ptr_size = tcx.data_layout.pointer_size();
|
||||
if a.size() < offset + 2 * ptr_size {
|
||||
// (partially) dangling reference
|
||||
return None;
|
||||
}
|
||||
let len = a
|
||||
.read_scalar(&tcx, alloc_range(offset + ptr_size, ptr_size), false)
|
||||
.ok()?
|
||||
.to_target_usize(&tcx)
|
||||
.discard_err()?;
|
||||
Some(len == 0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
ty::Array(_, len) => Some(len.try_to_target_usize(tcx)? == 0),
|
||||
_ => None,
|
||||
},
|
||||
(ConstValue::Indirect { .. }, ty::Array(_, len)) => Some(len.try_to_target_usize(tcx)? == 0),
|
||||
(ConstValue::ZeroSized, _) => Some(true),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field_of_struct<'tcx>(
|
||||
adt_def: ty::AdtDef<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
result: mir::Const<'tcx>,
|
||||
field: &Ident,
|
||||
) -> Option<mir::Const<'tcx>> {
|
||||
if let mir::Const::Val(result, ty) = result
|
||||
&& let Some(dc) = tcx.try_destructure_mir_constant_for_user_output(result, ty)
|
||||
value: ConstValue,
|
||||
ty: Ty<'tcx>,
|
||||
field: Symbol,
|
||||
) -> Option<(ConstValue, Ty<'tcx>)> {
|
||||
if let Some(dc) = tcx.try_destructure_mir_constant_for_user_output(value, ty)
|
||||
&& let Some(dc_variant) = dc.variant
|
||||
&& let Some(variant) = adt_def.variants().get(dc_variant)
|
||||
&& let Some(field_idx) = variant.fields.iter().position(|el| el.name == field.name)
|
||||
&& let Some(&(val, ty)) = dc.fields.get(field_idx)
|
||||
&& let Some(field_idx) = variant.fields.iter().position(|el| el.name == field)
|
||||
{
|
||||
Some(mir::Const::Val(val, ty))
|
||||
dc.fields.get(field_idx).copied()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// If `expr` evaluates to an integer constant, return its value.
|
||||
pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_simple(expr) {
|
||||
///
|
||||
/// The context argument is the context used to view the evaluated expression. e.g. when evaluating
|
||||
/// the argument in `f(m!(1))` the context of the call expression should be used. This is need so
|
||||
/// the const evaluator can see the `m` macro and marke the evaluation as non-local independant of
|
||||
/// what the macro expands to.
|
||||
pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> Option<u128> {
|
||||
if let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
@@ -967,7 +1120,12 @@ pub fn integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
}
|
||||
|
||||
/// Check if `expr` evaluates to an integer constant of 0.
|
||||
///
|
||||
/// The context argument is the context used to view the evaluated expression. e.g. when evaluating
|
||||
/// the argument in `f(m!(1))` the context of the call expression should be used. This is need so
|
||||
/// the const evaluator can see the `m` macro and marke the evaluation as non-local independant of
|
||||
/// what the macro expands to.
|
||||
#[inline]
|
||||
pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
integer_const(cx, expr) == Some(0)
|
||||
pub fn is_zero_integer_const(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
|
||||
integer_const(cx, expr, ctxt) == Some(0)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user