Rollup merge of #120806 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? `@Manishearth`
This commit is contained in:
Matthias Krüger 2024-02-09 19:21:17 +01:00 committed by GitHub
commit 8f00ffc901
131 changed files with 3886 additions and 621 deletions

View file

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

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

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

View file

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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");
}
},
}
},
_ => (),

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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