Rollup merge of #120806 - flip1995:clippy-subtree-update, r=Manishearth
Clippy subtree update r? `@Manishearth`
This commit is contained in:
commit
8f00ffc901
131 changed files with 3886 additions and 621 deletions
|
|
@ -499,6 +499,7 @@ struct NotSimplificationVisitor<'a, 'tcx> {
|
|||
impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
|
||||
&& !expr.span.from_expansion()
|
||||
&& !inner.span.from_expansion()
|
||||
&& let Some(suggestion) = simplify_not(self.cx, inner)
|
||||
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
|
||||
|
|
|
|||
168
clippy_lints/src/cargo/lint_groups_priority.rs
Normal file
168
clippy_lints/src/cargo/lint_groups_priority.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
use super::LINT_GROUPS_PRIORITY;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{unerased_lint_store, LateContext};
|
||||
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)]
|
||||
struct Lints {
|
||||
#[serde(default)]
|
||||
rust: LintTable,
|
||||
#[serde(default)]
|
||||
clippy: LintTable,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct CargoToml {
|
||||
lints: Lints,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct LintsAndGroups {
|
||||
lints: Vec<Spanned<String>>,
|
||||
groups: Vec<(Spanned<String>, Spanned<LintConfig>)>,
|
||||
}
|
||||
|
||||
fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
|
||||
Span::new(
|
||||
file.start_pos + BytePos::from_usize(range.start),
|
||||
file.start_pos + BytePos::from_usize(range.end),
|
||||
SyntaxContext::root(),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn check_table(cx: &LateContext<'_>, table: LintTable, groups: &FxHashSet<&str>, file: &SourceFile) {
|
||||
let mut by_priority = BTreeMap::<_, LintsAndGroups>::new();
|
||||
for (name, config) in table {
|
||||
let lints_and_groups = by_priority.entry(config.as_ref().priority()).or_default();
|
||||
if groups.contains(name.get_ref().as_str()) {
|
||||
lints_and_groups.groups.push((name, config));
|
||||
} else {
|
||||
lints_and_groups.lints.push(name);
|
||||
}
|
||||
}
|
||||
let low_priority = by_priority
|
||||
.iter()
|
||||
.find(|(_, lints_and_groups)| !lints_and_groups.lints.is_empty())
|
||||
.map_or(-1, |(&lowest_lint_priority, _)| lowest_lint_priority - 1);
|
||||
|
||||
for (priority, LintsAndGroups { lints, groups }) in by_priority {
|
||||
let Some(last_lint_alphabetically) = lints.last() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for (group, config) in groups {
|
||||
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()
|
||||
),
|
||||
|diag| {
|
||||
let config_span = toml_span(config.span(), file);
|
||||
if config.as_ref().is_implicit() {
|
||||
diag.span_label(config_span, "has an implicit priority of 0");
|
||||
}
|
||||
// add the label to next lint after this group that has the same priority
|
||||
let lint = lints
|
||||
.iter()
|
||||
.filter(|lint| lint.span().start > group.span().start)
|
||||
.min_by_key(|lint| lint.span().start)
|
||||
.unwrap_or(last_lint_alphabetically);
|
||||
diag.span_label(toml_span(lint.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();
|
||||
Serialize::serialize(
|
||||
&LintConfigTable {
|
||||
level: config.as_ref().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,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 mut rustc_groups = FxHashSet::default();
|
||||
let mut clippy_groups = FxHashSet::default();
|
||||
for (group, ..) in unerased_lint_store(cx.tcx.sess).get_lint_groups() {
|
||||
match group.split_once("::") {
|
||||
None => {
|
||||
rustc_groups.insert(group);
|
||||
},
|
||||
Some(("clippy", group)) => {
|
||||
clippy_groups.insert(group);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file);
|
||||
check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
mod common_metadata;
|
||||
mod feature_name;
|
||||
mod lint_groups_priority;
|
||||
mod multiple_crate_versions;
|
||||
mod wildcard_dependencies;
|
||||
|
||||
|
|
@ -165,6 +166,43 @@ declare_clippy_lint! {
|
|||
"wildcard dependencies being used"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for lint groups with the same priority as lints in the `Cargo.toml`
|
||||
/// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section).
|
||||
///
|
||||
/// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918)
|
||||
/// is resolved.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The order of lints in the `[lints]` is ignored, to have a lint override a group the
|
||||
/// `priority` field needs to be used, otherwise the sort order is undefined.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Does not check lints inherited using `lints.workspace = true`
|
||||
///
|
||||
/// ### Example
|
||||
/// ```toml
|
||||
/// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic`
|
||||
/// # which results in `similar_names` being `warn`
|
||||
/// [lints.clippy]
|
||||
/// pedantic = "warn"
|
||||
/// similar_names = "allow"
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```toml
|
||||
/// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names`
|
||||
/// # which results in `similar_names` being `allow`
|
||||
/// [lints.clippy]
|
||||
/// pedantic = { level = "warn", priority = -1 }
|
||||
/// similar_names = "allow"
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub LINT_GROUPS_PRIORITY,
|
||||
correctness,
|
||||
"a lint group in `Cargo.toml` at the same priority as a lint"
|
||||
}
|
||||
|
||||
pub struct Cargo {
|
||||
pub allowed_duplicate_crates: FxHashSet<String>,
|
||||
pub ignore_publish: bool,
|
||||
|
|
@ -175,7 +213,8 @@ impl_lint_pass!(Cargo => [
|
|||
REDUNDANT_FEATURE_NAMES,
|
||||
NEGATIVE_FEATURE_NAMES,
|
||||
MULTIPLE_CRATE_VERSIONS,
|
||||
WILDCARD_DEPENDENCIES
|
||||
WILDCARD_DEPENDENCIES,
|
||||
LINT_GROUPS_PRIORITY,
|
||||
]);
|
||||
|
||||
impl LateLintPass<'_> for Cargo {
|
||||
|
|
@ -188,6 +227,8 @@ impl LateLintPass<'_> for Cargo {
|
|||
];
|
||||
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
|
||||
|
||||
lint_groups_priority::check(cx);
|
||||
|
||||
if !NO_DEPS_LINTS
|
||||
.iter()
|
||||
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ mod fn_to_numeric_cast_any;
|
|||
mod fn_to_numeric_cast_with_truncation;
|
||||
mod ptr_as_ptr;
|
||||
mod ptr_cast_constness;
|
||||
mod ref_as_ptr;
|
||||
mod unnecessary_cast;
|
||||
mod utils;
|
||||
mod zero_ptr;
|
||||
|
|
@ -689,6 +690,30 @@ declare_clippy_lint! {
|
|||
"using `0 as *{const, mut} T`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts of references to pointer using `as`
|
||||
/// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using `as` casts may result in silently changing mutability or type.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let a_ref = &1;
|
||||
/// let a_ptr = a_ref as *const _;
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let a_ref = &1;
|
||||
/// let a_ptr = std::ptr::from_ref(a_ref);
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub REF_AS_PTR,
|
||||
pedantic,
|
||||
"using `as` to cast a reference to pointer"
|
||||
}
|
||||
|
||||
pub struct Casts {
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
|
@ -724,6 +749,7 @@ impl_lint_pass!(Casts => [
|
|||
AS_PTR_CAST_MUT,
|
||||
CAST_NAN_TO_INT,
|
||||
ZERO_PTR,
|
||||
REF_AS_PTR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
|
|
@ -771,7 +797,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
|
||||
as_underscore::check(cx, expr, cast_to_hir);
|
||||
|
||||
if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
if self.msrv.meets(msrvs::PTR_FROM_REF) {
|
||||
ref_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
|
||||
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
clippy_lints/src/casts/ref_as_ptr.rs
Normal file
55
clippy_lints/src/casts/ref_as_ptr.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_no_std_crate;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, TypeAndMut};
|
||||
|
||||
use super::REF_AS_PTR;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to_hir_ty: &Ty<'_>) {
|
||||
let (cast_from, cast_to) = (
|
||||
cx.typeck_results().expr_ty(cast_expr),
|
||||
cx.typeck_results().expr_ty(expr),
|
||||
);
|
||||
|
||||
if matches!(cast_from.kind(), ty::Ref(..))
|
||||
&& let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind()
|
||||
{
|
||||
let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
|
||||
let fn_name = match to_mutbl {
|
||||
Mutability::Not => "from_ref",
|
||||
Mutability::Mut => "from_mut",
|
||||
};
|
||||
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let turbofish = match &cast_to_hir_ty.kind {
|
||||
TyKind::Infer => String::new(),
|
||||
TyKind::Ptr(mut_ty) => {
|
||||
if matches!(mut_ty.ty.kind, TyKind::Infer) {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
"::<{}>",
|
||||
snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app)
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REF_AS_PTR,
|
||||
expr.span,
|
||||
"reference as raw pointer",
|
||||
"try",
|
||||
format!("{core_or_std}::ptr::{fn_name}{turbofish}({cast_expr_sugg})"),
|
||||
app,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -71,6 +71,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
|
||||
crate::box_default::BOX_DEFAULT_INFO,
|
||||
crate::cargo::CARGO_COMMON_METADATA_INFO,
|
||||
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
|
||||
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
|
||||
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
|
||||
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
|
||||
|
|
@ -96,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
|
||||
crate::casts::PTR_AS_PTR_INFO,
|
||||
crate::casts::PTR_CAST_CONSTNESS_INFO,
|
||||
crate::casts::REF_AS_PTR_INFO,
|
||||
crate::casts::UNNECESSARY_CAST_INFO,
|
||||
crate::casts::ZERO_PTR_INFO,
|
||||
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
|
||||
|
|
@ -212,6 +214,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
|
||||
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
|
||||
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
|
||||
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
|
||||
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
|
||||
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
|
||||
crate::indexing_slicing::INDEXING_SLICING_INFO,
|
||||
|
|
@ -384,6 +387,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::ITER_SKIP_ZERO_INFO,
|
||||
crate::methods::ITER_WITH_DRAIN_INFO,
|
||||
crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
|
||||
crate::methods::MANUAL_C_STR_LITERALS_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
crate::methods::MANUAL_FIND_MAP_INFO,
|
||||
crate::methods::MANUAL_IS_VARIANT_AND_INFO,
|
||||
|
|
@ -452,6 +456,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::UNNECESSARY_JOIN_INFO,
|
||||
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
|
||||
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
|
||||
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
|
||||
crate::methods::UNNECESSARY_SORT_BY_INFO,
|
||||
crate::methods::UNNECESSARY_TO_OWNED_INFO,
|
||||
crate::methods::UNWRAP_OR_DEFAULT_INFO,
|
||||
|
|
@ -656,6 +661,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
|
||||
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
|
||||
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
|
||||
crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ declare_clippy_lint! {
|
|||
/// unimplemented!();
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.40.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub TEST_ATTR_IN_DOCTEST,
|
||||
suspicious,
|
||||
"presence of `#[test]` in code examples"
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@ use clippy_utils::higher::VecArgs;
|
|||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::type_diagnostic_name;
|
||||
use clippy_utils::usage::{local_used_after_expr, local_used_in};
|
||||
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use clippy_utils::{get_path_from_caller_to_method_type, higher, is_adjusted, path_to_local, path_to_local_id};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{
|
||||
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
|
||||
ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
|
||||
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, ImplPolarity, List, Region, RegionKind,
|
||||
Ty, TypeVisitableExt, TypeckResults,
|
||||
};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
|
|
@ -21,8 +20,8 @@ use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for closures which just call another function where
|
||||
/// the function can be called directly. `unsafe` functions or calls where types
|
||||
/// get adjusted are ignored.
|
||||
/// the function can be called directly. `unsafe` functions, calls where types
|
||||
/// get adjusted or where the callee is marked `#[track_caller]` are ignored.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Needlessly creating a closure adds code for no benefit
|
||||
|
|
@ -136,7 +135,14 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
.map_or(callee_ty, |a| a.target.peel_refs());
|
||||
|
||||
let sig = match callee_ty_adjusted.kind() {
|
||||
ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(),
|
||||
ty::FnDef(def, _) => {
|
||||
// Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
|
||||
if cx.tcx.has_attr(*def, sym::track_caller) {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.tcx.fn_sig(def).skip_binder().skip_binder()
|
||||
},
|
||||
ty::FnPtr(sig) => sig.skip_binder(),
|
||||
ty::Closure(_, subs) => cx
|
||||
.tcx
|
||||
|
|
@ -186,6 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
},
|
||||
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
|
||||
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
|
||||
&& !cx.tcx.has_attr(method_def_id, sym::track_caller)
|
||||
&& check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder())
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
|
@ -195,11 +202,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
|||
"redundant closure",
|
||||
|diag| {
|
||||
let args = typeck.node_args(body.value.hir_id);
|
||||
let name = get_ufcs_type_name(cx, method_def_id, args);
|
||||
let caller = self_.hir_id.owner.def_id;
|
||||
let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the method itself",
|
||||
format!("{}::{}", name, path.ident.name),
|
||||
format!("{}::{}", type_name, path.ident.name),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
|
|
@ -301,27 +309,3 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
|
|||
.zip(to_sig.inputs_and_output)
|
||||
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
|
||||
}
|
||||
|
||||
fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String {
|
||||
let assoc_item = cx.tcx.associated_item(method_def_id);
|
||||
let def_id = assoc_item.container_id(cx.tcx);
|
||||
match assoc_item.container {
|
||||
ty::TraitContainer => cx.tcx.def_path_str(def_id),
|
||||
ty::ImplContainer => {
|
||||
let ty = cx.tcx.type_of(def_id).instantiate_identity();
|
||||
match ty.kind() {
|
||||
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
|
||||
ty::Array(..)
|
||||
| ty::Dynamic(..)
|
||||
| ty::Never
|
||||
| ty::RawPtr(_)
|
||||
| ty::Ref(..)
|
||||
| ty::Slice(_)
|
||||
| ty::Tuple(_) => {
|
||||
format!("<{}>", EarlyBinder::bind(ty).instantiate(cx.tcx, args))
|
||||
},
|
||||
_ => ty.to_string(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
133
clippy_lints/src/incompatible_msrv.rs
Normal file
133
clippy_lints/src/incompatible_msrv.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use clippy_config::msrvs::Msrv;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_attr::{StabilityLevel, StableSince};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///
|
||||
/// This lint checks that no function newer than the defined MSRV (minimum
|
||||
/// supported rust version) is used in the crate.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// It would prevent the crate to be actually used with the specified MSRV.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// // MSRV of 1.3.0
|
||||
/// use std::thread::sleep;
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // Sleep was defined in `1.4.0`.
|
||||
/// sleep(Duration::new(1, 0));
|
||||
/// ```
|
||||
///
|
||||
/// To fix this problem, either increase your MSRV or use another item
|
||||
/// available in your current MSRV.
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub INCOMPATIBLE_MSRV,
|
||||
suspicious,
|
||||
"ensures that all items used in the crate are available for the current MSRV"
|
||||
}
|
||||
|
||||
pub struct IncompatibleMsrv {
|
||||
msrv: Msrv,
|
||||
is_above_msrv: FxHashMap<DefId, RustcVersion>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
|
||||
|
||||
impl IncompatibleMsrv {
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
is_above_msrv: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_lossless)]
|
||||
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
|
||||
if let Some(version) = self.is_above_msrv.get(&def_id) {
|
||||
return *version;
|
||||
}
|
||||
let version = if let Some(version) = tcx
|
||||
.lookup_stability(def_id)
|
||||
.and_then(|stability| match stability.level {
|
||||
StabilityLevel::Stable {
|
||||
since: StableSince::Version(version),
|
||||
..
|
||||
} => Some(RustcVersion::new(
|
||||
version.major as _,
|
||||
version.minor as _,
|
||||
version.patch as _,
|
||||
)),
|
||||
_ => None,
|
||||
}) {
|
||||
version
|
||||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
|
||||
self.get_def_id_version(tcx, parent_def_id)
|
||||
} else {
|
||||
RustcVersion::new(1, 0, 0)
|
||||
};
|
||||
self.is_above_msrv.insert(def_id, version);
|
||||
version
|
||||
}
|
||||
|
||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) {
|
||||
if def_id.is_local() {
|
||||
// We don't check local items since their MSRV is supposed to always be valid.
|
||||
return;
|
||||
}
|
||||
let version = self.get_def_id_version(cx.tcx, def_id);
|
||||
if self.msrv.meets(version) {
|
||||
return;
|
||||
}
|
||||
self.emit_lint_for(cx, span, version);
|
||||
}
|
||||
|
||||
fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) {
|
||||
span_lint(
|
||||
cx,
|
||||
INCOMPATIBLE_MSRV,
|
||||
span,
|
||||
&format!(
|
||||
"current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`",
|
||||
self.msrv
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
|
||||
extract_msrv_attr!(LateContext);
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if self.msrv.current().is_none() {
|
||||
// If there is no MSRV, then no need to check anything...
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(_, _, _, span) => {
|
||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
self.emit_lint_if_under_msrv(cx, method_did, span);
|
||||
}
|
||||
},
|
||||
ExprKind::Call(call, [_]) => {
|
||||
if let ExprKind::Path(qpath) = call.kind
|
||||
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
|
||||
{
|
||||
self.emit_lint_if_under_msrv(cx, path_def_id, call.span);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ declare_clippy_lint! {
|
|||
/// let value = &my_map[key];
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub ITER_OVER_HASH_TYPE,
|
||||
restriction,
|
||||
"iterating over unordered hash-based types (`HashMap` and `HashSet`)"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ extern crate rustc_abi;
|
|||
extern crate rustc_arena;
|
||||
extern crate rustc_ast;
|
||||
extern crate rustc_ast_pretty;
|
||||
extern crate rustc_attr;
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_driver;
|
||||
extern crate rustc_errors;
|
||||
|
|
@ -158,6 +159,7 @@ mod implicit_return;
|
|||
mod implicit_saturating_add;
|
||||
mod implicit_saturating_sub;
|
||||
mod implied_bounds_in_impls;
|
||||
mod incompatible_msrv;
|
||||
mod inconsistent_struct_constructor;
|
||||
mod index_refutable_slice;
|
||||
mod indexing_slicing;
|
||||
|
|
@ -330,6 +332,7 @@ mod temporary_assignment;
|
|||
mod tests_outside_test_module;
|
||||
mod thread_local_initializer_can_be_made_const;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_trait_impl;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
mod transmute;
|
||||
|
|
@ -526,6 +529,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
ref allowed_dotfiles,
|
||||
ref allowed_idents_below_min_chars,
|
||||
ref allowed_scripts,
|
||||
ref allowed_wildcard_imports,
|
||||
ref arithmetic_side_effects_allowed_binary,
|
||||
ref arithmetic_side_effects_allowed_unary,
|
||||
ref arithmetic_side_effects_allowed,
|
||||
|
|
@ -580,6 +584,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
check_private_items,
|
||||
pub_underscore_fields_behavior,
|
||||
ref allowed_duplicate_crates,
|
||||
allow_comparison_to_zero,
|
||||
|
||||
blacklisted_names: _,
|
||||
cyclomatic_complexity_threshold: _,
|
||||
|
|
@ -877,7 +882,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
))
|
||||
});
|
||||
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
|
||||
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(wildcard_imports::WildcardImports::new(
|
||||
warn_on_all_wildcard_imports,
|
||||
allowed_wildcard_imports.clone(),
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
|
||||
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
|
||||
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
|
||||
|
|
@ -973,7 +983,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
|
||||
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
|
||||
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(operators::Operators::new(
|
||||
verbose_bit_mask_threshold,
|
||||
allow_comparison_to_zero,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
|
||||
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
|
||||
|
|
@ -1099,6 +1114,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
|||
store.register_late_pass(move |_| {
|
||||
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
|
||||
});
|
||||
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
|
||||
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ declare_clippy_lint! {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub INFINITE_LOOP,
|
||||
restriction,
|
||||
"possibly unintended infinite loop"
|
||||
|
|
|
|||
|
|
@ -201,12 +201,12 @@ fn never_loop_expr<'tcx>(
|
|||
})
|
||||
})
|
||||
},
|
||||
ExprKind::Block(b, l) => {
|
||||
if l.is_some() {
|
||||
ExprKind::Block(b, _) => {
|
||||
if b.targeted_by_break {
|
||||
local_labels.push((b.hir_id, false));
|
||||
}
|
||||
let ret = never_loop_block(cx, b, local_labels, main_loop_id);
|
||||
let jumped_to = l.is_some() && local_labels.pop().unwrap().1;
|
||||
let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1;
|
||||
match ret {
|
||||
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
|
||||
_ => ret,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
const ACCEPTABLE_METHODS: [&[&str]; 5] = [
|
||||
&paths::BINARYHEAP_ITER,
|
||||
|
|
@ -28,6 +29,7 @@ const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [
|
|||
(sym::Vec, None),
|
||||
(sym::VecDeque, None),
|
||||
];
|
||||
const MAP_TYPES: [rustc_span::Symbol; 2] = [sym::BTreeMap, sym::HashMap];
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -44,6 +46,7 @@ declare_clippy_lint! {
|
|||
/// ```no_run
|
||||
/// let mut vec = vec![0, 1, 2];
|
||||
/// vec.retain(|x| x % 2 == 0);
|
||||
/// vec.retain(|x| x % 2 == 0);
|
||||
/// ```
|
||||
#[clippy::version = "1.64.0"]
|
||||
pub MANUAL_RETAIN,
|
||||
|
|
@ -74,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
|
|||
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
|
||||
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
|
||||
{
|
||||
check_into_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_to_owned(cx, parent_expr, left_expr, target_expr, &self.msrv);
|
||||
check_into_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
check_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
check_to_owned(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,9 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
|
|||
|
||||
fn check_into_iter(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
|
||||
|
|
@ -98,16 +101,39 @@ fn check_into_iter(
|
|||
&& Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn()
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = target_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, target_expr);
|
||||
if match_map_type(cx, left_expr) {
|
||||
if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind {
|
||||
if let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) {
|
||||
make_span_lint_and_sugg(cx, parent_expr_span, sugg);
|
||||
}
|
||||
}
|
||||
// Cannot lint other cases because `retain` requires two parameters
|
||||
} else {
|
||||
// Can always move because `retain` and `filter` have the same bound on the predicate
|
||||
// for other types
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain({})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, closure_expr.span, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_iter(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
|
||||
|
|
@ -122,16 +148,50 @@ fn check_iter(
|
|||
&& match_acceptable_def_path(cx, iter_expr_def_id)
|
||||
&& match_acceptable_type(cx, left_expr, msrv)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, filter_expr);
|
||||
match filter_params.pat.kind {
|
||||
// hir::PatKind::Binding(_, _, _, None) => {
|
||||
// // Be conservative now. Do nothing here.
|
||||
// // TODO: Ideally, we can rewrite the lambda by stripping one level of reference
|
||||
// },
|
||||
hir::PatKind::Tuple([_, _], _) => {
|
||||
// the `&&` reference for the `filter` method will be auto derefed to `ref`
|
||||
// so, we can directly use the lambda
|
||||
// https://doc.rust-lang.org/reference/patterns.html#binding-modes
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain({})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, closure_expr.span, "..")
|
||||
),
|
||||
);
|
||||
},
|
||||
hir::PatKind::Ref(pat, _) => make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain(|{}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, pat.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
),
|
||||
),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_to_owned(
|
||||
cx: &LateContext<'_>,
|
||||
parent_expr: &hir::Expr<'_>,
|
||||
left_expr: &hir::Expr<'_>,
|
||||
target_expr: &hir::Expr<'_>,
|
||||
parent_expr_span: Span,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if msrv.meets(msrvs::STRING_RETAIN)
|
||||
|
|
@ -147,43 +207,25 @@ fn check_to_owned(
|
|||
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
|
||||
&& is_type_lang_item(cx, ty, hir::LangItem::String)
|
||||
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr)
|
||||
{
|
||||
suggest(cx, parent_expr, left_expr, filter_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) {
|
||||
if let hir::ExprKind::MethodCall(_, _, [closure], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = closure.kind
|
||||
&& let filter_body = cx.tcx.hir().body(body)
|
||||
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
|
||||
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
|
||||
&& let filter_body = cx.tcx.hir().body(closure.body)
|
||||
&& let [filter_params] = filter_body.params
|
||||
&& let Some(sugg) = match filter_params.pat.kind {
|
||||
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
|
||||
"{}.retain(|{filter_param_ident}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
)),
|
||||
hir::PatKind::Tuple([key_pat, value_pat], _) => make_sugg(cx, key_pat, value_pat, left_expr, filter_body),
|
||||
hir::PatKind::Ref(pat, _) => match pat.kind {
|
||||
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
|
||||
"{}.retain(|{filter_param_ident}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RETAIN,
|
||||
parent_expr.span,
|
||||
"this expression can be written more simply using `.retain()`",
|
||||
"consider calling `.retain()` instead",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind {
|
||||
make_span_lint_and_sugg(
|
||||
cx,
|
||||
parent_expr_span,
|
||||
format!(
|
||||
"{}.retain(|{}| {})",
|
||||
snippet(cx, left_expr.span, ".."),
|
||||
snippet(cx, pat.span, ".."),
|
||||
snippet(cx, filter_body.value.span, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
// Be conservative now. Do nothing for the `Binding` case.
|
||||
// TODO: Ideally, we can rewrite the lambda by stripping one level of reference
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,3 +271,20 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
|
|||
&& acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv))
|
||||
})
|
||||
}
|
||||
|
||||
fn match_map_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
|
||||
MAP_TYPES.iter().any(|ty| is_type_diagnostic_item(cx, expr_ty, *ty))
|
||||
}
|
||||
|
||||
fn make_span_lint_and_sugg(cx: &LateContext<'_>, span: Span, sugg: String) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_RETAIN,
|
||||
span,
|
||||
"this expression can be written more simply using `.retain()`",
|
||||
"consider calling `.retain()` instead",
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
197
clippy_lints/src/methods/manual_c_str_literals.rs
Normal file
197
clippy_lints/src/methods/manual_c_str_literals.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
use clippy_config::msrvs::{self, Msrv};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::source::snippet;
|
||||
use rustc_ast::{LitKind, StrStyle};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
use super::MANUAL_C_STR_LITERALS;
|
||||
|
||||
/// Checks:
|
||||
/// - `b"...".as_ptr()`
|
||||
/// - `b"...".as_ptr().cast()`
|
||||
/// - `"...".as_ptr()`
|
||||
/// - `"...".as_ptr().cast()`
|
||||
///
|
||||
/// Iff the parent call of `.cast()` isn't `CStr::from_ptr`, to avoid linting twice.
|
||||
pub(super) fn check_as_ptr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
receiver: &'tcx Expr<'tcx>,
|
||||
msrv: &Msrv,
|
||||
) {
|
||||
if let ExprKind::Lit(lit) = receiver.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node
|
||||
&& let casts_removed = peel_ptr_cast_ancestors(cx, expr)
|
||||
&& !get_parent_expr(cx, casts_removed).is_some_and(
|
||||
|parent| matches!(parent.kind, ExprKind::Call(func, _) if is_c_str_function(cx, func).is_some()),
|
||||
)
|
||||
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
|
||||
&& msrv.meets(msrvs::C_STR_LITERALS)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
receiver.span,
|
||||
"manually constructing a nul-terminated string",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
// an additional cast may be needed, since the type of `CStr::as_ptr` and
|
||||
// `"".as_ptr()` can differ and is platform dependent
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the callee is a "relevant" `CStr` function considered by this lint.
|
||||
/// Returns the function name.
|
||||
fn is_c_str_function(cx: &LateContext<'_>, func: &Expr<'_>) -> Option<Symbol> {
|
||||
if let ExprKind::Path(QPath::TypeRelative(cstr, fn_name)) = &func.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, ty_path)) = &cstr.kind
|
||||
&& cx.tcx.lang_items().c_str() == ty_path.res.opt_def_id()
|
||||
{
|
||||
Some(fn_name.ident.name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks calls to the `CStr` constructor functions:
|
||||
/// - `CStr::from_bytes_with_nul(..)`
|
||||
/// - `CStr::from_bytes_with_nul_unchecked(..)`
|
||||
/// - `CStr::from_ptr(..)`
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
|
||||
if let Some(fn_name) = is_c_str_function(cx, func)
|
||||
&& let [arg] = args
|
||||
&& msrv.meets(msrvs::C_STR_LITERALS)
|
||||
{
|
||||
match fn_name.as_str() {
|
||||
name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
|
||||
if !arg.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = arg.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node =>
|
||||
{
|
||||
check_from_bytes(cx, expr, arg, name);
|
||||
},
|
||||
"from_ptr" => check_from_ptr(cx, expr, arg),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
|
||||
fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
|
||||
if let ExprKind::MethodCall(method, lit, ..) = peel_ptr_cast(arg).kind
|
||||
&& method.ident.name == sym::as_ptr
|
||||
&& !lit.span.from_expansion()
|
||||
&& let ExprKind::Lit(lit) = lit.kind
|
||||
&& let LitKind::ByteStr(_, StrStyle::Cooked) = lit.node
|
||||
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
expr.span,
|
||||
"calling `CStr::from_ptr` with a byte string literal",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
|
||||
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
|
||||
let (span, applicability) = if let Some(parent) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::MethodCall(method, ..) = parent.kind
|
||||
&& [sym::unwrap, sym::expect].contains(&method.ident.name)
|
||||
{
|
||||
(parent.span, Applicability::MachineApplicable)
|
||||
} else if method == "from_bytes_with_nul_unchecked" {
|
||||
// `*_unchecked` returns `&CStr` directly, nothing needs to be changed
|
||||
(expr.span, Applicability::MachineApplicable)
|
||||
} else {
|
||||
// User needs to remove error handling, can't be machine applicable
|
||||
(expr.span, Applicability::HasPlaceholders)
|
||||
};
|
||||
|
||||
let Some(sugg) = rewrite_as_cstr(cx, arg.span) else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
span,
|
||||
"calling `CStr::new` with a byte string literal",
|
||||
r#"use a `c""` literal"#,
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
/// Rewrites a byte string literal to a c-str literal.
|
||||
/// `b"foo\0"` -> `c"foo"`
|
||||
///
|
||||
/// Returns `None` if it doesn't end in a NUL byte.
|
||||
fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> Option<String> {
|
||||
let mut sugg = String::from("c") + snippet(cx, span.source_callsite(), "..").trim_start_matches('b');
|
||||
|
||||
// NUL byte should always be right before the closing quote.
|
||||
if let Some(quote_pos) = sugg.rfind('"') {
|
||||
// Possible values right before the quote:
|
||||
// - literal NUL value
|
||||
if sugg.as_bytes()[quote_pos - 1] == b'\0' {
|
||||
sugg.remove(quote_pos - 1);
|
||||
}
|
||||
// - \x00
|
||||
else if sugg[..quote_pos].ends_with("\\x00") {
|
||||
sugg.replace_range(quote_pos - 4..quote_pos, "");
|
||||
}
|
||||
// - \0
|
||||
else if sugg[..quote_pos].ends_with("\\0") {
|
||||
sugg.replace_range(quote_pos - 2..quote_pos, "");
|
||||
}
|
||||
// No known suffix, so assume it's not a C-string.
|
||||
else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(sugg)
|
||||
}
|
||||
|
||||
fn get_cast_target<'tcx>(e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
match &e.kind {
|
||||
ExprKind::MethodCall(method, receiver, [], _) if method.ident.as_str() == "cast" => Some(receiver),
|
||||
ExprKind::Cast(expr, _) => Some(expr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `x.cast()` -> `x`
|
||||
/// `x as *const _` -> `x`
|
||||
/// `x` -> `x` (returns the same expression for non-cast exprs)
|
||||
fn peel_ptr_cast<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
get_cast_target(e).map_or(e, peel_ptr_cast)
|
||||
}
|
||||
|
||||
/// Same as `peel_ptr_cast`, but the other way around, by walking up the ancestor cast expressions:
|
||||
///
|
||||
/// `foo(x.cast() as *const _)`
|
||||
/// ^ given this `x` expression, returns the `foo(...)` expression
|
||||
fn peel_ptr_cast_ancestors<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
|
||||
let mut prev = e;
|
||||
for (_, node) in cx.tcx.hir().parent_iter(e.hir_id) {
|
||||
if let Node::Expr(e) = node
|
||||
&& get_cast_target(e).is_some()
|
||||
{
|
||||
prev = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
prev
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
|
|||
unwrap_arg: &'tcx hir::Expr<'_>,
|
||||
msrv: &Msrv,
|
||||
) -> bool {
|
||||
// lint if the caller of `map()` is an `Option`
|
||||
// lint if the caller of `map()` is an `Option` or a `Result`.
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ mod iter_skip_zero;
|
|||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod join_absolute_paths;
|
||||
mod manual_c_str_literals;
|
||||
mod manual_is_variant_and;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
|
|
@ -113,6 +114,7 @@ mod unnecessary_iter_cloned;
|
|||
mod unnecessary_join;
|
||||
mod unnecessary_lazy_eval;
|
||||
mod unnecessary_literal_unwrap;
|
||||
mod unnecessary_result_map_or_else;
|
||||
mod unnecessary_sort_by;
|
||||
mod unnecessary_to_owned;
|
||||
mod unwrap_expect_used;
|
||||
|
|
@ -3951,6 +3953,64 @@ declare_clippy_lint! {
|
|||
"cloning an `Option` via `as_ref().cloned()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely by using `unwrap_or_else()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # fn handle_error(_: ()) -> u32 { 0 }
|
||||
/// let x: Result<u32, ()> = Ok(0);
|
||||
/// let y = x.map_or_else(|err| handle_error(err), |n| n);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # fn handle_error(_: ()) -> u32 { 0 }
|
||||
/// let x: Result<u32, ()> = Ok(0);
|
||||
/// let y = x.unwrap_or_else(|err| handle_error(err));
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
suspicious,
|
||||
"making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either
|
||||
/// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()`
|
||||
/// on a (byte) string literal with a hardcoded `\0` byte at the end.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This can be written more concisely using `c"str"` literals and is also less error-prone,
|
||||
/// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CStr;
|
||||
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
|
||||
/// fn needs_cstr(_: &CStr) {}
|
||||
///
|
||||
/// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap());
|
||||
/// unsafe { libc::puts("World\0".as_ptr().cast()) }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CStr;
|
||||
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
|
||||
/// fn needs_cstr(_: &CStr) {}
|
||||
///
|
||||
/// needs_cstr(c"Hello");
|
||||
/// unsafe { libc::puts(c"World".as_ptr()) }
|
||||
/// ```
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub MANUAL_C_STR_LITERALS,
|
||||
pedantic,
|
||||
r#"creating a `CStr` through functions when `c""` literals can be used"#
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
|
|
@ -4109,6 +4169,8 @@ impl_lint_pass!(Methods => [
|
|||
MANUAL_IS_VARIANT_AND,
|
||||
STR_SPLIT_AT_NEWLINE,
|
||||
OPTION_AS_REF_CLONED,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
MANUAL_C_STR_LITERALS,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -4136,6 +4198,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
hir::ExprKind::Call(func, args) => {
|
||||
from_iter_instead_of_collect::check(cx, expr, args, func);
|
||||
unnecessary_fallible_conversions::check_function(cx, expr, func);
|
||||
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
|
||||
},
|
||||
hir::ExprKind::MethodCall(method_call, receiver, args, _) => {
|
||||
let method_span = method_call.ident.span;
|
||||
|
|
@ -4354,6 +4417,7 @@ impl Methods {
|
|||
}
|
||||
},
|
||||
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
|
||||
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
|
||||
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
|
||||
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
|
||||
("cloned", []) => {
|
||||
|
|
@ -4592,6 +4656,7 @@ impl Methods {
|
|||
},
|
||||
("map_or_else", [def, map]) => {
|
||||
result_map_or_else_none::check(cx, expr, recv, def, map);
|
||||
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
|
||||
},
|
||||
("next", []) => {
|
||||
if let Some((name2, recv2, args2, _, _)) = method_call(recv) {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,6 @@ fn check_fold_with_op(
|
|||
cx,
|
||||
UNNECESSARY_FOLD,
|
||||
fold_span.with_hi(expr.span.hi()),
|
||||
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
|
||||
"this `.fold` can be written more succinctly using another method",
|
||||
"try",
|
||||
sugg,
|
||||
|
|
|
|||
95
clippy_lints/src/methods/unnecessary_result_map_or_else.rs
Normal file
95
clippy_lints/src/methods/unnecessary_result_map_or_else.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::peel_blocks;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
|
||||
|
||||
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
|
||||
let msg = "unused \"map closure\" when calling `Result::map_or_else` value";
|
||||
let self_snippet = snippet(cx, recv.span, "..");
|
||||
let err_snippet = snippet(cx, def_arg.span, "..");
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_RESULT_MAP_OR_ELSE,
|
||||
expr.span,
|
||||
msg,
|
||||
"consider using `unwrap_or_else`",
|
||||
format!("{self_snippet}.unwrap_or_else({err_snippet})"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
|
||||
for stmt in statements {
|
||||
if let StmtKind::Local(local) = stmt.kind
|
||||
&& let Some(init) = local.init
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
|
||||
&& let hir::def::Res::Local(local_hir_id) = path.res
|
||||
&& local_hir_id == hir_id
|
||||
{
|
||||
hir_id = local.pat.hir_id;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(hir_id)
|
||||
}
|
||||
|
||||
fn handle_qpath(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
def_arg: &Expr<'_>,
|
||||
expected_hir_id: HirId,
|
||||
qpath: QPath<'_>,
|
||||
) {
|
||||
if let QPath::Resolved(_, path) = qpath
|
||||
&& let hir::def::Res::Local(hir_id) = path.res
|
||||
&& expected_hir_id == hir_id
|
||||
{
|
||||
emit_lint(cx, expr, recv, def_arg);
|
||||
}
|
||||
}
|
||||
|
||||
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Result`s.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
recv: &'tcx Expr<'_>,
|
||||
def_arg: &'tcx Expr<'_>,
|
||||
map_arg: &'tcx Expr<'_>,
|
||||
) {
|
||||
// lint if the caller of `map_or_else()` is a `Result`
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
|
||||
&& let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind
|
||||
&& let body = cx.tcx.hir().body(body)
|
||||
&& let Some(first_param) = body.params.first()
|
||||
{
|
||||
let body_expr = peel_blocks(body.value);
|
||||
|
||||
match body_expr.kind {
|
||||
ExprKind::Path(qpath) => {
|
||||
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
|
||||
},
|
||||
// If this is a block (that wasn't peeled off), then it means there are statements.
|
||||
ExprKind::Block(block, _) => {
|
||||
if let Some(block_expr) = block.expr
|
||||
// First we ensure that this is a "binding chain" (each statement is a binding
|
||||
// of the previous one) and that it is a binding of the closure argument.
|
||||
&& let Some(last_chain_binding_id) =
|
||||
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
|
||||
&& let ExprKind::Path(qpath) = block_expr.kind
|
||||
{
|
||||
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -357,7 +357,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
|
|||
}
|
||||
},
|
||||
ExprKind::Block(block, _) => {
|
||||
if block.stmts.is_empty() {
|
||||
if block.stmts.is_empty() && !block.targeted_by_break {
|
||||
block.expr.as_ref().and_then(|e| {
|
||||
match block.rules {
|
||||
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
|
|||
{
|
||||
(
|
||||
trait_item_id,
|
||||
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.args) as *const _ as usize),
|
||||
FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_regions(trait_ref.args)) as usize),
|
||||
usize::from(sig.decl.implicit_self.has_implicit_self()),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -390,7 +390,6 @@ fn has_matching_args(kind: FnKind, args: GenericArgsRef<'_>) -> bool {
|
|||
GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx),
|
||||
GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
|
||||
}),
|
||||
#[allow(trivial_casts)]
|
||||
FnKind::ImplTraitFn(expected_args) => args as *const _ as usize == expected_args,
|
||||
FnKind::ImplTraitFn(expected_args) => std::ptr::from_ref(args) as usize == expected_args,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -771,6 +771,7 @@ declare_clippy_lint! {
|
|||
pub struct Operators {
|
||||
arithmetic_context: numeric_arithmetic::Context,
|
||||
verbose_bit_mask_threshold: u64,
|
||||
modulo_arithmetic_allow_comparison_to_zero: bool,
|
||||
}
|
||||
impl_lint_pass!(Operators => [
|
||||
ABSURD_EXTREME_COMPARISONS,
|
||||
|
|
@ -801,10 +802,11 @@ impl_lint_pass!(Operators => [
|
|||
SELF_ASSIGNMENT,
|
||||
]);
|
||||
impl Operators {
|
||||
pub fn new(verbose_bit_mask_threshold: u64) -> Self {
|
||||
pub fn new(verbose_bit_mask_threshold: u64, modulo_arithmetic_allow_comparison_to_zero: bool) -> Self {
|
||||
Self {
|
||||
arithmetic_context: numeric_arithmetic::Context::default(),
|
||||
verbose_bit_mask_threshold,
|
||||
modulo_arithmetic_allow_comparison_to_zero,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -835,12 +837,19 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
|
|||
cmp_owned::check(cx, op.node, lhs, rhs);
|
||||
float_cmp::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_one::check(cx, e, op.node, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(
|
||||
cx,
|
||||
e,
|
||||
op.node,
|
||||
lhs,
|
||||
rhs,
|
||||
self.modulo_arithmetic_allow_comparison_to_zero,
|
||||
);
|
||||
},
|
||||
ExprKind::AssignOp(op, lhs, rhs) => {
|
||||
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
|
||||
misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs);
|
||||
modulo_arithmetic::check(cx, e, op.node, lhs, rhs, false);
|
||||
},
|
||||
ExprKind::Assign(lhs, rhs, _) => {
|
||||
assign_op_pattern::check(cx, e, lhs, rhs);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sext;
|
||||
use rustc_hir::{BinOpKind, Expr};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use std::fmt::Display;
|
||||
|
|
@ -14,8 +14,13 @@ pub(super) fn check<'tcx>(
|
|||
op: BinOpKind,
|
||||
lhs: &'tcx Expr<'_>,
|
||||
rhs: &'tcx Expr<'_>,
|
||||
allow_comparison_to_zero: bool,
|
||||
) {
|
||||
if op == BinOpKind::Rem {
|
||||
if allow_comparison_to_zero && used_in_comparison_with_zero(cx, e) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lhs_operand = analyze_operand(lhs, cx, e);
|
||||
let rhs_operand = analyze_operand(rhs, cx, e);
|
||||
if let Some(lhs_operand) = lhs_operand
|
||||
|
|
@ -28,6 +33,26 @@ pub(super) fn check<'tcx>(
|
|||
};
|
||||
}
|
||||
|
||||
fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find_parent(expr.hir_id) else {
|
||||
return false;
|
||||
};
|
||||
let ExprKind::Binary(op, lhs, rhs) = parent_expr.kind else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if op.node == BinOpKind::Eq || op.node == BinOpKind::Ne {
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), rhs) {
|
||||
return true;
|
||||
}
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), lhs) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
struct OperandInfo {
|
||||
string_representation: Option<String>,
|
||||
is_negative: bool,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::rustc_lint::LintContext;
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use hir::Param;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
|
||||
|
|
@ -13,6 +14,7 @@ use rustc_middle::hir::nested_filter;
|
|||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::ExpnKind;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -89,7 +91,12 @@ fn find_innermost_closure<'tcx>(
|
|||
cx: &LateContext<'tcx>,
|
||||
mut expr: &'tcx hir::Expr<'tcx>,
|
||||
mut steps: usize,
|
||||
) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, ty::Asyncness)> {
|
||||
) -> Option<(
|
||||
&'tcx hir::Expr<'tcx>,
|
||||
&'tcx hir::FnDecl<'tcx>,
|
||||
ty::Asyncness,
|
||||
&'tcx [Param<'tcx>],
|
||||
)> {
|
||||
let mut data = None;
|
||||
|
||||
while let hir::ExprKind::Closure(closure) = expr.kind
|
||||
|
|
@ -110,6 +117,7 @@ fn find_innermost_closure<'tcx>(
|
|||
} else {
|
||||
ty::Asyncness::No
|
||||
},
|
||||
body.params,
|
||||
));
|
||||
steps -= 1;
|
||||
}
|
||||
|
|
@ -152,7 +160,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
|||
// without this check, we'd end up linting twice.
|
||||
&& !matches!(recv.kind, hir::ExprKind::Call(..))
|
||||
&& let (full_expr, call_depth) = get_parent_call_exprs(cx, expr)
|
||||
&& let Some((body, fn_decl, coroutine_kind)) = find_innermost_closure(cx, recv, call_depth)
|
||||
&& let Some((body, fn_decl, coroutine_kind, params)) = find_innermost_closure(cx, recv, call_depth)
|
||||
// outside macros we lint properly. Inside macros, we lint only ||() style closures.
|
||||
&& (!matches!(expr.span.ctxt().outer_expn_data().kind, ExpnKind::Macro(_, _)) || params.is_empty())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ use clippy_utils::ty::needs_ordered_drop;
|
|||
use rustc_ast::Mutability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{BindingAnnotation, ByRef, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
|
||||
use rustc_hir_typeck::expr_use_visitor::PlaceBase;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::UpvarCapture;
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::DesugaringKind;
|
||||
|
|
@ -69,6 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
// the local is user-controlled
|
||||
&& !in_external_macro(cx.sess(), local.span)
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& !is_by_value_closure_capture(cx, local.hir_id, binding_id)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
|
@ -82,6 +85,29 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the enclosing body is a closure and if the given local is captured by value.
|
||||
///
|
||||
/// In those cases, the redefinition may be necessary to force a move:
|
||||
/// ```
|
||||
/// fn assert_static<T: 'static>(_: T) {}
|
||||
///
|
||||
/// let v = String::new();
|
||||
/// let closure = || {
|
||||
/// let v = v; // <- removing this redefinition makes `closure` no longer `'static`
|
||||
/// dbg!(&v);
|
||||
/// };
|
||||
/// assert_static(closure);
|
||||
/// ```
|
||||
fn is_by_value_closure_capture(cx: &LateContext<'_>, redefinition: HirId, root_variable: HirId) -> bool {
|
||||
let closure_def_id = cx.tcx.hir().enclosing_body_owner(redefinition);
|
||||
|
||||
cx.tcx.is_closure_or_coroutine(closure_def_id.to_def_id())
|
||||
&& cx.tcx.closure_captures(closure_def_id).iter().any(|c| {
|
||||
matches!(c.info.capture_kind, UpvarCapture::ByValue)
|
||||
&& matches!(c.place.base, PlaceBase::Upvar(upvar) if upvar.var_path.hir_id == root_variable)
|
||||
})
|
||||
}
|
||||
|
||||
/// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced.
|
||||
fn find_binding(pat: &Pat<'_>, name: Ident) -> Option<BindingAnnotation> {
|
||||
let mut ret = None;
|
||||
|
|
|
|||
|
|
@ -188,7 +188,6 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
|
|||
match init_lit.node {
|
||||
// In these cases the annotation is redundant
|
||||
LitKind::Str(..)
|
||||
| LitKind::ByteStr(..)
|
||||
| LitKind::Byte(..)
|
||||
| LitKind::Char(..)
|
||||
| LitKind::Bool(..)
|
||||
|
|
@ -202,6 +201,16 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
|
|||
}
|
||||
},
|
||||
LitKind::Err => (),
|
||||
LitKind::ByteStr(..) => {
|
||||
// We only lint if the type annotation is an array type (e.g. &[u8; 4]).
|
||||
// If instead it is a slice (e.g. &[u8]) it may not be redundant, so we
|
||||
// don't lint.
|
||||
if let hir::TyKind::Ref(_, mut_ty) = ty.kind
|
||||
&& matches!(mut_ty.ty.kind, hir::TyKind::Array(..))
|
||||
{
|
||||
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ declare_clippy_lint! {
|
|||
/// // ^^^ this closure executes 123 times
|
||||
/// // and the vecs will have the expected capacity
|
||||
/// ```
|
||||
#[clippy::version = "1.74.0"]
|
||||
#[clippy::version = "1.76.0"]
|
||||
pub REPEAT_VEC_WITH_CAPACITY,
|
||||
suspicious,
|
||||
"repeating a `Vec::with_capacity` expression which does not retain capacity"
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lin
|
|||
use clippy_utils::source::{snippet_opt, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
|
||||
use clippy_utils::{fn_def_id, is_from_proc_macro, is_inside_let_else, path_to_local_id, span_find_starting_semi};
|
||||
use clippy_utils::{
|
||||
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id,
|
||||
span_find_starting_semi,
|
||||
};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::LangItem::ResultErr;
|
||||
use rustc_hir::{
|
||||
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
|
||||
StmtKind,
|
||||
|
|
@ -18,6 +22,7 @@ use rustc_session::declare_lint_pass;
|
|||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{BytePos, Pos, Span};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -146,14 +151,14 @@ impl<'tcx> RetReplacement<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ToString for RetReplacement<'tcx> {
|
||||
fn to_string(&self) -> String {
|
||||
impl<'tcx> Display for RetReplacement<'tcx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Empty => String::new(),
|
||||
Self::Block => "{}".to_string(),
|
||||
Self::Unit => "()".to_string(),
|
||||
Self::IfSequence(inner, _) => format!("({inner})"),
|
||||
Self::Expr(inner, _) => inner.to_string(),
|
||||
Self::Empty => write!(f, ""),
|
||||
Self::Block => write!(f, "{{}}"),
|
||||
Self::Unit => write!(f, "()"),
|
||||
Self::IfSequence(inner, _) => write!(f, "({inner})"),
|
||||
Self::Expr(inner, _) => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +186,15 @@ impl<'tcx> LateLintPass<'tcx> for Return {
|
|||
if !in_external_macro(cx.sess(), stmt.span)
|
||||
&& let StmtKind::Semi(expr) = stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
||||
&& let ExprKind::Match(.., MatchSource::TryDesugar(_)) = ret.kind
|
||||
// return Err(...)? desugars to a match
|
||||
// over a Err(...).branch()
|
||||
// which breaks down to a branch call, with the callee being
|
||||
// the constructor of the Err variant
|
||||
&& let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind
|
||||
&& let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind
|
||||
&& let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr)
|
||||
|
||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir().get_parent_item(expr.hir_id))
|
||||
&& let ItemKind::Fn(_, _, body) = item.kind
|
||||
|
|
|
|||
67
clippy_lints/src/to_string_trait_impl.rs
Normal file
67
clippy_lints/src/to_string_trait_impl.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for direct implementations of `ToString`.
|
||||
/// ### Why is this bad?
|
||||
/// This trait is automatically implemented for any type which implements the `Display` trait.
|
||||
/// As such, `ToString` shouldn’t be implemented directly: `Display` should be implemented instead,
|
||||
/// and you get the `ToString` implementation for free.
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// struct Point {
|
||||
/// x: usize,
|
||||
/// y: usize,
|
||||
/// }
|
||||
///
|
||||
/// impl ToString for Point {
|
||||
/// fn to_string(&self) -> String {
|
||||
/// format!("({}, {})", self.x, self.y)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// struct Point {
|
||||
/// x: usize,
|
||||
/// y: usize,
|
||||
/// }
|
||||
///
|
||||
/// impl std::fmt::Display for Point {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// write!(f, "({}, {})", self.x, self.y)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.77.0"]
|
||||
pub TO_STRING_TRAIT_IMPL,
|
||||
style,
|
||||
"check for direct implementations of `ToString`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(ToStringTraitImpl => [TO_STRING_TRAIT_IMPL]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'tcx>) {
|
||||
if let ItemKind::Impl(Impl {
|
||||
of_trait: Some(trait_ref),
|
||||
..
|
||||
}) = it.kind
|
||||
&& let Some(trait_did) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_did)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
TO_STRING_TRAIT_IMPL,
|
||||
it.span,
|
||||
"direct implementation of `ToString`",
|
||||
None,
|
||||
"prefer implementing `Display` instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,14 +69,6 @@ fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) {
|
|||
);
|
||||
}
|
||||
|
||||
fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
|
||||
match ty.peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()),
|
||||
ty::Foreign(def_id) => Some(*def_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> {
|
||||
let TyKind::Path(qpath) = hir_ty.kind else { return None };
|
||||
match qpath {
|
||||
|
|
@ -131,21 +123,49 @@ fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Opt
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
/// When we have `x == y` where `x = &T` and `y = &T`, then that resolves to
|
||||
/// `<&T as PartialEq<&T>>::eq`, which is not the same as `<T as PartialEq<T>>::eq`,
|
||||
/// however we still would want to treat it the same, because we know that it's a blanket impl
|
||||
/// that simply delegates to the `PartialEq` impl with one reference removed.
|
||||
///
|
||||
/// Still, we can't just do `lty.peel_refs() == rty.peel_refs()` because when we have `x = &T` and
|
||||
/// `y = &&T`, this is not necessarily the same as `<T as PartialEq<T>>::eq`
|
||||
///
|
||||
/// So to avoid these FNs and FPs, we keep removing a layer of references from *both* sides
|
||||
/// until both sides match the expected LHS and RHS type (or they don't).
|
||||
fn matches_ty<'tcx>(
|
||||
mut left: Ty<'tcx>,
|
||||
mut right: Ty<'tcx>,
|
||||
expected_left: Ty<'tcx>,
|
||||
expected_right: Ty<'tcx>,
|
||||
) -> bool {
|
||||
while let (&ty::Ref(_, lty, _), &ty::Ref(_, rty, _)) = (left.kind(), right.kind()) {
|
||||
if lty == expected_left && rty == expected_right {
|
||||
return true;
|
||||
}
|
||||
left = lty;
|
||||
right = rty;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
|
||||
let args = cx
|
||||
.tcx
|
||||
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
|
||||
.inputs();
|
||||
let Some(sig) = cx
|
||||
.typeck_results()
|
||||
.liberated_fn_sigs()
|
||||
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// That has two arguments.
|
||||
if let [self_arg, other_arg] = args
|
||||
&& let Some(self_arg) = get_ty_def_id(*self_arg)
|
||||
&& let Some(other_arg) = get_ty_def_id(*other_arg)
|
||||
if let [self_arg, other_arg] = sig.inputs()
|
||||
&& let &ty::Ref(_, self_arg, _) = self_arg.kind()
|
||||
&& let &ty::Ref(_, other_arg, _) = other_arg.kind()
|
||||
// The two arguments are of the same type.
|
||||
&& self_arg == other_arg
|
||||
&& let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id)
|
||||
// The trait is `PartialEq`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
|
||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, trait_def_id)
|
||||
{
|
||||
let to_check_op = if name.name == sym::eq {
|
||||
BinOpKind::Eq
|
||||
|
|
@ -154,31 +174,19 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
|
|||
};
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::Binary(op, left, right) if op.node == to_check_op => {
|
||||
// Then we check if the left-hand element is of the same type as `self`.
|
||||
if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left)
|
||||
&& let Some(left_id) = get_ty_def_id(left_ty)
|
||||
&& self_arg == left_id
|
||||
&& let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
|
||||
&& let Some(right_id) = get_ty_def_id(right_ty)
|
||||
&& other_arg == right_id
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// Then we check if the LHS matches self_arg and RHS matches other_arg
|
||||
let left_ty = cx.typeck_results().expr_ty_adjusted(left);
|
||||
let right_ty = cx.typeck_results().expr_ty_adjusted(right);
|
||||
matches_ty(left_ty, right_ty, self_arg, other_arg)
|
||||
},
|
||||
ExprKind::MethodCall(segment, receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||
if let Some(ty) = cx.typeck_results().expr_ty_opt(receiver)
|
||||
&& let Some(ty_id) = get_ty_def_id(ty)
|
||||
&& self_arg != ty_id
|
||||
{
|
||||
// Since this called on a different type, the lint should not be
|
||||
// triggered here.
|
||||
return;
|
||||
}
|
||||
ExprKind::MethodCall(segment, receiver, [arg], _) if segment.ident.name == name.name => {
|
||||
let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver);
|
||||
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
|
||||
|
||||
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
|
||||
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
|
||||
&& trait_id == trait_def_id
|
||||
&& matches_ty(receiver_ty, arg_ty, self_arg, other_arg)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths};
|
||||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
|
||||
use hir::{ExprKind, PatKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -82,37 +83,72 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
|
|||
}
|
||||
|
||||
if let Some(exp) = block.expr
|
||||
&& matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _))
|
||||
&& matches!(
|
||||
exp.kind,
|
||||
hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, hir::MatchSource::Normal)
|
||||
)
|
||||
{
|
||||
check_expr(cx, exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
||||
// if there is a guard, we consider the result to be consumed
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
if is_unreachable_or_panic(cx, arm.body) {
|
||||
// if the body is unreachable or there is a panic,
|
||||
// we consider the result to be consumed
|
||||
return false;
|
||||
}
|
||||
|
||||
if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind {
|
||||
return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn non_consuming_ok_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
||||
// if there is a guard, we consider the result to be consumed
|
||||
if arm.guard.is_some() {
|
||||
return false;
|
||||
}
|
||||
if is_unreachable_or_panic(cx, arm.body) {
|
||||
// if the body is unreachable or there is a panic,
|
||||
// we consider the result to be consumed
|
||||
return false;
|
||||
}
|
||||
|
||||
if is_ok_wild_or_dotdot_pattern(cx, arm.pat) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::If(cond, _, _)
|
||||
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
|
||||
&& pattern_is_ignored_ok(cx, pat)
|
||||
&& is_ok_wild_or_dotdot_pattern(cx, pat)
|
||||
&& let Some(op) = should_lint(cx, init) =>
|
||||
{
|
||||
emit_lint(cx, cond.span, op, &[pat.span]);
|
||||
},
|
||||
hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||
let found_arms: Vec<_> = arms
|
||||
.iter()
|
||||
.filter_map(|arm| {
|
||||
if pattern_is_ignored_ok(cx, arm.pat) {
|
||||
Some(arm.span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !found_arms.is_empty() {
|
||||
emit_lint(cx, expr.span, op, found_arms.as_slice());
|
||||
// we will capture only the case where the match is Ok( ) or Err( )
|
||||
// prefer to match the minimum possible, and expand later if needed
|
||||
// to avoid false positives on something as used as this
|
||||
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
||||
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
|
||||
emit_lint(cx, expr.span, op, &[arm1.pat.span]);
|
||||
}
|
||||
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
|
||||
emit_lint(cx, expr.span, op, &[arm2.pat.span]);
|
||||
}
|
||||
},
|
||||
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
|
||||
_ if let Some(op) = should_lint(cx, expr) => {
|
||||
emit_lint(cx, expr.span, op, &[]);
|
||||
},
|
||||
|
|
@ -130,25 +166,40 @@ fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option
|
|||
check_io_mode(cx, inner)
|
||||
}
|
||||
|
||||
fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool {
|
||||
fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> bool {
|
||||
// the if checks whether we are in a result Ok( ) pattern
|
||||
// and the return checks whether it is unhandled
|
||||
|
||||
if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind
|
||||
if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind
|
||||
// we check against Result::Ok to avoid linting on Err(_) or something else.
|
||||
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
|
||||
{
|
||||
return match (inner_pat, ddp.as_opt_usize()) {
|
||||
// Ok(_) pattern
|
||||
([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true,
|
||||
// Ok(..) pattern
|
||||
([], Some(0)) => true,
|
||||
_ => false,
|
||||
};
|
||||
if matches!(inner_pat, []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let [cons_pat] = inner_pat
|
||||
&& matches!(cons_pat.kind, PatKind::Wild)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// this is partially taken from panic_unimplemented
|
||||
fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
||||
let expr = peel_blocks(expr);
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
||||
return false;
|
||||
};
|
||||
if is_panic(cx, macro_call.def_id) {
|
||||
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
|
||||
}
|
||||
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
|
||||
}
|
||||
|
||||
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
||||
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
|
||||
if matches!(
|
||||
|
|
|
|||
|
|
@ -490,9 +490,9 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
|||
format!("ClosureKind::Coroutine(CoroutineKind::Coroutine(Movability::{movability:?})")
|
||||
},
|
||||
},
|
||||
ClosureKind::CoroutineClosure(desugaring) => format!(
|
||||
"ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})"
|
||||
),
|
||||
ClosureKind::CoroutineClosure(desugaring) => {
|
||||
format!("ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})")
|
||||
},
|
||||
};
|
||||
|
||||
let ret_ty = match fn_decl.output {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_test_module_or_function;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
|
||||
|
|
@ -100,13 +101,15 @@ declare_clippy_lint! {
|
|||
pub struct WildcardImports {
|
||||
warn_on_all: bool,
|
||||
test_modules_deep: u32,
|
||||
allowed_segments: FxHashSet<String>,
|
||||
}
|
||||
|
||||
impl WildcardImports {
|
||||
pub fn new(warn_on_all: bool) -> Self {
|
||||
pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self {
|
||||
Self {
|
||||
warn_on_all,
|
||||
test_modules_deep: 0,
|
||||
allowed_segments: allowed_wildcard_imports,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,6 +193,7 @@ impl WildcardImports {
|
|||
item.span.from_expansion()
|
||||
|| is_prelude_import(segments)
|
||||
|| (is_super_only_import(segments) && self.test_modules_deep > 0)
|
||||
|| is_allowed_via_config(segments, &self.allowed_segments)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,10 +202,18 @@ impl WildcardImports {
|
|||
fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
|
||||
segments
|
||||
.iter()
|
||||
.any(|ps| ps.ident.name.as_str().contains(sym::prelude.as_str()))
|
||||
.any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
|
||||
}
|
||||
|
||||
// Allow "super::*" imports in tests.
|
||||
fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
|
||||
segments.len() == 1 && segments[0].ident.name == kw::Super
|
||||
}
|
||||
|
||||
// Allow skipping imports containing user configured segments,
|
||||
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
|
||||
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
|
||||
// segment matching need to be exact instead of using 'contains', in case user unintentionaly put
|
||||
// a single character in the config thus skipping most of the warnings.
|
||||
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue