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

This commit is contained in:
Philipp Krones 2025-05-15 19:19:08 +02:00
commit 0bb1b5bd3b
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
263 changed files with 5325 additions and 4470 deletions

View file

@ -468,7 +468,7 @@ declare_clippy_lint! {
/// #[ignore = "Some good reason"]
/// fn test() {}
/// ```
#[clippy::version = "1.85.0"]
#[clippy::version = "1.88.0"]
pub IGNORE_WITHOUT_REASON,
pedantic,
"ignored tests without messages"

View file

@ -26,16 +26,16 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
if namespace.is_none()
&& matches!(
name.as_str(),
"ambiguous_glob_reexports"
| "dead_code"
| "deprecated"
| "hidden_glob_reexports"
| "unreachable_pub"
| "unused"
| "unused_braces"
| "unused_import_braces"
| "unused_imports"
name,
sym::ambiguous_glob_reexports
| sym::dead_code
| sym::deprecated
| sym::hidden_glob_reexports
| sym::unreachable_pub
| sym::unused
| sym::unused_braces
| sym::unused_import_braces
| sym::unused_imports
)
{
return;
@ -43,16 +43,16 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
if namespace == Some(sym::clippy)
&& matches!(
name.as_str(),
"wildcard_imports"
| "enum_glob_use"
| "redundant_pub_crate"
| "macro_use_imports"
| "unsafe_removed_from_name"
| "module_name_repetitions"
| "single_component_path_imports"
| "disallowed_types"
| "unused_trait_names"
name,
sym::wildcard_imports
| sym::enum_glob_use
| sym::redundant_pub_crate
| sym::macro_use_imports
| sym::unsafe_removed_from_name
| sym::module_name_repetitions
| sym::single_component_path_imports
| sym::disallowed_types
| sym::unused_trait_names
)
{
return;

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPathWithoutReplacement, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{match_def_path, paths};
use clippy_utils::paths::{self, PathNS};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, DefIdMap};
use rustc_lint::{LateContext, LateLintPass};
@ -182,6 +182,7 @@ impl AwaitHolding {
let (def_ids, _) = create_disallowed_map(
tcx,
&conf.await_holding_invalid_types,
PathNS::Type,
crate::disallowed_types::def_kind_predicate,
"type",
false,
@ -275,12 +276,10 @@ fn emit_invalid_type(
}
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
cx.tcx.is_diagnostic_item(sym::MutexGuard, def_id)
|| cx.tcx.is_diagnostic_item(sym::RwLockReadGuard, def_id)
|| cx.tcx.is_diagnostic_item(sym::RwLockWriteGuard, def_id)
|| match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
|| match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
|| match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
match cx.tcx.get_diagnostic_name(def_id) {
Some(name) => matches!(name, sym::MutexGuard | sym::RwLockReadGuard | sym::RwLockWriteGuard),
None => paths::PARKING_LOT_GUARDS.iter().any(|guard| guard.matches(cx, def_id)),
}
}
fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sugg::Sugg;
use clippy_utils::sym;
use clippy_utils::ty::{implements_trait, is_copy};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -73,10 +74,9 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
return;
};
let macro_name = cx.tcx.item_name(macro_call.def_id);
let eq_macro = match macro_name.as_str() {
"assert_eq" | "debug_assert_eq" => true,
"assert_ne" | "debug_assert_ne" => false,
let eq_macro = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
Some(sym::assert_eq_macro | sym::debug_assert_eq_macro) => true,
Some(sym::assert_ne_macro | sym::debug_assert_ne_macro) => false,
_ => return,
};
let Some((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
@ -115,6 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
return;
}
let macro_name = cx.tcx.item_name(macro_call.def_id);
let macro_name = macro_name.as_str();
let non_eq_mac = &macro_name[..macro_name.len() - 3];
span_lint_and_then(

View file

@ -0,0 +1,82 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArg, Ty};
use rustc_span::def_id::DefId;
use rustc_span::{Symbol, sym};
use super::CONFUSING_METHOD_TO_NUMERIC_CAST;
fn get_primitive_ty_name(ty: Ty<'_>) -> Option<&'static str> {
match ty.kind() {
ty::Char => Some("char"),
ty::Int(int) => Some(int.name_str()),
ty::Uint(uint) => Some(uint.name_str()),
ty::Float(float) => Some(float.name_str()),
_ => None,
}
}
fn get_const_name_and_ty_name(
cx: &LateContext<'_>,
method_name: Symbol,
method_def_id: DefId,
generics: &[GenericArg<'_>],
) -> Option<(&'static str, &'static str)> {
let method_name = method_name.as_str();
let diagnostic_name = cx.tcx.get_diagnostic_name(method_def_id);
let ty_name = if diagnostic_name.is_some_and(|diag| diag == sym::cmp_ord_min || diag == sym::cmp_ord_max) {
// We get the type on which the `min`/`max` method of the `Ord` trait is implemented.
if let [ty] = generics
&& let Some(ty) = ty.as_type()
{
get_primitive_ty_name(ty)?
} else {
return None;
}
} else if let Some(impl_id) = cx.tcx.impl_of_method(method_def_id)
&& let Some(ty_name) = get_primitive_ty_name(cx.tcx.type_of(impl_id).instantiate_identity())
&& ["min", "max", "minimum", "maximum", "min_value", "max_value"].contains(&method_name)
{
ty_name
} else {
return None;
};
let const_name = if method_name.starts_with("max") { "MAX" } else { "MIN" };
Some((const_name, ty_name))
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
// We allow casts from any function type to any function type.
match cast_to.kind() {
ty::FnDef(..) | ty::FnPtr(..) => return,
_ => { /* continue to checks */ },
}
if let ty::FnDef(def_id, generics) = cast_from.kind()
&& let Some(method_name) = cx.tcx.opt_item_name(*def_id)
&& let Some((const_name, ty_name)) = get_const_name_and_ty_name(cx, method_name, *def_id, generics.as_slice())
{
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
span_lint_and_then(
cx,
CONFUSING_METHOD_TO_NUMERIC_CAST,
expr.span,
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
|diag| {
diag.span_suggestion_verbose(
expr.span,
"did you mean to use the associated constant?",
format!("{ty_name}::{const_name} as {cast_to}"),
applicability,
);
},
);
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::is_normalizable;
use clippy_utils::{expr_or_init, match_def_path, path_def_id, paths, std_or_core};
use clippy_utils::{expr_or_init, path_def_id, paths, std_or_core};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind};
@ -55,7 +54,7 @@ fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) ->
fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool {
if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind
&& let Some(fun_id) = path_def_id(cx, fun)
&& match_def_path(cx, fun_id, &paths::ALIGN_OF)
&& paths::ALIGN_OF.matches(cx, fun_id)
&& let Some(args) = path.segments.last().and_then(|seg| seg.args)
&& let [GenericArg::Type(generic_ty)] = args.args
{
@ -71,12 +70,10 @@ fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>)
return false;
}
let to_mid_ty = cx.typeck_results().node_type(to.hir_id);
is_normalizable(cx, cx.param_env, to_mid_ty)
&& cx
.tcx
.layout_of(cx.typing_env().as_query_input(to_mid_ty))
.is_ok_and(|layout| {
let align = u128::from(layout.align.abi.bytes());
u128::from(val) <= align
})
cx.tcx
.layout_of(cx.typing_env().as_query_input(to_mid_ty))
.is_ok_and(|layout| {
let align = u128::from(layout.align.abi.bytes());
u128::from(val) <= align
})
}

View file

@ -14,6 +14,7 @@ mod cast_sign_loss;
mod cast_slice_different_sizes;
mod cast_slice_from_raw_parts;
mod char_lit_as_u8;
mod confusing_method_to_numeric_cast;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
@ -780,12 +781,38 @@ declare_clippy_lint! {
/// let aligned = std::ptr::dangling::<u32>();
/// let mut_ptr: *mut i64 = std::ptr::dangling_mut();
/// ```
#[clippy::version = "1.87.0"]
#[clippy::version = "1.88.0"]
pub MANUAL_DANGLING_PTR,
style,
"casting small constant literals to pointers to create dangling pointers"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of a primitive method pointer like `max`/`min` to any integer type.
///
/// ### Why restrict this?
/// Casting a function pointer to an integer can have surprising results and can occur
/// accidentally if parentheses are omitted from a function call. If you aren't doing anything
/// low-level with function pointers then you can opt out of casting functions to integers in
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
/// pointer casts in your code.
///
/// ### Example
/// ```no_run
/// let _ = u16::max as usize;
/// ```
///
/// Use instead:
/// ```no_run
/// let _ = u16::MAX as usize;
/// ```
#[clippy::version = "1.86.0"]
pub CONFUSING_METHOD_TO_NUMERIC_CAST,
suspicious,
"casting a primitive method pointer to any integer type"
}
pub struct Casts {
msrv: Msrv,
}
@ -823,6 +850,7 @@ impl_lint_pass!(Casts => [
REF_AS_PTR,
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
CONFUSING_METHOD_TO_NUMERIC_CAST,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -847,6 +875,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to);
fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to);
confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);

View file

@ -0,0 +1,100 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg::Sugg;
use clippy_utils::visitors::is_const_evaluatable;
use clippy_utils::{is_in_const_context, is_mutable, is_trait_method};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for slice references with cloned references such as `&[f.clone()]`.
///
/// ### Why is this bad
///
/// A reference does not need to be owned in order to used as a slice.
///
/// ### Known problems
///
/// This lint does not know whether or not a clone implementation has side effects.
///
/// ### Example
///
/// ```ignore
/// let data = 10;
/// let data_ref = &data;
/// take_slice(&[data_ref.clone()]);
/// ```
/// Use instead:
/// ```ignore
/// use std::slice;
/// let data = 10;
/// let data_ref = &data;
/// take_slice(slice::from_ref(data_ref));
/// ```
#[clippy::version = "1.87.0"]
pub CLONED_REF_TO_SLICE_REFS,
perf,
"cloning a reference for slice references"
}
pub struct ClonedRefToSliceRefs<'a> {
msrv: &'a Msrv,
}
impl<'a> ClonedRefToSliceRefs<'a> {
pub fn new(conf: &'a Conf) -> Self {
Self { msrv: &conf.msrv }
}
}
impl_lint_pass!(ClonedRefToSliceRefs<'_> => [CLONED_REF_TO_SLICE_REFS]);
impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if self.msrv.meets(cx, {
if is_in_const_context(cx) {
msrvs::CONST_SLICE_FROM_REF
} else {
msrvs::SLICE_FROM_REF
}
})
// `&[foo.clone()]` expressions
&& let ExprKind::AddrOf(_, mutability, arr) = &expr.kind
// mutable references would have a different meaning
&& mutability.is_not()
// check for single item arrays
&& let ExprKind::Array([item]) = &arr.kind
// check for clones
&& let ExprKind::MethodCall(_, val, _, _) = item.kind
&& is_trait_method(cx, item, sym::Clone)
// check for immutability or purity
&& (!is_mutable(cx, val) || is_const_evaluatable(cx, val))
// get appropriate crate for `slice::from_ref`
&& let Some(builtin_crate) = clippy_utils::std_or_core(cx)
{
let mut sugg = Sugg::hir(cx, val, "_");
if !cx.typeck_results().expr_ty(val).is_ref() {
sugg = sugg.addr();
}
span_lint_and_sugg(
cx,
CLONED_REF_TO_SLICE_REFS,
expr.span,
format!("this call to `clone` can be replaced with `{builtin_crate}::slice::from_ref`"),
"try",
format!("{builtin_crate}::slice::from_ref({sugg})"),
Applicability::MaybeIncorrect,
);
}
}
}

View file

@ -1,11 +1,11 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
use rustc_ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -78,14 +78,14 @@ declare_clippy_lint! {
}
pub struct CollapsibleIf {
let_chains_enabled: bool,
msrv: Msrv,
lint_commented_code: bool,
}
impl CollapsibleIf {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
pub fn new(conf: &'static Conf) -> Self {
Self {
let_chains_enabled: tcx.features().let_chains(),
msrv: conf.msrv,
lint_commented_code: conf.lint_commented_code,
}
}
@ -127,7 +127,7 @@ impl CollapsibleIf {
if let Some(inner) = expr_block(then)
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
&& let ExprKind::If(check_inner, _, None) = &inner.kind
&& self.eligible_condition(check_inner)
&& self.eligible_condition(cx, check_inner)
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
@ -163,8 +163,9 @@ impl CollapsibleIf {
}
}
pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool {
self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..))
fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool {
!matches!(cond.kind, ExprKind::Let(..))
|| (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS))
}
}
@ -180,7 +181,7 @@ impl LateLintPass<'_> for CollapsibleIf {
{
Self::check_collapsible_else_if(cx, then.span, else_);
} else if else_.is_none()
&& self.eligible_condition(cond)
&& self.eligible_condition(cx, cond)
&& let ExprKind::Block(then, None) = then.kind
{
self.check_collapsible_if_if(cx, expr, cond, then);
@ -202,13 +203,12 @@ fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match block.stmts {
[] => block.expr,
[stmt] => {
if let StmtKind::Semi(expr) = stmt.kind {
Some(expr)
} else {
None
}
},
[
Stmt {
kind: StmtKind::Semi(expr),
..
},
] if block.expr.is_none() => Some(expr),
_ => None,
}
}

View file

@ -64,6 +64,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::CAST_SLICE_DIFFERENT_SIZES_INFO,
crate::casts::CAST_SLICE_FROM_RAW_PARTS_INFO,
crate::casts::CHAR_LIT_AS_U8_INFO,
crate::casts::CONFUSING_METHOD_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
@ -75,6 +76,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::ZERO_PTR_INFO,
crate::cfg_not_test::CFG_NOT_TEST_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO,
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
crate::collapsible_if::COLLAPSIBLE_IF_INFO,
@ -705,13 +707,9 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS_INFO,
crate::transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS_INFO,
crate::transmute::TRANSMUTE_BYTES_TO_STR_INFO,
crate::transmute::TRANSMUTE_FLOAT_TO_INT_INFO,
crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO,
crate::transmute::TRANSMUTE_INT_TO_CHAR_INFO,
crate::transmute::TRANSMUTE_INT_TO_FLOAT_INFO,
crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO,
crate::transmute::TRANSMUTE_NULL_TO_FN_INFO,
crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO,
crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO,
crate::transmute::TRANSMUTE_PTR_TO_REF_INFO,
crate::transmute::TRANSMUTE_UNDEFINED_REPR_INFO,

View file

@ -2,18 +2,18 @@
// Prefer to use those when possible.
macro_rules! declare_with_version {
($name:ident($name_version:ident): &[$ty:ty] = &[$(
($name:ident($name_version:ident) = [$(
#[clippy::version = $version:literal]
$e:expr,
)*]) => {
pub static $name: &[$ty] = &[$($e),*];
pub static $name: &[(&str, &str)] = &[$($e),*];
#[allow(unused)]
pub static $name_version: &[&str] = &[$($version),*];
};
}
#[rustfmt::skip]
declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
declare_with_version! { DEPRECATED(DEPRECATED_VERSION) = [
#[clippy::version = "pre 1.29.0"]
("clippy::should_assert_eq", "`assert!(a == b)` can now print the values the same way `assert_eq!(a, b) can"),
#[clippy::version = "pre 1.29.0"]
@ -44,11 +44,10 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"),
#[clippy::version = "1.86.0"]
("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"),
// end deprecated lints. used by `cargo dev deprecate_lint`
]}
#[rustfmt::skip]
declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
declare_with_version! { RENAMED(RENAMED_VERSION) = [
#[clippy::version = ""]
("clippy::almost_complete_letter_range", "clippy::almost_complete_range"),
#[clippy::version = ""]
@ -187,5 +186,12 @@ declare_with_version! { RENAMED(RENAMED_VERSION): &[(&str, &str)] = &[
("clippy::vtable_address_comparisons", "ambiguous_wide_pointer_comparisons"),
#[clippy::version = ""]
("clippy::reverse_range_loop", "clippy::reversed_empty_ranges"),
// end renamed lints. used by `cargo dev rename_lint`
#[clippy::version = "1.88.0"]
("clippy::transmute_int_to_float", "unnecessary_transmutes"),
#[clippy::version = "1.88.0"]
("clippy::transmute_int_to_char", "unnecessary_transmutes"),
#[clippy::version = "1.88.0"]
("clippy::transmute_float_to_int", "unnecessary_transmutes"),
#[clippy::version = "1.88.0"]
("clippy::transmute_num_to_bytes", "unnecessary_transmutes"),
]}

View file

@ -2,7 +2,7 @@ use std::ops::ControlFlow;
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths};
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, paths};
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
@ -377,7 +377,7 @@ fn check_unsafe_derive_deserialize<'tcx>(
}
if let Some(trait_def_id) = trait_ref.trait_def_id()
&& match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
&& paths::SERDE_DESERIALIZE.matches(cx, trait_def_id)
&& let ty::Adt(def, _) = ty.kind()
&& let Some(local_def_id) = def.did().as_local()
&& let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id)

View file

@ -1,8 +1,8 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::macros::macro_backtrace;
use clippy_utils::paths::PathNS;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefIdMap;
@ -76,6 +76,7 @@ impl DisallowedMacros {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_macros,
PathNS::Macro,
|def_kind| matches!(def_kind, DefKind::Macro(_)),
"macro",
false,

View file

@ -1,6 +1,7 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::PathNS;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{Expr, ExprKind};
@ -66,6 +67,7 @@ impl DisallowedMethods {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_methods,
PathNS::Value,
|def_kind| {
matches!(
def_kind,

View file

@ -1,6 +1,7 @@
use clippy_config::Conf;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths::PathNS;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
@ -60,7 +61,14 @@ pub struct DisallowedTypes {
impl DisallowedTypes {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true);
let (def_ids, prim_tys) = create_disallowed_map(
tcx,
&conf.disallowed_types,
PathNS::Type,
def_kind_predicate,
"type",
true,
);
Self { def_ids, prim_tys }
}

View file

@ -93,7 +93,7 @@ declare_clippy_lint! {
/// ```no_run
/// //! <code>[first](x)second</code>
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub DOC_LINK_CODE,
nursery,
"link with code back-to-back with other code"

View file

@ -759,12 +759,12 @@ impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
let recv_ty = cx.typeck_results().expr_ty(receiver);
if recv_ty.is_floating_point() && !is_no_std_crate(cx) && is_inherent_method_call(cx, expr) {
match path.ident.name.as_str() {
"ln" => check_ln1p(cx, expr, receiver),
"log" => check_log_base(cx, expr, receiver, args),
"powf" => check_powf(cx, expr, receiver, args),
"powi" => check_powi(cx, expr, receiver, args),
"sqrt" => check_hypot(cx, expr, receiver),
match path.ident.name {
sym::ln => check_ln1p(cx, expr, receiver),
sym::log => check_log_base(cx, expr, receiver, args),
sym::powf => check_powf(cx, expr, receiver, args),
sym::powi => check_powi(cx, expr, receiver, args),
sym::sqrt => check_hypot(cx, expr, receiver),
_ => {},
}
}

View file

@ -9,8 +9,8 @@ mod too_many_arguments;
mod too_many_lines;
use clippy_config::Conf;
use clippy_utils::def_path_def_ids;
use clippy_utils::msrvs::Msrv;
use clippy_utils::paths::{PathNS, lookup_path_str};
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_lint::{LateContext, LateLintPass};
@ -469,7 +469,7 @@ impl Functions {
trait_ids: conf
.allow_renamed_params_for
.iter()
.flat_map(|p| def_path_def_ids(tcx, &p.split("::").collect::<Vec<_>>()))
.flat_map(|p| lookup_path_str(tcx, PathNS::Type, p))
.collect(),
msrv: conf.msrv,
}

View file

@ -69,7 +69,7 @@ declare_clippy_lint! {
///
/// let result = a.saturating_sub(b);
/// ```
#[clippy::version = "1.44.0"]
#[clippy::version = "1.83.0"]
pub INVERTED_SATURATING_SUB,
correctness,
"Check if a variable is smaller than another one and still subtract from it even if smaller"

View file

@ -5,7 +5,7 @@ use clippy_utils::macros::span_is_local;
use clippy_utils::source::is_present_in_source;
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::symbol::Symbol;
@ -162,6 +162,7 @@ pub struct ItemNameRepetitions {
enum_threshold: u64,
struct_threshold: u64,
avoid_breaking_exported_api: bool,
allow_exact_repetitions: bool,
allow_private_module_inception: bool,
allowed_prefixes: FxHashSet<String>,
}
@ -173,6 +174,7 @@ impl ItemNameRepetitions {
enum_threshold: conf.enum_variant_name_threshold,
struct_threshold: conf.struct_field_name_threshold,
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
allow_exact_repetitions: conf.allow_exact_repetitions,
allow_private_module_inception: conf.allow_private_module_inception,
allowed_prefixes: conf.allowed_prefixes.iter().map(|s| to_camel_case(s)).collect(),
}
@ -405,6 +407,7 @@ fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>
if count_match_start(item_name, name).char_count == item_name_chars
&& name.chars().nth(item_name_chars).is_some_and(|c| !c.is_lowercase())
&& name.chars().nth(item_name_chars + 1).is_some_and(|c| !c.is_numeric())
&& !check_enum_tuple_path_match(name, variant.data)
{
span_lint_hir(
cx,
@ -420,7 +423,9 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
let name = variant.ident.name.as_str();
let item_name_chars = item_name.chars().count();
if count_match_end(item_name, name).char_count == item_name_chars {
if count_match_end(item_name, name).char_count == item_name_chars
&& !check_enum_tuple_path_match(name, variant.data)
{
span_lint_hir(
cx,
ENUM_VARIANT_NAMES,
@ -431,6 +436,27 @@ fn check_enum_end(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>)
}
}
/// Checks if an enum tuple variant contains a single field
/// whose qualified path contains the variant's name.
fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_>) -> bool {
// Only check single-field tuple variants
let VariantData::Tuple(fields, ..) = variant_data else {
return false;
};
if fields.len() != 1 {
return false;
}
// Check if field type is a path and contains the variant name
match fields[0].ty.kind {
TyKind::Path(QPath::Resolved(_, path)) => path
.segments
.iter()
.any(|segment| segment.ident.name.as_str() == variant_name),
TyKind::Path(QPath::TypeRelative(_, segment)) => segment.ident.name.as_str() == variant_name,
_ => false,
}
}
impl LateLintPass<'_> for ItemNameRepetitions {
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
let Some(_ident) = item.kind.ident() else { return };
@ -462,11 +488,21 @@ impl LateLintPass<'_> for ItemNameRepetitions {
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is accepted.
if cx.tcx.visibility(item.owner_id).is_public()
&& cx.tcx.visibility(mod_owner_id.def_id).is_public()
&& item_camel.len() > mod_camel.len()
{
// name. Having the same name is only accepted if `allow_exact_repetition` is set to `true`.
let both_are_public =
cx.tcx.visibility(item.owner_id).is_public() && cx.tcx.visibility(mod_owner_id.def_id).is_public();
if both_are_public && !self.allow_exact_repetitions && item_camel == *mod_camel {
span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name is the same as its containing module's name",
);
}
if both_are_public && item_camel.len() > mod_camel.len() {
let matching = count_match_start(mod_camel, &item_camel);
let rmatching = count_match_end(mod_camel, &item_camel);
let nchars = mod_camel.chars().count();

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
use clippy_utils::ty::{implements_trait, is_must_use_ty};
use clippy_utils::{is_from_proc_macro, is_must_use_func_call, paths};
use rustc_hir::{LetStmt, LocalSource, PatKind};
use rustc_lint::{LateContext, LateLintPass};
@ -129,12 +129,6 @@ declare_clippy_lint! {
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]);
const SYNC_GUARD_PATHS: [&[&str]; 3] = [
&paths::PARKING_LOT_MUTEX_GUARD,
&paths::PARKING_LOT_RWLOCK_READ_GUARD,
&paths::PARKING_LOT_RWLOCK_WRITE_GUARD,
];
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) {
if matches!(local.source, LocalSource::Normal)
@ -144,7 +138,9 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
{
let init_ty = cx.typeck_results().expr_ty(init);
let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {
GenericArgKind::Type(inner_ty) => SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path)),
GenericArgKind::Type(inner_ty) => inner_ty
.ty_adt_def()
.is_some_and(|adt| paths::PARKING_LOT_GUARDS.iter().any(|path| path.matches(cx, adt.did()))),
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
});
if contains_sync_guard {

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_errors::Applicability;
use rustc_hir::{LetStmt, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -32,13 +33,19 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
&& !local.span.in_external_macro(cx.tcx.sess.source_map())
&& !is_from_proc_macro(cx, ty)
{
span_lint_and_help(
span_lint_and_then(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration",
|diag| {
diag.span_suggestion_verbose(
ty.span.with_lo(local.pat.span.hi()),
"remove the explicit type `_` declaration",
"",
Applicability::MachineApplicable,
);
},
);
}
}

View file

@ -1,5 +1,4 @@
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
#![feature(macro_metavar_expr_concat)]
#![feature(f128)]
@ -7,7 +6,6 @@
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(iter_partition_in_place)]
#![feature(let_chains)]
#![feature(never_type)]
#![feature(round_char_boundary)]
#![feature(rustc_private)]
@ -96,6 +94,7 @@ mod cargo;
mod casts;
mod cfg_not_test;
mod checked_conversions;
mod cloned_ref_to_slice_refs;
mod cognitive_complexity;
mod collapsible_if;
mod collection_is_never_read;
@ -731,7 +730,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf)));
store.register_late_pass(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf)));
store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
store.register_early_pass(|| Box::new(precedence::Precedence));
store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
@ -748,7 +747,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(unused_self::UnusedSelf::new(conf)));
store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
store.register_late_pass(|_| Box::new(exit::Exit));
store.register_late_pass(|_| Box::new(to_digit_is_some::ToDigitIsSome));
store.register_late_pass(move |_| Box::new(to_digit_is_some::ToDigitIsSome::new(conf)));
store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(conf)));
store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(conf)));
store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
@ -944,5 +943,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf)));
store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap));
store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix));
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -88,7 +88,7 @@ declare_clippy_lint! {
/// x.chars()
/// }
/// ```
#[clippy::version = "1.84.0"]
#[clippy::version = "1.87.0"]
pub ELIDABLE_LIFETIME_NAMES,
pedantic,
"lifetime name that can be replaced with the anonymous lifetime"

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{HasSession, snippet_with_applicability};
use clippy_utils::ty::implements_trait;
use clippy_utils::ty::{implements_trait, is_slice_like};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{higher, peel_blocks_with_stmt, span_contains_comment};
use rustc_ast::ast::LitKind;
@ -58,6 +58,8 @@ pub(super) fn check<'tcx>(
&& let Res::Local(idx_hir) = idx_path.res
&& !is_local_used(cx, assignval, idx_hir)
&& msrv.meets(cx, msrvs::SLICE_FILL)
&& let slice_ty = cx.typeck_results().expr_ty(slice).peel_refs()
&& is_slice_like(cx, slice_ty)
{
sugg(cx, body, expr, slice.span, assignval.span);
}

View file

@ -778,7 +778,7 @@ declare_clippy_lint! {
/// let _ = s[idx..];
/// }
/// ```
#[clippy::version = "1.83.0"]
#[clippy::version = "1.88.0"]
pub CHAR_INDICES_AS_BYTE_INDICES,
correctness,
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"

View file

@ -36,7 +36,7 @@ declare_clippy_lint! {
/// a.abs_diff(b)
/// # ;
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.88.0"]
pub MANUAL_ABS_DIFF,
complexity,
"using an if-else pattern instead of `abs_diff`"

View file

@ -1,6 +1,7 @@
use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sym;
use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
@ -10,7 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{Ty, UintTy};
use rustc_session::declare_lint_pass;
use rustc_span::{Span, sym};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -47,9 +48,9 @@ enum MatchType<'a, 'b> {
fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) -> Option<(Span, MatchType<'a, 'b>)> {
if let MethodCall(path, expr, _, _) = kind {
let is_lower = match path.ident.name.as_str() {
"to_ascii_lowercase" => true,
"to_ascii_uppercase" => false,
let is_lower = match path.ident.name {
sym::to_ascii_lowercase => true,
sym::to_ascii_uppercase => false,
_ => return None,
};
let ty_raw = cx.typeck_results().expr_ty(expr);

View file

@ -4,11 +4,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_lint_allowed, is_never_expr, msrvs, pat_and_expr_can_be_question_mark, peel_blocks};
use clippy_utils::{
MaybePath, is_lint_allowed, is_never_expr, is_wild, msrvs, pat_and_expr_can_be_question_mark, path_res, peel_blocks,
};
use rustc_ast::BindingMode;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::{Arm, Expr, ExprKind, HirId, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LintContext};
use rustc_span::Span;
@ -91,14 +94,15 @@ impl<'tcx> QuestionMark {
let Some((idx, diverging_arm)) = diverging_arm_opt else {
return;
};
let pat_arm = &arms[1 - idx];
// If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
// However, if it arrives in second position, its pattern may cover some cases already covered
// by the diverging one.
// TODO: accept the non-diverging arm as a second position if patterns are disjointed.
if idx == 0 {
if idx == 0 && !is_arms_disjointed(cx, diverging_arm, pat_arm) {
return;
}
let pat_arm = &arms[1 - idx];
let Some(ident_map) = expr_simple_identity_map(local.pat, pat_arm.pat, pat_arm.body) else {
return;
};
@ -110,6 +114,63 @@ impl<'tcx> QuestionMark {
}
}
/// Checks if the patterns of the arms are disjointed. Currently, we only support patterns of simple
/// enum variants without nested patterns or bindings.
///
/// TODO: Support more complex patterns.
fn is_arms_disjointed(cx: &LateContext<'_>, arm1: &Arm<'_>, arm2: &Arm<'_>) -> bool {
if arm1.guard.is_some() || arm2.guard.is_some() {
return false;
}
if !is_enum_variant(cx, arm1.pat) || !is_enum_variant(cx, arm2.pat) {
return false;
}
true
}
/// Returns `true` if the given pattern is a variant of an enum.
pub fn is_enum_variant(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
struct Pat<'hir>(&'hir rustc_hir::Pat<'hir>);
impl<'hir> MaybePath<'hir> for Pat<'hir> {
fn qpath_opt(&self) -> Option<&QPath<'hir>> {
match self.0.kind {
PatKind::Struct(ref qpath, fields, _)
if fields
.iter()
.all(|field| is_wild(field.pat) || matches!(field.pat.kind, PatKind::Binding(..))) =>
{
Some(qpath)
},
PatKind::TupleStruct(ref qpath, pats, _)
if pats
.iter()
.all(|pat| is_wild(pat) || matches!(pat.kind, PatKind::Binding(..))) =>
{
Some(qpath)
},
PatKind::Expr(&PatExpr {
kind: PatExprKind::Path(ref qpath),
..
}) => Some(qpath),
_ => None,
}
}
fn hir_id(&self) -> HirId {
self.0.hir_id
}
}
let res = path_res(cx, &Pat(pat));
matches!(
res,
Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(CtorOf::Variant, _), _)
)
}
fn emit_manual_let_else(
cx: &LateContext<'_>,
span: Span,

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::msrvs::Msrv;
use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym};
use clippy_utils::{is_none_arm, msrvs, paths, peel_hir_expr_refs, sym};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal};
@ -80,26 +80,26 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
check_map(cx, callee, span, self.msrv);
}
},
ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name.as_str() {
"unwrap_or" => {
ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name {
sym::unwrap_or => {
if is_empty_slice(cx, or) {
check_map(cx, callee, span, self.msrv);
}
},
"unwrap_or_else" => {
sym::unwrap_or_else => {
if returns_empty_slice(cx, or) {
check_map(cx, callee, span, self.msrv);
}
},
_ => {},
},
ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name.as_str() {
"map_or" => {
ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name {
sym::map_or => {
if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
check_as_ref(cx, callee, span, self.msrv);
}
},
"map_or_else" => {
sym::map_or_else => {
if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) {
check_as_ref(cx, callee, span, self.msrv);
}
@ -220,5 +220,5 @@ fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
}
fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
clippy_utils::is_expr_path_def_path(cx, expr, &["core", "slice", "raw", "from_ref"])
paths::SLICE_FROM_REF.matches_path(cx, expr)
}

View file

@ -1,5 +1,6 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline};
use rustc_ast::{BindingMode, ByRef};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, Pat, PatExpr, PatExprKind, PatKind, QPath};
@ -16,7 +17,7 @@ use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT};
fn get_some(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<HirId> {
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
&& let PatKind::Binding(_, pat_id, _, _) = pat.kind
&& let PatKind::Binding(BindingMode(ByRef::No, _), pat_id, _, _) = pat.kind
&& let Some(def_id) = path.res.opt_def_id()
// Since it comes from a pattern binding, we need to get the parent to actually match
// against it.

View file

@ -62,7 +62,7 @@ declare_clippy_lint! {
/// let mut an_option = Some(0);
/// let taken = an_option.replace(1);
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub MEM_REPLACE_OPTION_WITH_SOME,
style,
"replacing an `Option` with `Some` instead of `replace()`"

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline};
use clippy_utils::sym;
use clippy_utils::ty::is_type_lang_item;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -21,8 +22,8 @@ pub(super) fn check<'tcx>(
) {
if let ExprKind::MethodCall(path_segment, ..) = recv.kind
&& matches!(
path_segment.ident.name.as_str(),
"to_lowercase" | "to_uppercase" | "to_ascii_lowercase" | "to_ascii_uppercase"
path_segment.ident.name,
sym::to_lowercase | sym::to_uppercase | sym::to_ascii_lowercase | sym::to_ascii_uppercase
)
{
return;

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{expr_or_init, paths};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
@ -8,13 +9,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args
if let [error_kind, error] = args
&& !expr.span.from_expansion()
&& !error_kind.span.from_expansion()
&& clippy_utils::is_expr_path_def_path(cx, path, &clippy_utils::paths::IO_ERROR_NEW)
&& clippy_utils::is_expr_path_def_path(
cx,
clippy_utils::expr_or_init(cx, error_kind),
&clippy_utils::paths::IO_ERRORKIND_OTHER,
)
&& let ExprKind::Path(QPath::TypeRelative(_, new_segment)) = path.kind
&& paths::IO_ERROR_NEW.matches_path(cx, path)
&& paths::IO_ERRORKIND_OTHER_CTOR.matches_path(cx, expr_or_init(cx, error_kind))
&& msrv.meets(cx, msrvs::IO_ERROR_OTHER)
{
span_lint_and_then(

View file

@ -1,13 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::{get_parent_expr, sym};
use rustc_ast::{LitKind, StrStyle};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{Span, Symbol, sym};
use rustc_span::{Span, Symbol};
use super::MANUAL_C_STR_LITERALS;
@ -71,15 +71,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
&& cx.tcx.sess.edition() >= Edition2021
&& msrv.meets(cx, msrvs::C_STR_LITERALS)
{
match fn_name.as_str() {
name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
match fn_name {
sym::from_bytes_with_nul | sym::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);
check_from_bytes(cx, expr, arg, fn_name);
},
"from_ptr" => check_from_ptr(cx, expr, arg),
sym::from_ptr => check_from_ptr(cx, expr, arg),
_ => {},
}
}
@ -106,13 +106,13 @@ fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
}
}
/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: Symbol) {
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" {
} else if method == sym::from_bytes_with_nul_unchecked {
// `*_unchecked` returns `&CStr` directly, nothing needs to be changed
(expr.span, Applicability::MachineApplicable)
} else {

View file

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_def_path, path_def_id};
use clippy_utils::{path_res, sym};
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_lint::LateContext;
use rustc_middle::ty::layout::LayoutOf;
@ -79,16 +80,15 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> {
}
let ty = cx.typeck_results().expr_ty(expr);
let ty_str = ty.to_string();
// `std::T::MAX` `std::T::MIN` constants
if let Some(id) = path_def_id(cx, expr) {
if match_def_path(cx, id, &["core", &ty_str, "MAX"]) {
return Some(MinMax::Max);
}
if match_def_path(cx, id, &["core", &ty_str, "MIN"]) {
return Some(MinMax::Min);
// `T::MAX` and `T::MIN` constants
if let hir::ExprKind::Path(hir::QPath::TypeRelative(base, seg)) = expr.kind
&& let Res::PrimTy(_) = path_res(cx, base)
{
match seg.ident.name {
sym::MAX => return Some(MinMax::Max),
sym::MIN => return Some(MinMax::Min),
_ => {},
}
}

View file

@ -4428,7 +4428,7 @@ declare_clippy_lint! {
/// let file = BufReader::new(std::fs::File::open("./bytes.txt").unwrap());
/// file.bytes();
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub UNBUFFERED_BYTES,
perf,
"calling .bytes() is very inefficient when data is not in memory"
@ -4453,7 +4453,7 @@ declare_clippy_lint! {
/// values.contains(&10)
/// }
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub MANUAL_CONTAINS,
perf,
"unnecessary `iter().any()` on slices that can be replaced with `contains()`"
@ -4709,6 +4709,8 @@ impl_lint_pass!(Methods => [
]);
/// Extracts a method call name, args, and `Span` of the method name.
/// This ensures that neither the receiver nor any of the arguments
/// come from expansion.
pub fn method_call<'tcx>(
recv: &'tcx Expr<'tcx>,
) -> Option<(&'tcx str, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> {
@ -4907,6 +4909,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
impl Methods {
#[allow(clippy::too_many_lines)]
fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Handle method calls whose receiver and arguments may not come from expansion
if let Some((name, recv, args, span, call_span)) = method_call(expr) {
match (name, args) {
("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
@ -5049,29 +5052,12 @@ impl Methods {
Some(("err", recv, [], err_span, _)) => {
err_expect::check(cx, expr, recv, span, err_span, self.msrv);
},
_ => unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
),
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("expect_err", [_]) => {
("expect_err", [_]) | ("unwrap_err" | "unwrap_unchecked" | "unwrap_err_unchecked", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("extend", [arg]) => {
string_extend_chars::check(cx, expr, recv, arg);
@ -5437,27 +5423,6 @@ impl Methods {
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_err", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_or", [u_arg]) => {
match method_call(recv) {
@ -5486,9 +5451,6 @@ impl Methods {
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_unchecked" | "unwrap_err_unchecked", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("unwrap_or_else", [u_arg]) => {
match method_call(recv) {
Some(("map", recv, [map_arg], _, _))
@ -5526,6 +5488,56 @@ impl Methods {
_ => {},
}
}
// Handle method calls whose receiver and arguments may come from expansion
if let ExprKind::MethodCall(path, recv, args, _call_span) = expr.kind {
match (path.ident.name.as_str(), args) {
("expect", [_]) if !matches!(method_call(recv), Some(("ok" | "err", _, [], _, _))) => {
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("expect_err", [_]) => {
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_expect_in_consts,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("unwrap", []) => {
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_err", []) => {
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_unwrap_in_consts,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
_ => {},
}
}
}
}

View file

@ -7,9 +7,8 @@ use rustc_span::Span;
use super::NEEDLESS_CHARACTER_ITERATION;
use super::utils::get_last_chain_binding_hir_id;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths::CHAR_IS_ASCII;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::{match_def_path, path_to_local_id, peel_blocks, sym};
use clippy_utils::{is_path_diagnostic_item, path_to_local_id, peel_blocks, sym};
fn peels_expr_ref<'a, 'tcx>(mut expr: &'a Expr<'tcx>) -> &'a Expr<'tcx> {
while let ExprKind::AddrOf(_, _, e) = expr.kind {
@ -76,9 +75,7 @@ fn handle_expr(
// If we have `!is_ascii`, then only `.any()` should warn. And if the condition is
// `is_ascii`, then only `.all()` should warn.
if revert != is_all
&& let ExprKind::Path(path) = fn_path.kind
&& let Some(fn_def_id) = cx.qpath_res(&path, fn_path.hir_id).opt_def_id()
&& match_def_path(cx, fn_def_id, &CHAR_IS_ASCII)
&& is_path_diagnostic_item(cx, fn_path, sym::char_is_ascii)
&& path_to_local_id(peels_expr_ref(arg), first_param)
&& let Some(snippet) = before_chars.get_source_text(cx)
{

View file

@ -357,20 +357,20 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
if let Some(hir_id) = self.current_statement_hir_id {
self.hir_id_uses_map.insert(hir_id, self.uses.len());
}
match method_name.ident.name.as_str() {
"into_iter" => self.uses.push(Some(IterFunction {
match method_name.ident.name {
sym::into_iter => self.uses.push(Some(IterFunction {
func: IterFunctionKind::IntoIter(expr.hir_id),
span: expr.span,
})),
"len" => self.uses.push(Some(IterFunction {
sym::len => self.uses.push(Some(IterFunction {
func: IterFunctionKind::Len,
span: expr.span,
})),
"is_empty" => self.uses.push(Some(IterFunction {
sym::is_empty => self.uses.push(Some(IterFunction {
func: IterFunctionKind::IsEmpty,
span: expr.span,
})),
"contains" => self.uses.push(Some(IterFunction {
sym::contains => self.uses.push(Some(IterFunction {
func: IterFunctionKind::Contains(args[0].span),
span: expr.span,
})),

View file

@ -1,8 +1,8 @@
use rustc_data_structures::fx::FxHashMap;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
use clippy_utils::{match_any_def_paths, paths};
use clippy_utils::paths;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_ast::ast::LitKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
@ -13,7 +13,7 @@ use rustc_span::{Span, sym};
use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS};
fn is_open_options(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || match_type(cx, ty, &paths::TOKIO_IO_OPEN_OPTIONS)
is_type_diagnostic_item(cx, ty, sym::FsOpenOptions) || paths::TOKIO_IO_OPEN_OPTIONS.matches_ty(cx, ty)
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
@ -126,14 +126,14 @@ fn get_open_options(
&& let ExprKind::Path(path) = callee.kind
&& let Some(did) = cx.qpath_res(&path, callee.hir_id).opt_def_id()
{
let std_file_options = [sym::file_options, sym::open_options_new];
let is_std_options = matches!(
cx.tcx.get_diagnostic_name(did),
Some(sym::file_options | sym::open_options_new)
);
let tokio_file_options: &[&[&str]] = &[&paths::TOKIO_IO_OPEN_OPTIONS_NEW, &paths::TOKIO_FILE_OPTIONS];
let is_std_options = std_file_options
.into_iter()
.any(|sym| cx.tcx.is_diagnostic_item(sym, did));
is_std_options || match_any_def_paths(cx, did, tokio_file_options).is_some()
is_std_options
|| paths::TOKIO_IO_OPEN_OPTIONS_NEW.matches(cx, did)
|| paths::TOKIO_FILE_OPTIONS.matches(cx, did)
} else {
false
}

View file

@ -9,7 +9,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability};
use clippy_utils::ty::get_type_diagnostic_name;
use clippy_utils::visitors::for_each_unconsumed_temporary;
use clippy_utils::{is_expr_final_block_expr, peel_blocks};
use clippy_utils::{get_parent_expr, peel_blocks};
use super::RETURN_AND_THEN;
@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
recv: &'tcx hir::Expr<'tcx>,
arg: &'tcx hir::Expr<'_>,
) {
if !is_expr_final_block_expr(cx.tcx, expr) {
if cx.tcx.hir_get_fn_id_for_return_block(expr.hir_id).is_none() {
return;
}
@ -55,12 +55,24 @@ pub(super) fn check<'tcx>(
None => &body_snip,
};
let sugg = format!(
"let {} = {}?;\n{}",
arg_snip,
recv_snip,
reindent_multiline(inner, false, indent_of(cx, expr.span))
);
// If suggestion is going to get inserted as part of a `return` expression, it must be blockified.
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
let base_indent = indent_of(cx, parent_expr.span);
let inner_indent = base_indent.map(|i| i + 4);
format!(
"{}\n{}\n{}",
reindent_multiline(&format!("{{\nlet {arg_snip} = {recv_snip}?;"), true, inner_indent),
reindent_multiline(inner, false, inner_indent),
reindent_multiline("}", false, base_indent),
)
} else {
format!(
"let {} = {}?;\n{}",
arg_snip,
recv_snip,
reindent_multiline(inner, false, indent_of(cx, expr.span))
)
};
span_lint_and_sugg(
cx,

View file

@ -4,7 +4,7 @@ use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::{Descend, for_each_expr};
use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths};
use clippy_utils::{is_diag_item_method, path_to_local_id, paths};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::{
@ -288,7 +288,7 @@ fn parse_iter_usage<'tcx>(
match (name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span),
("next_tuple", []) => {
return if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE)
return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did)
&& let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind()
&& cx.tcx.is_diagnostic_item(sym::Option, adt_def.did())
&& let ty::Tuple(subs) = subs.type_at(0).kind()

View file

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

View file

@ -1,9 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, should_call_clone_as_function, walk_ptrs_ty_depth};
use clippy_utils::{
get_parent_expr, is_diag_trait_item, match_def_path, path_to_local_id, peel_blocks, strip_pat_refs,
};
use clippy_utils::{get_parent_expr, is_diag_trait_item, path_to_local_id, peel_blocks, strip_pat_refs};
use rustc_errors::Applicability;
use rustc_hir::{self as hir, LangItem};
use rustc_lint::LateContext;
@ -81,8 +79,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
applicability,
);
}
} else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
|| match_def_path(cx, def_id, &["core", "result", "Result", call_name])
} else if let Some(impl_id) = cx.tcx.opt_parent(def_id)
&& let Some(adt) = cx.tcx.type_of(impl_id).instantiate_identity().ty_adt_def()
&& (cx.tcx.lang_items().option_type() == Some(adt.did()) || cx.tcx.is_diagnostic_item(sym::Result, adt.did()))
{
let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();

View file

@ -155,9 +155,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
return;
}
let mir = cx.tcx.mir_drops_elaborated_and_const_checked(def_id);
let mir = cx.tcx.optimized_mir(def_id);
if let Ok(()) = is_min_const_fn(cx, &mir.borrow(), self.msrv)
if let Ok(()) = is_min_const_fn(cx, mir, self.msrv)
&& let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) =
cx.tcx.hir_node_by_def_id(def_id)
{

View file

@ -48,6 +48,8 @@ pub struct MissingDoc {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
crate_items_only: bool,
/// Whether to allow fields starting with an underscore to skip documentation requirements
allow_unused: bool,
/// Stack of whether #[doc(hidden)] is set
/// at each level which has lint attributes.
doc_hidden_stack: Vec<bool>,
@ -59,6 +61,7 @@ impl MissingDoc {
pub fn new(conf: &'static Conf) -> Self {
Self {
crate_items_only: conf.missing_docs_in_crate_items,
allow_unused: conf.missing_docs_allow_unused,
doc_hidden_stack: vec![false],
prev_span: None,
}
@ -260,11 +263,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
}
fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
if !sf.is_positional() {
if !(sf.is_positional()
|| is_from_proc_macro(cx, sf)
|| self.allow_unused && sf.ident.as_str().starts_with('_'))
{
let attrs = cx.tcx.hir_attrs(sf.hir_id);
if !is_from_proc_macro(cx, sf) {
self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
}
self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field");
}
self.prev_span = Some(sf.span);
}

View file

@ -1,6 +1,6 @@
use clippy_config::Conf;
use clippy_utils::def_path_def_ids;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths::{PathNS, lookup_path_str};
use clippy_utils::source::SpanRangeExt;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -56,8 +56,12 @@ impl ImportRename {
renames: conf
.enforced_import_renames
.iter()
.map(|x| (x.path.split("::").collect::<Vec<_>>(), Symbol::intern(&x.rename)))
.flat_map(|(path, rename)| def_path_def_ids(tcx, &path).map(move |id| (id, rename)))
.map(|x| (&x.path, Symbol::intern(&x.rename)))
.flat_map(|(path, rename)| {
lookup_path_str(tcx, PathNS::Arbitrary, path)
.into_iter()
.map(move |id| (id, rename))
})
.collect(),
}
}

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sym;
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
use rustc_lint::{LateContext, LateLintPass};
@ -42,10 +43,9 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
let Some(macro_call) = root_macro_call_first_node(cx, e) else {
return;
};
let macro_name = cx.tcx.item_name(macro_call.def_id);
if !matches!(
macro_name.as_str(),
"debug_assert" | "debug_assert_eq" | "debug_assert_ne"
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::debug_assert_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
) {
return;
}
@ -60,7 +60,10 @@ impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
cx,
DEBUG_ASSERT_WITH_MUT_CALL,
span,
format!("do not call a function with mutable arguments inside of `{macro_name}!`"),
format!(
"do not call a function with mutable arguments inside of `{}!`",
cx.tcx.item_name(macro_call.def_id)
),
);
}
}
@ -96,10 +99,6 @@ impl<'tcx> Visitor<'tcx> for MutArgVisitor<'_, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id)
&& adj

View file

@ -1,13 +1,14 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::diagnostics::{span_lint, span_lint_hir_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::paths::{self, PathNS, find_crates, lookup_path_str};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{def_path_def_ids, fn_def_id, is_no_std_crate, path_def_id};
use clippy_utils::{fn_def_id, is_no_std_crate, path_def_id, sym};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId};
use rustc_hir::{self as hir, BodyId, Expr, ExprKind, Item, ItemKind};
use rustc_hir::{self as hir, BodyId, Expr, ExprKind, HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -62,10 +63,7 @@ static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[
pub struct NonStdLazyStatic {
msrv: Msrv,
lazy_static_lazy_static: Vec<DefId>,
once_cell_crate: Vec<CrateNum>,
once_cell_sync_lazy: Vec<DefId>,
once_cell_sync_lazy_new: Vec<DefId>,
once_cell_crates: Vec<CrateNum>,
sugg_map: FxIndexMap<DefId, Option<String>>,
lazy_type_defs: FxIndexMap<DefId, LazyInfo>,
uses_other_once_cell_types: bool,
@ -76,10 +74,7 @@ impl NonStdLazyStatic {
pub fn new(conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv,
lazy_static_lazy_static: Vec::new(),
once_cell_crate: Vec::new(),
once_cell_sync_lazy: Vec::new(),
once_cell_sync_lazy_new: Vec::new(),
once_cell_crates: Vec::new(),
sugg_map: FxIndexMap::default(),
lazy_type_defs: FxIndexMap::default(),
uses_other_once_cell_types: false,
@ -95,17 +90,15 @@ fn can_use_lazy_cell(cx: &LateContext<'_>, msrv: Msrv) -> bool {
impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
fn check_crate(&mut self, cx: &LateContext<'hir>) {
// Fetch def_ids for external paths
self.lazy_static_lazy_static = def_path_def_ids(cx.tcx, &["lazy_static", "lazy_static"]).collect();
self.once_cell_sync_lazy = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy"]).collect();
self.once_cell_sync_lazy_new = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy", "new"]).collect();
// And CrateNums for `once_cell` crate
self.once_cell_crate = self.once_cell_sync_lazy.iter().map(|d| d.krate).collect();
// Add CrateNums for `once_cell` crate
self.once_cell_crates = find_crates(cx.tcx, sym::once_cell)
.iter()
.map(|def_id| def_id.krate)
.collect();
// Convert hardcoded fn replacement list into a map with def_id
for (path, sugg) in FUNCTION_REPLACEMENTS {
let path_vec: Vec<&str> = path.split("::").collect();
for did in def_path_def_ids(cx.tcx, &path_vec) {
for did in lookup_path_str(cx.tcx, PathNS::Value, path) {
self.sugg_map.insert(did, sugg.map(ToOwned::to_owned));
}
}
@ -114,7 +107,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
fn check_item(&mut self, cx: &LateContext<'hir>, item: &Item<'hir>) {
if let ItemKind::Static(..) = item.kind
&& let Some(macro_call) = clippy_utils::macros::root_macro_call(item.span)
&& self.lazy_static_lazy_static.contains(&macro_call.def_id)
&& paths::LAZY_STATIC.matches(cx, macro_call.def_id)
&& can_use_lazy_cell(cx, self.msrv)
{
span_lint(
@ -130,7 +123,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
return;
}
if let Some(lazy_info) = LazyInfo::from_item(self, cx, item)
if let Some(lazy_info) = LazyInfo::from_item(cx, item)
&& can_use_lazy_cell(cx, self.msrv)
{
self.lazy_type_defs.insert(item.owner_id.to_def_id(), lazy_info);
@ -155,9 +148,9 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic {
if let rustc_hir::TyKind::Path(qpath) = ty.peel_refs().kind
&& let Some(ty_def_id) = cx.qpath_res(&qpath, ty.hir_id).opt_def_id()
// Is from `once_cell` crate
&& self.once_cell_crate.contains(&ty_def_id.krate)
&& self.once_cell_crates.contains(&ty_def_id.krate)
// And is NOT `once_cell::sync::Lazy`
&& !self.once_cell_sync_lazy.contains(&ty_def_id)
&& !paths::ONCE_CELL_SYNC_LAZY.matches(cx, ty_def_id)
{
self.uses_other_once_cell_types = true;
}
@ -180,6 +173,8 @@ struct LazyInfo {
/// // ^^^^
/// ```
ty_span_no_args: Span,
/// Item on which the lint must be generated.
item_hir_id: HirId,
/// `Span` and `DefId` of calls on `Lazy` type.
/// i.e.:
/// ```ignore
@ -190,12 +185,12 @@ struct LazyInfo {
}
impl LazyInfo {
fn from_item(state: &NonStdLazyStatic, cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> {
fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Option<Self> {
// Check if item is a `once_cell:sync::Lazy` static.
if let ItemKind::Static(_, ty, _, body_id) = item.kind
&& let Some(path_def_id) = path_def_id(cx, ty)
&& let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
&& state.once_cell_sync_lazy.contains(&path_def_id)
&& paths::ONCE_CELL_SYNC_LAZY.matches(cx, path_def_id)
{
let ty_span_no_args = path_span_without_args(path);
let body = cx.tcx.hir_body(body_id);
@ -204,7 +199,7 @@ impl LazyInfo {
let mut new_fn_calls = FxIndexMap::default();
for_each_expr::<(), ()>(cx, body, |ex| {
if let Some((fn_did, call_span)) = fn_def_id_and_span_from_body(cx, ex, body_id)
&& state.once_cell_sync_lazy_new.contains(&fn_did)
&& paths::ONCE_CELL_SYNC_LAZY_NEW.matches(cx, fn_did)
{
new_fn_calls.insert(call_span, fn_did);
}
@ -213,6 +208,7 @@ impl LazyInfo {
Some(LazyInfo {
ty_span_no_args,
item_hir_id: item.hir_id(),
calls_span_and_id: new_fn_calls,
})
} else {
@ -236,9 +232,10 @@ impl LazyInfo {
}
}
span_lint_and_then(
span_lint_hir_and_then(
cx,
NON_STD_LAZY_STATICS,
self.item_hir_id,
self.ty_span_no_args,
"this type has been superseded by `LazyLock` in the standard library",
|diag| {

View file

@ -1,20 +1,18 @@
use clippy_utils::ast_utils::is_useless_with_eq_exprs;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
use clippy_utils::{eq_expr_value, is_in_test_function};
use clippy_utils::{eq_expr_value, is_in_test_function, sym};
use rustc_hir::{BinOpKind, Expr};
use rustc_lint::LateContext;
use super::EQ_OP;
pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let Some((macro_call, macro_name)) = first_node_macro_backtrace(cx, e).find_map(|macro_call| {
let name = cx.tcx.item_name(macro_call.def_id);
if let Some(macro_call) = first_node_macro_backtrace(cx, e).find(|macro_call| {
matches!(
name.as_str(),
"assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne"
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro)
)
.then(|| (macro_call, name))
}) && let Some((lhs, rhs, _)) = find_assert_eq_args(cx, e, macro_call.expn)
&& eq_expr_value(cx, lhs, rhs)
&& macro_call.is_local()
@ -24,7 +22,10 @@ pub(crate) fn check_assert<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
cx,
EQ_OP,
lhs.span.to(rhs.span),
format!("identical args used in this `{macro_name}!` macro call"),
format!(
"identical args used in this `{}!` macro call",
cx.tcx.item_name(macro_call.def_id)
),
);
}
}

View file

@ -1,6 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use super::INTEGER_DIVISION;
@ -13,7 +15,8 @@ pub(crate) fn check<'tcx>(
) {
if op == hir::BinOpKind::Div
&& cx.typeck_results().expr_ty(left).is_integral()
&& cx.typeck_results().expr_ty(right).is_integral()
&& let right_ty = cx.typeck_results().expr_ty(right)
&& (right_ty.is_integral() || is_type_diagnostic_item(cx, right_ty, sym::NonZero))
{
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, INTEGER_DIVISION, expr.span, "integer division", |diag| {

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{Descend, for_each_expr};
use clippy_utils::{is_inside_always_const_context, return_ty};
@ -69,10 +69,11 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir
return ControlFlow::Continue(Descend::Yes);
};
if !is_inside_always_const_context(cx.tcx, e.hir_id)
&& matches!(
cx.tcx.item_name(macro_call.def_id).as_str(),
"panic" | "assert" | "assert_eq" | "assert_ne"
)
&& (is_panic(cx, macro_call.def_id)
|| matches!(
cx.tcx.get_diagnostic_name(macro_call.def_id),
Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro)
))
{
panics.push(macro_call.span);
ControlFlow::Continue(Descend::No)

View file

@ -113,8 +113,8 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
);
return;
}
match cx.tcx.item_name(macro_call.def_id).as_str() {
"todo" => {
match cx.tcx.get_diagnostic_name(macro_call.def_id) {
Some(sym::todo_macro) => {
span_lint(
cx,
TODO,
@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
"`todo` should not be present in production code",
);
},
"unimplemented" => {
Some(sym::unimplemented_macro) => {
span_lint(
cx,
UNIMPLEMENTED,
@ -130,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
"`unimplemented` should not be present in production code",
);
},
"unreachable" => {
Some(sym::unreachable_macro) => {
span_lint(cx, UNREACHABLE, macro_call.span, "usage of the `unreachable!` macro");
},
_ => {},

View file

@ -2,8 +2,9 @@ use std::fmt::Display;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::paths::PathLookup;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::{def_path_res_with_base, find_crates, path_def_id, paths, sym};
use clippy_utils::{path_def_id, paths};
use rustc_ast::ast::{LitKind, StrStyle};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{BorrowKind, Expr, ExprKind, OwnerId};
@ -121,17 +122,9 @@ impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX, REGEX_CREATION_IN_LOOPS]
impl<'tcx> LateLintPass<'tcx> for Regex {
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
// We don't use `match_def_path` here because that relies on matching the exact path, which changed
// between regex 1.8 and 1.9
//
// `def_path_res_with_base` will resolve through re-exports but is relatively heavy, so we only
// perform the operation once and store the results
let regex_crates = find_crates(cx.tcx, sym::regex);
let mut resolve = |path: &[&str], kind: RegexKind| {
for res in def_path_res_with_base(cx.tcx, regex_crates.clone(), &path[1..]) {
if let Some(id) = res.opt_def_id() {
self.definitions.insert(id, kind);
}
let mut resolve = |path: &PathLookup, kind: RegexKind| {
for &id in path.get(cx) {
self.definitions.insert(id, kind);
}
};

View file

@ -5,7 +5,7 @@ use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor,
leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg,
span_find_starting_semi,
span_find_starting_semi, sym,
};
use core::ops::ControlFlow;
use rustc_ast::MetaItemInner;
@ -22,7 +22,7 @@ use rustc_middle::ty::{self, GenericArgKind, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::edition::Edition;
use rustc_span::{BytePos, Pos, Span, sym};
use rustc_span::{BytePos, Pos, Span};
use std::borrow::Cow;
use std::fmt::Display;
@ -411,8 +411,8 @@ fn check_final_expr<'tcx>(
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
&& tool.ident.name == sym::clippy
&& matches!(
lint_name.ident.name.as_str(),
"needless_return" | "style" | "all" | "warnings"
lint_name.ident.name,
sym::needless_return | sym::style | sym::all | sym::warnings
)
{
// This is an expectation of the `needless_return` lint

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{get_trait_def_id, paths};
use clippy_utils::paths;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -32,9 +32,7 @@ impl<'tcx> LateLintPass<'tcx> for SerdeApi {
}) = item.kind
{
let did = trait_ref.path.res.def_id();
if let Some(visit_did) = get_trait_def_id(cx.tcx, &paths::SERDE_DE_VISITOR)
&& did == visit_did
{
if paths::SERDE_DE_VISITOR.matches(cx, did) {
let mut seen_str = None;
let mut seen_string = None;
for item in *items {

View file

@ -30,7 +30,7 @@ declare_clippy_lint! {
/// param * 2
/// }
/// ```
#[clippy::version = "1.86.0"]
#[clippy::version = "1.87.0"]
pub SINGLE_OPTION_MAP,
nursery,
"Checks for functions with method calls to `.map(_)` on an arg of type `Option` as the outermost expression."

View file

@ -3,7 +3,7 @@ use clippy_utils::higher::VecArgs;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, is_no_std_crate};
use clippy_utils::{is_no_std_crate, paths};
use rustc_ast::{LitIntType, LitKind, UintTy};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr};
@ -100,7 +100,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit {
&& let Some(start_snippet) = start.span.get_source_text(cx)
&& let Some(end_snippet) = end.span.get_source_text(cx)
{
let should_emit_every_value = if let Some(step_def_id) = get_trait_def_id(cx.tcx, &["core", "iter", "Step"])
let should_emit_every_value = if let Some(step_def_id) = paths::ITER_STEP.only(cx)
&& implements_trait(cx, ty, step_def_id, &[])
{
true

View file

@ -334,7 +334,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
if let ExprKind::MethodCall(path, recv, [], _) = &e.kind
&& path.ident.name == sym::into_bytes
&& let ExprKind::MethodCall(path, recv, [], _) = &recv.kind
&& matches!(path.ident.name.as_str(), "to_owned" | "to_string")
&& matches!(path.ident.name, sym::to_owned | sym::to_string)
&& let ExprKind::Lit(lit) = &recv.kind
&& let LitKind::Str(lit_content, _) = &lit.node
&& lit_content.as_str().is_ascii()

View file

@ -1,11 +1,12 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_def_path, sym};
use clippy_utils::{is_in_const_context, paths, sym};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
@ -33,34 +34,35 @@ declare_clippy_lint! {
"`char.is_digit()` is clearer"
}
declare_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]);
impl_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]);
pub(crate) struct ToDigitIsSome {
msrv: Msrv,
}
impl ToDigitIsSome {
pub(crate) fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let hir::ExprKind::MethodCall(is_some_path, to_digit_expr, [], _) = &expr.kind
&& is_some_path.ident.name == sym::is_some
{
let match_result = match &to_digit_expr.kind {
let match_result = match to_digit_expr.kind {
hir::ExprKind::MethodCall(to_digits_path, char_arg, [radix_arg], _) => {
if to_digits_path.ident.name == sym::to_digit
&& let char_arg_ty = cx.typeck_results().expr_ty_adjusted(char_arg)
&& *char_arg_ty.kind() == ty::Char
&& cx.typeck_results().expr_ty_adjusted(char_arg).is_char()
{
Some((true, *char_arg, radix_arg))
Some((true, char_arg, radix_arg))
} else {
None
}
},
hir::ExprKind::Call(to_digits_call, [char_arg, radix_arg]) => {
if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind
&& let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id)
&& let Some(to_digits_def_id) = to_digits_call_res.opt_def_id()
&& match_def_path(
cx,
to_digits_def_id,
&["core", "char", "methods", "<impl char>", "to_digit"],
)
{
if paths::CHAR_TO_DIGIT.matches_path(cx, to_digits_call) {
Some((false, char_arg, radix_arg))
} else {
None
@ -69,7 +71,9 @@ impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
_ => None,
};
if let Some((is_method_call, char_arg, radix_arg)) = match_result {
if let Some((is_method_call, char_arg, radix_arg)) = match_result
&& (!is_in_const_context(cx) || self.msrv.meets(cx, msrvs::CONST_CHAR_IS_DIGIT))
{
let mut applicability = Applicability::MachineApplicable;
let char_arg_snip = snippet_with_applicability(cx, char_arg.span, "_", &mut applicability);
let radix_snip = snippet_with_applicability(cx, radix_arg.span, "_", &mut applicability);

View file

@ -15,7 +15,7 @@ use rustc_hir::{
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{BytePos, Span};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -282,18 +282,18 @@ impl TraitBounds {
.iter()
.copied()
.chain(p.bounds.iter())
.filter_map(get_trait_info_from_bound)
.map(|(_, _, span)| snippet_with_applicability(cx, span, "..", &mut applicability))
.map(|bound| snippet_with_applicability(cx, bound.span(), "_", &mut applicability))
.join(" + ");
let hint_string = format!(
"consider combining the bounds: `{}: {trait_bounds}`",
snippet(cx, p.bounded_ty.span, "_"),
);
let ty_name = snippet(cx, p.bounded_ty.span, "_");
span_lint_and_help(
cx,
TYPE_REPETITION_IN_BOUNDS,
bound.span,
"this type has already been used as a bound predicate",
format!("type `{ty_name}` has already been used as a bound predicate"),
None,
hint_string,
);
@ -395,15 +395,7 @@ impl Hash for ComparableTraitRef<'_, '_> {
fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
if let GenericBound::Trait(t) = bound {
let trait_path = t.trait_ref.path;
let trait_span = {
let path_span = trait_path.span;
if let BoundPolarity::Maybe(_) = t.modifiers.polarity {
path_span.with_lo(path_span.lo() - BytePos(1)) // include the `?`
} else {
path_span
}
};
Some((trait_path.res, trait_path.segments, trait_span))
Some((trait_path.res, trait_path.segments, t.span))
} else {
None
}

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_normalizable;
use clippy_utils::{eq_expr_value, path_to_local, sym};
use rustc_abi::WrappingRange;
use rustc_errors::Applicability;
@ -84,8 +83,6 @@ pub(super) fn check<'tcx>(
&& path.ident.name == sym::then_some
&& is_local_with_projections(transmutable)
&& binops_with_local(cx, transmutable, receiver)
&& is_normalizable(cx, cx.param_env, from_ty)
&& is_normalizable(cx, cx.param_env, to_ty)
// we only want to lint if the target type has a niche that is larger than the one of the source type
// e.g. `u8` to `NonZero<u8>` should lint, but `NonZero<u8>` to `u8` should not
&& let Ok(from_layout) = cx.tcx.layout_of(cx.typing_env().as_query_input(from_ty))

View file

@ -1,13 +1,9 @@
mod crosspointer_transmute;
mod eager_transmute;
mod missing_transmute_annotations;
mod transmute_float_to_int;
mod transmute_int_to_bool;
mod transmute_int_to_char;
mod transmute_int_to_float;
mod transmute_int_to_non_zero;
mod transmute_null_to_fn;
mod transmute_num_to_bytes;
mod transmute_ptr_to_ptr;
mod transmute_ptr_to_ref;
mod transmute_ref_to_ref;
@ -141,40 +137,6 @@ declare_clippy_lint! {
"transmutes from a pointer to a reference type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from an integer to a `char`.
///
/// ### Why is this bad?
/// Not every integer is a Unicode scalar value.
///
/// ### Known problems
/// - [`from_u32`] which this lint suggests using is slower than `transmute`
/// as it needs to validate the input.
/// If you are certain that the input is always a valid Unicode scalar value,
/// use [`from_u32_unchecked`] which is as fast as `transmute`
/// but has a semantically meaningful name.
/// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`.
///
/// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html
/// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html
///
/// ### Example
/// ```no_run
/// let x = 1_u32;
/// unsafe {
/// let _: char = std::mem::transmute(x); // where x: u32
/// }
///
/// // should be:
/// let _ = std::char::from_u32(x).unwrap();
/// ```
#[clippy::version = "pre 1.29.0"]
pub TRANSMUTE_INT_TO_CHAR,
complexity,
"transmutes from an integer to a `char`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a `&[u8]` to a `&str`.
@ -232,29 +194,6 @@ declare_clippy_lint! {
"transmutes from an integer to a `bool`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from an integer to a float.
///
/// ### Why is this bad?
/// Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive
/// and safe.
///
/// ### Example
/// ```no_run
/// unsafe {
/// let _: f32 = std::mem::transmute(1_u32); // where x: u32
/// }
///
/// // should be:
/// let _: f32 = f32::from_bits(1_u32);
/// ```
#[clippy::version = "pre 1.29.0"]
pub TRANSMUTE_INT_TO_FLOAT,
complexity,
"transmutes from an integer to a float"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from `T` to `NonZero<T>`, and suggests the `new_unchecked`
@ -280,52 +219,6 @@ declare_clippy_lint! {
"transmutes from an integer to a non-zero wrapper"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a float to an integer.
///
/// ### Why is this bad?
/// Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive
/// and safe.
///
/// ### Example
/// ```no_run
/// unsafe {
/// let _: u32 = std::mem::transmute(1f32);
/// }
///
/// // should be:
/// let _: u32 = 1f32.to_bits();
/// ```
#[clippy::version = "1.41.0"]
pub TRANSMUTE_FLOAT_TO_INT,
complexity,
"transmutes from a float to an integer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a number to an array of `u8`
///
/// ### Why this is bad?
/// Transmutes are dangerous and error-prone, whereas `to_ne_bytes`
/// is intuitive and safe.
///
/// ### Example
/// ```no_run
/// unsafe {
/// let x: [u8; 8] = std::mem::transmute(1i64);
/// }
///
/// // should be
/// let x: [u8; 8] = 0i64.to_ne_bytes();
/// ```
#[clippy::version = "1.58.0"]
pub TRANSMUTE_NUM_TO_BYTES,
complexity,
"transmutes from a number to an array of `u8`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a pointer to a pointer, or
@ -581,13 +474,9 @@ impl_lint_pass!(Transmute => [
TRANSMUTE_PTR_TO_PTR,
USELESS_TRANSMUTE,
WRONG_TRANSMUTE,
TRANSMUTE_INT_TO_CHAR,
TRANSMUTE_BYTES_TO_STR,
TRANSMUTE_INT_TO_BOOL,
TRANSMUTE_INT_TO_FLOAT,
TRANSMUTE_INT_TO_NON_ZERO,
TRANSMUTE_FLOAT_TO_INT,
TRANSMUTE_NUM_TO_BYTES,
UNSOUND_COLLECTION_TRANSMUTE,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
TRANSMUTE_UNDEFINED_REPR,
@ -632,14 +521,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
| transmute_null_to_fn::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv)
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
| transmute_int_to_non_zero::check(cx, e, from_ty, to_ty, arg)
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context, self.msrv)
| (unsound_collection_transmute::check(cx, e, from_ty, to_ty)
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty))
| (eager_transmute::check(cx, e, arg, from_ty, to_ty));

View file

@ -1,66 +0,0 @@
use super::TRANSMUTE_FLOAT_TO_INT;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg;
use rustc_ast as ast;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
/// Checks for `transmute_float_to_int` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
mut arg: &'tcx Expr<'_>,
const_context: bool,
msrv: Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Float(float_ty), ty::Int(_) | ty::Uint(_))
if !const_context || msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV) =>
{
span_lint_and_then(
cx,
TRANSMUTE_FLOAT_TO_INT,
e.span,
format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|diag| {
let mut sugg = sugg::Sugg::hir(cx, arg, "..");
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind {
arg = inner_expr;
}
if let ExprKind::Lit(lit) = &arg.kind
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
&& let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node
{
let op = format!("{sugg}{}", float_ty.name_str()).into();
match sugg {
sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op),
_ => sugg = sugg::Sugg::NonParen(op),
}
}
sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_paren()).into());
// cast the result of `to_bits` if `to_ty` is signed
sugg = if let ty::Int(int_ty) = to_ty.kind() {
sugg.as_ty(int_ty.name_str().to_string())
} else {
sugg
};
diag.span_suggestion(e.span, "consider using", sugg, Applicability::Unspecified);
},
);
true
},
_ => false,
}
}

View file

@ -1,47 +0,0 @@
use super::TRANSMUTE_INT_TO_CHAR;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{std_or_core, sugg};
use rustc_ast as ast;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
/// Checks for `transmute_int_to_char` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) if !const_context => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_CHAR,
e.span,
format!("transmute from a `{from_ty}` to a `char`"),
|diag| {
let Some(top_crate) = std_or_core(cx) else { return };
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(_) = from_ty.kind() {
arg.as_ty(ast::UintTy::U32.name_str())
} else {
arg
};
diag.span_suggestion(
e.span,
"consider using",
format!("{top_crate}::char::from_u32({arg}).unwrap()"),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View file

@ -1,50 +0,0 @@
use super::TRANSMUTE_INT_TO_FLOAT;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
/// Checks for `transmute_int_to_float` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
msrv: Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context || msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV) => {
span_lint_and_then(
cx,
TRANSMUTE_INT_TO_FLOAT,
e.span,
format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
let arg = if let ty::Int(int_ty) = from_ty.kind() {
arg.as_ty(format!(
"u{}",
int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string())
))
} else {
arg
};
diag.span_suggestion(
e.span,
"consider using",
format!("{to_ty}::from_bits({arg})"),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View file

@ -1,50 +0,0 @@
use super::TRANSMUTE_NUM_TO_BYTES;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, UintTy};
/// Checks for `transmute_int_to_float` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
const_context: bool,
msrv: Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(_) | ty::Uint(_) | ty::Float(_), ty::Array(arr_ty, _)) => {
if !matches!(arr_ty.kind(), ty::Uint(UintTy::U8)) {
return false;
}
if matches!(from_ty.kind(), ty::Float(_)) && const_context && !msrv.meets(cx, msrvs::CONST_FLOAT_BITS_CONV)
{
return false;
}
span_lint_and_then(
cx,
TRANSMUTE_NUM_TO_BYTES,
e.span,
format!("transmute from a `{from_ty}` to a `{to_ty}`"),
|diag| {
let arg = sugg::Sugg::hir(cx, arg, "..");
diag.span_suggestion(
e.span,
"consider using `to_ne_bytes()`",
format!("{arg}.to_ne_bytes()"),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View file

@ -385,7 +385,7 @@ declare_clippy_lint! {
/// ```no_run
/// let right: std::borrow::Cow<'_, [u8]>;
/// ```
#[clippy::version = "1.85.0"]
#[clippy::version = "1.87.0"]
pub OWNED_COW,
style,
"needlessly owned Cow type"

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
use clippy_utils::sym;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@ -7,11 +8,12 @@ use super::UNIT_CMP;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
let macro_name = cx.tcx.item_name(macro_call.def_id);
let result = match macro_name.as_str() {
"assert_eq" | "debug_assert_eq" => "succeed",
"assert_ne" | "debug_assert_ne" => "fail",
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
{
let result = match diag_name {
sym::assert_eq_macro | sym::debug_assert_eq_macro => "succeed",
sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail",
_ => return,
};
let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else {
@ -24,7 +26,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
cx,
UNIT_CMP,
macro_call.span,
format!("`{macro_name}` of unit values detected. This will always {result}"),
format!(
"`{}` of unit values detected. This will always {result}",
cx.tcx.item_name(macro_call.def_id)
),
);
}
return;

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::is_def_id_trait_method;
use rustc_hir::def::DefKind;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Node, YieldSource};
use rustc_hir::{Body, Defaultness, Expr, ExprKind, FnDecl, HirId, Node, TraitItem, YieldSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_session::impl_lint_pass;
@ -116,7 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
span: Span,
def_id: LocalDefId,
) {
if !span.from_expansion() && fn_kind.asyncness().is_async() && !is_def_id_trait_method(cx, def_id) {
if !span.from_expansion()
&& fn_kind.asyncness().is_async()
&& !is_def_id_trait_method(cx, def_id)
&& !is_default_trait_impl(cx, def_id)
{
let mut visitor = AsyncFnVisitor {
cx,
found_await: false,
@ -189,3 +193,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
}
}
}
fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
matches!(
cx.tcx.hir_node_by_def_id(def_id),
Node::TraitItem(TraitItem {
defaultness: Defaultness::Default { .. },
..
})
)
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks};
use clippy_utils::{is_res_lang_ctor, paths, peel_blocks};
use hir::{ExprKind, HirId, PatKind};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -93,14 +93,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
return;
}
let async_paths: [&[&str]; 4] = [
let async_paths = [
&paths::TOKIO_IO_ASYNCREADEXT,
&paths::TOKIO_IO_ASYNCWRITEEXT,
&paths::FUTURES_IO_ASYNCREADEXT,
&paths::FUTURES_IO_ASYNCWRITEEXT,
];
if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) {
if async_paths.into_iter().any(|path| path.matches(cx, trait_id)) {
return;
}
}
@ -226,7 +226,7 @@ fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
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")
cx.tcx.is_diagnostic_item(sym::unreachable_macro, macro_call.def_id)
}
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
@ -291,19 +291,28 @@ fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
},
};
match (
is_trait_method(cx, call, sym::IoRead),
is_trait_method(cx, call, sym::IoWrite),
match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
|| match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT),
match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
|| match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT),
) {
(true, _, _, _) => Some(IoOp::SyncRead(vectorized)),
(_, true, _, _) => Some(IoOp::SyncWrite(vectorized)),
(_, _, true, _) => Some(IoOp::AsyncRead(vectorized)),
(_, _, _, true) => Some(IoOp::AsyncWrite(vectorized)),
_ => None,
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(call.hir_id)
&& let Some(trait_def_id) = cx.tcx.trait_of_item(method_def_id)
{
if let Some(diag_name) = cx.tcx.get_diagnostic_name(trait_def_id) {
match diag_name {
sym::IoRead => Some(IoOp::SyncRead(vectorized)),
sym::IoWrite => Some(IoOp::SyncWrite(vectorized)),
_ => None,
}
} else if paths::FUTURES_IO_ASYNCREADEXT.matches(cx, trait_def_id)
|| paths::TOKIO_IO_ASYNCREADEXT.matches(cx, trait_def_id)
{
Some(IoOp::AsyncRead(vectorized))
} else if paths::TOKIO_IO_ASYNCWRITEEXT.matches(cx, trait_def_id)
|| paths::FUTURES_IO_ASYNCWRITEEXT.matches(cx, trait_def_id)
{
Some(IoOp::AsyncWrite(vectorized))
} else {
None
}
} else {
None
}
}

View file

@ -4,15 +4,15 @@ use clippy_utils::usage::is_potentially_local_place;
use clippy_utils::{higher, path_to_local, sym};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, PathSegment, UnOp};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceWithHirId};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::mir::FakeReadCause;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@ -111,7 +111,7 @@ struct UnwrapInfo<'tcx> {
/// The check, like `x.is_ok()`
check: &'tcx Expr<'tcx>,
/// The check's name, like `is_ok`
check_name: &'tcx PathSegment<'tcx>,
check_name: Symbol,
/// The branch where the check takes place, like `if x.is_ok() { .. }`
branch: &'tcx Expr<'tcx>,
/// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
@ -133,12 +133,12 @@ fn collect_unwrap_info<'tcx>(
invert: bool,
is_entire_condition: bool,
) -> Vec<UnwrapInfo<'tcx>> {
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
is_type_diagnostic_item(cx, ty, sym::Option) && ["is_some", "is_none"].contains(&method_name)
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
is_type_diagnostic_item(cx, ty, sym::Option) && matches!(method_name, sym::is_none | sym::is_some)
}
fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
is_type_diagnostic_item(cx, ty, sym::Result) && ["is_ok", "is_err"].contains(&method_name)
fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok)
}
if let ExprKind::Binary(op, left, right) = &expr.kind {
@ -155,14 +155,10 @@ fn collect_unwrap_info<'tcx>(
} else if let ExprKind::MethodCall(method_name, receiver, [], _) = &expr.kind
&& let Some(local_id) = path_to_local(receiver)
&& let ty = cx.typeck_results().expr_ty(receiver)
&& let name = method_name.ident.as_str()
&& let name = method_name.ident.name
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name))
{
let unwrappable = match name {
"is_some" | "is_ok" => true,
"is_err" | "is_none" => false,
_ => unreachable!(),
};
let unwrappable = matches!(name, sym::is_some | sym::is_ok);
let safe_to_unwrap = unwrappable != invert;
let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
UnwrappableKind::Option
@ -174,7 +170,7 @@ fn collect_unwrap_info<'tcx>(
local_id,
if_expr,
check: expr,
check_name: method_name,
check_name: name,
branch,
safe_to_unwrap,
kind,
@ -184,12 +180,12 @@ fn collect_unwrap_info<'tcx>(
Vec::new()
}
/// A HIR visitor delegate that checks if a local variable of type `Option<_>` is mutated,
/// *except* for if `Option::as_mut` is called.
/// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated,
/// *except* for if `.as_mut()` is called.
/// The reason for why we allow that one specifically is that `.as_mut()` cannot change
/// the option to `None`, and that is important because this lint relies on the fact that
/// the variant, and that is important because this lint relies on the fact that
/// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if
/// the option is changed to None between `is_some` and `unwrap`.
/// the option is changed to None between `is_some` and `unwrap`, ditto for `Result`.
/// (And also `.as_mut()` is a somewhat common method that is still worth linting on.)
struct MutationVisitor<'tcx> {
is_mutated: bool,
@ -198,13 +194,13 @@ struct MutationVisitor<'tcx> {
}
/// Checks if the parent of the expression pointed at by the given `HirId` is a call to
/// `Option::as_mut`.
/// `.as_mut()`.
///
/// Used by the mutation visitor to specifically allow `.as_mut()` calls.
/// In particular, the `HirId` that the visitor receives is the id of the local expression
/// (i.e. the `x` in `x.as_mut()`), and that is the reason for why we care about its parent
/// expression: that will be where the actual method call is.
fn is_option_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool {
if let Node::Expr(mutating_expr) = tcx.parent_hir_node(expr_id)
&& let ExprKind::MethodCall(path, _, [], _) = mutating_expr.kind
{
@ -218,14 +214,16 @@ impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> {
fn borrow(&mut self, cat: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if let ty::BorrowKind::Mutable = bk
&& is_potentially_local_place(self.local_id, &cat.place)
&& !is_option_as_mut_use(self.tcx, diag_expr_id)
&& !is_as_mut_use(self.tcx, diag_expr_id)
{
self.is_mutated = true;
}
}
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {
self.is_mutated = true;
fn mutate(&mut self, cat: &PlaceWithHirId<'tcx>, _: HirId) {
if is_potentially_local_place(self.local_id, &cat.place) {
self.is_mutated = true;
}
}
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
@ -276,12 +274,10 @@ enum AsRefKind {
/// If it isn't, the expression itself is returned.
fn consume_option_as_ref<'tcx>(expr: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, Option<AsRefKind>) {
if let ExprKind::MethodCall(path, recv, [], _) = expr.kind {
if path.ident.name == sym::as_ref {
(recv, Some(AsRefKind::AsRef))
} else if path.ident.name == sym::as_mut {
(recv, Some(AsRefKind::AsMut))
} else {
(expr, None)
match path.ident.name {
sym::as_ref => (recv, Some(AsRefKind::AsRef)),
sym::as_mut => (recv, Some(AsRefKind::AsMut)),
_ => (expr, None),
}
} else {
(expr, None)
@ -296,6 +292,10 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
if expr.span.in_external_macro(self.cx.tcx.sess.source_map()) {
return;
}
// Skip checking inside closures since they are visited through `Unwrap::check_fn()` already.
if matches!(expr.kind, ExprKind::Closure(_)) {
return;
}
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) {
walk_expr(self, cond);
self.visit_branch(expr, cond, then, false);
@ -332,8 +332,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
expr.span,
format!(
"called `{}` on `{unwrappable_variable_name}` after checking its variant with `{}`",
method_name.ident.name,
unwrappable.check_name.ident.as_str(),
method_name.ident.name, unwrappable.check_name,
),
|diag| {
if is_entire_condition {

View file

@ -1,16 +1,18 @@
use clippy_utils::{get_attr, higher};
use clippy_utils::{MaybePath, get_attr, higher, path_def_id};
use itertools::Itertools;
use rustc_ast::LitIntType;
use rustc_ast::ast::{LitFloatType, LitKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_hir::{
self as hir, BindingMode, CaptureBy, Closure, ClosureKind, ConstArg, ConstArgKind, CoroutineKind, ExprKind,
FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr, TyKind,
FnRetTy, HirId, Lit, PatExprKind, PatKind, QPath, StmtKind, StructTailExpr,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::symbol::{Ident, Symbol};
use std::cell::Cell;
use std::fmt::{Display, Formatter, Write as _};
use std::fmt::{Display, Formatter};
declare_lint_pass!(
/// ### What it does
@ -148,6 +150,15 @@ fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_,
}
}
fn paths_static_name(cx: &LateContext<'_>, id: DefId) -> String {
cx.get_def_path(id)
.iter()
.map(Symbol::as_str)
.filter(|s| !s.starts_with('<'))
.join("_")
.to_uppercase()
}
struct Binding<T> {
name: String,
value: T,
@ -257,11 +268,44 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
chain!(self, "{symbol}.as_str() == {:?}", symbol.value.as_str());
}
fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
fn qpath<'p>(&self, qpath: &Binding<&QPath<'_>>, has_hir_id: &Binding<&impl MaybePath<'p>>) {
if let QPath::LangItem(lang_item, ..) = *qpath.value {
chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
} else if let Ok(path) = path_to_string(qpath.value) {
chain!(self, "match_qpath({qpath}, &[{}])", path);
} else if let Some(def_id) = self.cx.qpath_res(qpath.value, has_hir_id.value.hir_id()).opt_def_id()
&& !def_id.is_local()
{
bind!(self, def_id);
chain!(
self,
"let Some({def_id}) = cx.qpath_res({qpath}, {has_hir_id}.hir_id).opt_def_id()"
);
if let Some(name) = self.cx.tcx.get_diagnostic_name(def_id.value) {
chain!(self, "cx.tcx.is_diagnostic_item(sym::{name}, {def_id})");
} else {
chain!(
self,
"paths::{}.matches(cx, {def_id}) // Add the path to `clippy_utils::paths` if needed",
paths_static_name(self.cx, def_id.value)
);
}
}
}
fn maybe_path<'p>(&self, path: &Binding<&impl MaybePath<'p>>) {
if let Some(id) = path_def_id(self.cx, path.value)
&& !id.is_local()
{
if let Some(lang) = self.cx.tcx.lang_items().from_def_id(id) {
chain!(self, "is_path_lang_item(cx, {path}, LangItem::{}", lang.name());
} else if let Some(name) = self.cx.tcx.get_diagnostic_name(id) {
chain!(self, "is_path_diagnostic_item(cx, {path}, sym::{name})");
} else {
chain!(
self,
"paths::{}.matches_path(cx, {path}) // Add the path to `clippy_utils::paths` if needed",
paths_static_name(self.cx, id)
);
}
}
}
@ -270,7 +314,6 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ConstArgKind::Path(ref qpath) => {
bind!(self, qpath);
chain!(self, "let ConstArgKind::Path(ref {qpath}) = {const_arg}.kind");
self.qpath(qpath);
},
ConstArgKind::Anon(anon_const) => {
bind!(self, anon_const);
@ -394,12 +437,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
bind!(self, let_expr);
kind!("Let({let_expr})");
self.pat(field!(let_expr.pat));
// Does what ExprKind::Cast does, only adds a clause for the type
// if it's a path
if let Some(TyKind::Path(qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) {
bind!(self, qpath);
chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
self.qpath(qpath);
if let Some(ty) = let_expr.value.ty {
bind!(self, ty);
chain!(self, "let Some({ty}) = {let_expr}.ty");
self.maybe_path(ty);
}
self.expr(field!(let_expr.init));
},
@ -451,11 +492,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ExprKind::Cast(expr, cast_ty) => {
bind!(self, expr, cast_ty);
kind!("Cast({expr}, {cast_ty})");
if let TyKind::Path(ref qpath) = cast_ty.value.kind {
bind!(self, qpath);
chain!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
self.qpath(qpath);
}
self.maybe_path(cast_ty);
self.expr(expr);
},
ExprKind::Type(expr, _ty) => {
@ -561,10 +598,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
self.expr(object);
self.expr(index);
},
ExprKind::Path(ref qpath) => {
bind!(self, qpath);
kind!("Path(ref {qpath})");
self.qpath(qpath);
ExprKind::Path(_) => {
self.maybe_path(expr);
},
ExprKind::AddrOf(kind, mutability, inner) => {
bind!(self, inner);
@ -608,7 +643,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
StructTailExpr::None | StructTailExpr::DefaultFields(_) => None,
});
kind!("Struct({qpath}, {fields}, {base})");
self.qpath(qpath);
self.qpath(qpath, expr);
self.slice(fields, |field| {
self.ident(field!(field.ident));
self.expr(field!(field.expr));
@ -648,7 +683,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
self.expr(expr);
}
fn pat_expr(&self, lit: &Binding<&hir::PatExpr<'_>>) {
fn pat_expr(&self, lit: &Binding<&hir::PatExpr<'_>>, pat: &Binding<&hir::Pat<'_>>) {
let kind = |kind| chain!(self, "let PatExprKind::{kind} = {lit}.kind");
macro_rules! kind {
($($t:tt)*) => (kind(format_args!($($t)*)));
@ -657,15 +692,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatExprKind::Lit { lit, negated } => {
bind!(self, lit);
bind!(self, negated);
kind!("Lit{{ref {lit}, {negated} }}");
kind!("Lit {{ ref {lit}, {negated} }}");
self.lit(lit);
},
PatExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
PatExprKind::Path(ref qpath) => {
bind!(self, qpath);
kind!("Path(ref {qpath})");
self.qpath(qpath);
},
PatExprKind::Path(_) => self.maybe_path(pat),
}
}
@ -697,7 +728,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatKind::Struct(ref qpath, fields, ignore) => {
bind!(self, qpath, fields);
kind!("Struct(ref {qpath}, {fields}, {ignore})");
self.qpath(qpath);
self.qpath(qpath, pat);
self.slice(fields, |field| {
self.ident(field!(field.ident));
self.pat(field!(field.pat));
@ -711,7 +742,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
bind!(self, qpath, fields);
kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
self.qpath(qpath);
self.qpath(qpath, pat);
self.slice(fields, |pat| self.pat(pat));
},
PatKind::Tuple(fields, skip_pos) => {
@ -743,13 +774,13 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
PatKind::Expr(lit_expr) => {
bind!(self, lit_expr);
kind!("Expr({lit_expr})");
self.pat_expr(lit_expr);
self.pat_expr(lit_expr, pat);
},
PatKind::Range(start, end, end_kind) => {
opt_bind!(self, start, end);
kind!("Range({start}, {end}, RangeEnd::{end_kind:?})");
start.if_some(|e| self.pat_expr(e));
end.if_some(|e| self.pat_expr(e));
start.if_some(|e| self.pat_expr(e, pat));
end.if_some(|e| self.pat_expr(e, pat));
},
PatKind::Slice(start, middle, end) => {
bind!(self, start, end);
@ -797,32 +828,3 @@ fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
let attrs = cx.tcx.hir_attrs(hir_id);
get_attr(cx.sess(), attrs, "author").count() > 0
}
fn path_to_string(path: &QPath<'_>) -> Result<String, ()> {
fn inner(s: &mut String, path: &QPath<'_>) -> Result<(), ()> {
match *path {
QPath::Resolved(_, path) => {
for (i, segment) in path.segments.iter().enumerate() {
if i > 0 {
*s += ", ";
}
write!(s, "{:?}", segment.ident.as_str()).unwrap();
}
},
QPath::TypeRelative(ty, segment) => match &ty.kind {
TyKind::Path(inner_path) => {
inner(s, inner_path)?;
*s += ", ";
write!(s, "{:?}", segment.ident.as_str()).unwrap();
},
other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
},
QPath::LangItem(..) => return Err(()),
}
Ok(())
}
let mut s = String::new();
inner(&mut s, path)?;
Ok(s)
}

View file

@ -498,9 +498,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
None => return,
},
LitKind::Char => (
match lit.symbol.as_str() {
"\"" => "\\\"",
"\\'" => "'",
match lit.symbol {
sym::DOUBLE_QUOTE => "\\\"",
sym::BACKSLASH_SINGLE_QUOTE => "'",
_ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
Some(stripped) => stripped,
None => return,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::{is_normalizable, is_type_diagnostic_item, ty_from_hir_ty};
use clippy_utils::ty::{is_type_diagnostic_item, ty_from_hir_ty};
use rustc_hir::{self as hir, AmbigArg, HirId, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf as _;
@ -54,8 +54,6 @@ impl LateLintPass<'_> for ZeroSizedMapValues {
// Fixes https://github.com/rust-lang/rust-clippy/issues/7447 because of
// https://github.com/rust-lang/rust/blob/master/compiler/rustc_middle/src/ty/sty.rs#L968
&& !ty.has_escaping_bound_vars()
// Do this to prevent `layout_of` crashing, being unable to fully normalize `ty`.
&& is_normalizable(cx, cx.param_env, ty)
&& let Ok(layout) = cx.layout_of(ty)
&& layout.is_zst()
{