Merge commit '0621446356' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-04-22 16:10:59 +02:00
parent ed892e72dd
commit ff428d91c2
746 changed files with 13925 additions and 7188 deletions

View file

@ -1,7 +1,7 @@
[package]
name = "clippy_lints"
# begin autogenerated version
version = "0.1.87"
version = "0.1.88"
# end autogenerated version
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
@ -19,10 +19,7 @@ itertools = "0.12"
quine-mc_cluskey = "0.2"
regex-syntax = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
tempfile = { version = "3.3.0", optional = true }
toml = "0.7.3"
regex = { version = "1.5", optional = true }
unicode-normalization = "0.1"
unicode-script = { version = "0.5", default-features = false }
semver = "1.0"
@ -31,10 +28,6 @@ url = "2.2"
[dev-dependencies]
walkdir = "2.3"
[features]
# build clippy with internal lints enabled, off by default
internal = ["serde_json", "tempfile", "regex"]
[package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)]
rustc_private = true

View file

@ -5,6 +5,7 @@ use clippy_config::types::{
SourceItemOrderingWithinModuleItemGroupings,
};
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::is_cfg_test;
use rustc_hir::{
AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, Variant,
VariantData,
@ -263,10 +264,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
continue;
}
if let Some(cur_v) = cur_v {
if cur_v.ident.name.as_str() > variant.ident.name.as_str() && cur_v.span != variant.span {
Self::lint_member_name(cx, &variant.ident, &cur_v.ident);
}
if let Some(cur_v) = cur_v
&& cur_v.ident.name.as_str() > variant.ident.name.as_str()
&& cur_v.span != variant.span
{
Self::lint_member_name(cx, &variant.ident, &cur_v.ident);
}
cur_v = Some(variant);
}
@ -278,10 +280,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
continue;
}
if let Some(cur_f) = cur_f {
if cur_f.ident.name.as_str() > field.ident.name.as_str() && cur_f.span != field.span {
Self::lint_member_name(cx, &field.ident, &cur_f.ident);
}
if let Some(cur_f) = cur_f
&& cur_f.ident.name.as_str() > field.ident.name.as_str()
&& cur_f.span != field.span
{
Self::lint_member_name(cx, &field.ident, &cur_f.ident);
}
cur_f = Some(field);
}
@ -342,7 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
struct CurItem<'a> {
item: &'a Item<'a>,
order: usize,
name: String,
name: Option<String>,
}
let mut cur_t: Option<CurItem<'_>> = None;
@ -359,32 +362,36 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
// as no sorting by source map/line of code has to be applied.
//
for item in items {
if is_cfg_test(cx.tcx, item.hir_id()) {
continue;
}
if item.span.in_external_macro(cx.sess().source_map()) {
continue;
}
let ident = if let Some(ident) = item.kind.ident() {
ident
} else if let ItemKind::Impl(_) = item.kind
&& !get_item_name(item).is_empty()
{
rustc_span::Ident::empty() // FIXME: a bit strange, is there a better way to do it?
} else {
continue;
};
if ident.name.as_str().starts_with('_') {
// Filters out unnamed macro-like impls for various derives,
// e.g. serde::Serialize or num_derive::FromPrimitive.
continue;
}
if ident.name == rustc_span::sym::std && item.span.is_dummy() {
if let ItemKind::ExternCrate(None, _) = item.kind {
// Filters the auto-included Rust standard library.
if let Some(ident) = item.kind.ident() {
if ident.name.as_str().starts_with('_') {
// Filters out unnamed macro-like impls for various derives,
// e.g. serde::Serialize or num_derive::FromPrimitive.
continue;
}
println!("Unknown item: {item:?}");
if ident.name == rustc_span::sym::std && item.span.is_dummy() {
if let ItemKind::ExternCrate(None, _) = item.kind {
// Filters the auto-included Rust standard library.
continue;
}
if cfg!(debug_assertions) {
rustc_middle::bug!("unknown item: {item:?}");
}
}
} else if let ItemKind::Impl(_) = item.kind
&& get_item_name(item).is_some()
{
// keep going below
} else {
continue;
}
let item_kind = convert_module_item_kind(&item.kind);
@ -493,7 +500,7 @@ fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleIte
/// further in the [Rust Reference, Paths Chapter][rust_ref].
///
/// [rust_ref]: https://doc.rust-lang.org/reference/paths.html#crate-1
fn get_item_name(item: &Item<'_>) -> String {
fn get_item_name(item: &Item<'_>) -> Option<String> {
match item.kind {
ItemKind::Impl(im) => {
if let TyKind::Path(path) = im.self_ty.kind {
@ -513,27 +520,19 @@ fn get_item_name(item: &Item<'_>) -> String {
}
segs.push(String::new());
segs.join("!!")
Some(segs.join("!!"))
},
QPath::TypeRelative(_, _path_seg) => {
// This case doesn't exist in the clippy tests codebase.
String::new()
None
},
QPath::LangItem(_, _) => String::new(),
QPath::LangItem(_, _) => None,
}
} else {
// Impls for anything that isn't a named type can be skipped.
String::new()
None
}
},
// FIXME: `Ident::empty` for anonymous items is a bit strange, is there
// a better way to do it?
_ => item
.kind
.ident()
.unwrap_or(rustc_span::Ident::empty())
.name
.as_str()
.to_owned(),
_ => item.kind.ident().map(|name| name.as_str().to_owned()),
}
}

View file

@ -12,17 +12,17 @@ declare_clippy_lint! {
/// regardless of whether good alternatives exist or not. If you want more
/// precise lints for `as`, please consider using these separate lints:
///
/// - clippy::cast_lossless
/// - clippy::cast_possible_truncation
/// - clippy::cast_possible_wrap
/// - clippy::cast_precision_loss
/// - clippy::cast_sign_loss
/// - clippy::char_lit_as_u8
/// - clippy::fn_to_numeric_cast
/// - clippy::fn_to_numeric_cast_with_truncation
/// - clippy::ptr_as_ptr
/// - clippy::unnecessary_cast
/// - invalid_reference_casting
/// - `clippy::cast_lossless`
/// - `clippy::cast_possible_truncation`
/// - `clippy::cast_possible_wrap`
/// - `clippy::cast_precision_loss`
/// - `clippy::cast_sign_loss`
/// - `clippy::char_lit_as_u8`
/// - `clippy::fn_to_numeric_cast`
/// - `clippy::fn_to_numeric_cast_with_truncation`
/// - `clippy::ptr_as_ptr`
/// - `clippy::unnecessary_cast`
/// - `invalid_reference_casting`
///
/// There is a good explanation the reason why this lint should work in this
/// way and how it is useful [in this

View file

@ -243,7 +243,7 @@ fn build_sugg<'tcx>(
// `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
Sugg::hir_with_applicability(cx, lhs, "_", app)
}
.maybe_par();
.maybe_paren();
// Determine whether we need to reference the argument to clone_from().
let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg);
@ -284,7 +284,7 @@ fn build_sugg<'tcx>(
let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
// `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`
// `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_par();
let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_paren();
let inner_type = cx.typeck_results().expr_ty(ref_expr);
// If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it
// deref to a mutable reference.
@ -296,7 +296,7 @@ fn build_sugg<'tcx>(
} else {
// `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`
// `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_par().mut_addr()
Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_paren().mut_addr()
};
match call_kind {

View file

@ -8,17 +8,18 @@ use rustc_span::{DUMMY_SP, sym};
pub(super) fn check(cx: &EarlyContext<'_>, name: Symbol, items: &[MetaItemInner]) {
for lint in items {
if let Some(lint_name) = extract_clippy_lint(lint) {
if lint_name.as_str() == "restriction" && name != sym::allow {
span_lint_and_help(
cx,
BLANKET_CLIPPY_RESTRICTION_LINTS,
lint.span(),
"`clippy::restriction` is not meant to be enabled as a group",
None,
"enable the restriction lints you need individually",
);
}
if let Some(lint_name) = extract_clippy_lint(lint)
&& lint_name.as_str() == "restriction"
&& name != sym::allow
{
span_lint_and_help(
cx,
BLANKET_CLIPPY_RESTRICTION_LINTS,
lint.span(),
"`clippy::restriction` is not meant to be enabled as a group",
None,
"enable the restriction lints you need individually",
);
}
}
}

View file

@ -6,10 +6,10 @@ use rustc_span::Span;
use semver::Version;
pub(super) fn check(cx: &EarlyContext<'_>, span: Span, lit: &MetaItemLit) {
if let LitKind::Str(is, _) = lit.kind {
if is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok() {
return;
}
if let LitKind::Str(is, _) = lit.kind
&& (is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok())
{
return;
}
span_lint(
cx,

View file

@ -36,10 +36,7 @@ fn check_duplicated_attr(
}
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc
|| name == sym::cfg_attr_trace
|| name == sym::rustc_on_unimplemented
|| name == sym::reason {
if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.

View file

@ -14,8 +14,9 @@ mod useless_attribute;
mod utils;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::msrvs::{self, Msrv, MsrvStack};
use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind};
use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -448,6 +449,31 @@ declare_clippy_lint! {
"duplicated attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for ignored tests without messages.
///
/// ### Why is this bad?
/// The reason for ignoring the test may not be obvious.
///
/// ### Example
/// ```no_run
/// #[test]
/// #[ignore]
/// fn test() {}
/// ```
/// Use instead:
/// ```no_run
/// #[test]
/// #[ignore = "Some good reason"]
/// fn test() {}
/// ```
#[clippy::version = "1.85.0"]
pub IGNORE_WITHOUT_REASON,
pedantic,
"ignored tests without messages"
}
pub struct Attributes {
msrv: Msrv,
}
@ -532,6 +558,7 @@ impl_lint_pass!(PostExpansionEarlyAttributes => [
ALLOW_ATTRIBUTES,
ALLOW_ATTRIBUTES_WITHOUT_REASON,
DEPRECATED_SEMVER,
IGNORE_WITHOUT_REASON,
USELESS_ATTRIBUTE,
BLANKET_CLIPPY_RESTRICTION_LINTS,
SHOULD_PANIC_WITHOUT_EXPECT,
@ -546,28 +573,27 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
if let Some(items) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() {
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes::check(cx, attr);
}
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION)
if let Some(items) = &attr.meta_item_list()
&& let Some(ident) = attr.ident()
{
if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes::check(cx, attr);
}
if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
allow_attributes_without_reason::check(cx, ident.name, items, attr);
}
if is_lint_level(ident.name, attr.id) {
blanket_clippy_restriction_lints::check(cx, ident.name, items);
}
if items.is_empty() || !attr.has_name(sym::deprecated) {
return;
}
for item in items {
if let MetaItemInner::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{
allow_attributes_without_reason::check(cx, ident.name, items, attr);
}
if is_lint_level(ident.name, attr.id) {
blanket_clippy_restriction_lints::check(cx, ident.name, items);
}
if items.is_empty() || !attr.has_name(sym::deprecated) {
return;
}
for item in items {
if let MetaItemInner::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{
deprecated_semver::check(cx, item.span(), lit);
}
deprecated_semver::check(cx, item.span(), lit);
}
}
}
@ -575,6 +601,22 @@ impl EarlyLintPass for PostExpansionEarlyAttributes {
if attr.has_name(sym::should_panic) {
should_panic_without_expect::check(cx, attr);
}
if attr.has_name(sym::ignore)
&& match &attr.kind {
AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }),
AttrKind::DocComment(..) => true,
}
{
span_lint_and_help(
cx,
IGNORE_WITHOUT_REASON,
attr.span,
"`#[ignore]` without reason",
None,
"add a reason with `= \"..\"`",
);
}
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &'_ ast::Item) {

View file

@ -30,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute],
diag.warn(
"unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI",
)
.help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
.help("qualify the desired ABI explicitly via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`")
.span_label(packed_span, "`packed` representation set here");
},
);

View file

@ -14,75 +14,75 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
if attr.span.in_external_macro(cx.sess().source_map()) {
return;
}
if let Some(lint_list) = &attr.meta_item_list() {
if attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id)) {
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
return;
};
if let Some(lint_list) = &attr.meta_item_list()
&& attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id))
{
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else {
return;
};
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"
)
{
return;
}
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"
)
{
return;
}
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"
)
{
return;
}
},
ItemKind::ExternCrate(..) => {
if is_word(lint, sym::unused_imports) && skip_unused_imports {
return;
}
if is_word(lint, sym::unused_extern_crates) {
return;
}
},
_ => {},
}
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"
)
{
return;
}
},
ItemKind::ExternCrate(..) => {
if is_word(lint, sym::unused_imports) && skip_unused_imports {
return;
}
if is_word(lint, sym::unused_extern_crates) {
return;
}
},
_ => {},
}
let line_span = first_line_of_span(cx, attr.span);
}
let line_span = first_line_of_span(cx, attr.span);
if let Some(src) = line_span.get_source_text(cx) {
if src.contains("#[") {
#[expect(clippy::collapsible_span_lint_calls)]
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
diag.span_suggestion(
line_span,
"if you just forgot a `!`, use",
src.replacen("#[", "#![", 1),
Applicability::MaybeIncorrect,
);
});
}
}
if let Some(src) = line_span.get_source_text(cx)
&& src.contains("#[")
{
#[expect(clippy::collapsible_span_lint_calls)]
span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| {
diag.span_suggestion(
line_span,
"if you just forgot a `!`, use",
src.replacen("#[", "#![", 1),
Applicability::MaybeIncorrect,
);
});
}
}
}

View file

@ -179,9 +179,14 @@ pub struct AwaitHolding {
impl AwaitHolding {
pub(crate) fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
def_ids: create_disallowed_map(tcx, &conf.await_holding_invalid_types),
}
let (def_ids, _) = create_disallowed_map(
tcx,
&conf.await_holding_invalid_types,
crate::disallowed_types::def_kind_predicate,
"type",
false,
);
Self { def_ids }
}
}
@ -192,10 +197,9 @@ impl<'tcx> LateLintPass<'tcx> for AwaitHolding {
def_id,
..
}) = expr.kind
&& let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id)
{
if let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id) {
self.check_interior_types(cx, coroutine_layout);
}
self.check_interior_types(cx, coroutine_layout);
}
}
}

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::HasSession;
use clippy_utils::sugg::Sugg;
use clippy_utils::{is_else_clause, is_in_const_context};
use clippy_utils::{higher, is_else_clause, is_in_const_context, span_contains_comment};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
@ -46,18 +47,25 @@ declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]);
impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let ExprKind::If(cond, then, Some(else_)) = expr.kind
&& matches!(cond.kind, ExprKind::DropTemps(_))
if !expr.span.from_expansion()
&& let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let Some(then_lit) = as_int_bool_lit(then)
&& let Some(else_lit) = as_int_bool_lit(else_)
&& let Some(else_lit) = as_int_bool_lit(r#else)
&& then_lit != else_lit
&& !expr.span.from_expansion()
&& !is_in_const_context(cx)
{
let ty = cx.typeck_results().expr_ty(then);
let mut applicability = Applicability::MachineApplicable;
let mut applicability = if span_contains_comment(cx.sess().source_map(), expr.span) {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
let snippet = {
let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
let mut sugg = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability);
if !then_lit {
sugg = !sugg;
}
@ -72,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
s
};
let into_snippet = snippet.clone().maybe_par();
let into_snippet = snippet.clone().maybe_paren();
let as_snippet = snippet.as_ty(ty);
span_lint_and_then(
@ -91,10 +99,11 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf {
}
}
fn as_int_bool_lit(e: &Expr<'_>) -> Option<bool> {
if let ExprKind::Block(b, _) = e.kind
fn as_int_bool_lit(expr: &Expr<'_>) -> Option<bool> {
if let ExprKind::Block(b, _) = expr.kind
&& b.stmts.is_empty()
&& let Some(e) = b.expr
&& !e.span.from_expansion()
&& let ExprKind::Lit(lit) = e.kind
&& let LitKind::Int(x, _) = lit.node
{

View file

@ -13,7 +13,7 @@ use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
use rustc_lint::{LateContext, LateLintPass, Level};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use rustc_span::{Span, SyntaxContext, sym};
declare_clippy_lint! {
/// ### What it does
@ -242,11 +242,11 @@ struct Hir2Qmm<'a, 'tcx, 'v> {
impl<'v> Hir2Qmm<'_, '_, 'v> {
fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
for a in a {
if let ExprKind::Binary(binop, lhs, rhs) = &a.kind {
if binop.node == op {
v = self.extract(op, &[lhs, rhs], v)?;
continue;
}
if let ExprKind::Binary(binop, lhs, rhs) = &a.kind
&& binop.node == op
{
v = self.extract(op, &[lhs, rhs], v)?;
continue;
}
v.push(self.run(a)?);
}
@ -349,9 +349,13 @@ impl SuggestContext<'_, '_, '_> {
if let Some(str) = simplify_not(self.cx, self.msrv, terminal) {
self.output.push_str(&str);
} else {
self.output.push('!');
self.output
.push_str(&Sugg::hir_opt(self.cx, terminal)?.maybe_par().to_string());
let mut app = Applicability::MachineApplicable;
let snip = Sugg::hir_with_context(self.cx, terminal, SyntaxContext::root(), "", &mut app);
// Ignore the case If the expression is inside a macro expansion, or the default snippet is used
if app != Applicability::MachineApplicable {
return None;
}
self.output.push_str(&(!snip).to_string());
}
},
True | False | Not(_) => {
@ -414,12 +418,12 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio
let lhs_snippet = lhs.span.get_source_text(cx)?;
let rhs_snippet = rhs.span.get_source_text(cx)?;
if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) {
if let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) {
// e.g. `(a as u64) < b`. Without the parens the `<` is
// interpreted as a start of generic arguments for `u64`
return Some(format!("({lhs_snippet}){op}{rhs_snippet}"));
}
if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')'))
&& let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node)
{
// e.g. `(a as u64) < b`. Without the parens the `<` is
// interpreted as a start of generic arguments for `u64`
return Some(format!("({lhs_snippet}){op}{rhs_snippet}"));
}
Some(format!("{lhs_snippet}{op}{rhs_snippet}"))

View file

@ -2,7 +2,7 @@ use crate::reference::DEREF_ADDROF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed};
use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed, is_mutable};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -73,6 +73,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
}
})
&& !is_from_proc_macro(cx, e)
&& let e_ty = cx.typeck_results().expr_ty_adjusted(e)
// check if the reference is coercing to a mutable reference
&& (!matches!(e_ty.kind(), ty::Ref(_, _, Mutability::Mut)) || is_mutable(cx, deref_target))
&& let Some(deref_text) = deref_target.span.get_source_text(cx)
{
span_lint_and_then(
@ -90,10 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
// has deref trait -> give 2 help
// doesn't have deref trait -> give 1 help
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() {
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
return;
}
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait()
&& !implements_trait(cx, *inner_ty, deref_trait_id, &[])
{
return;
}
diag.span_suggestion(

View file

@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::{is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core};
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, AutoBorrow};
use rustc_span::BytePos;
use super::BORROW_AS_PTR;
@ -29,10 +30,6 @@ pub(super) fn check<'tcx>(
}
let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) {
let operator_kind = match mutability {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
// Make sure that the span to be replaced doesn't include parentheses, that could break the
// suggestion.
let span = if has_enclosing_paren(snippet_with_applicability(cx, expr.span, "", &mut app)) {
@ -42,7 +39,7 @@ pub(super) fn check<'tcx>(
} else {
expr.span
};
(format!("&raw {operator_kind} {snip}"), span)
(format!("&raw {} {snip}", mutability.ptr_str()), span)
} else {
let Some(std_or_core) = std_or_core(cx) else {
return false;
@ -59,3 +56,25 @@ pub(super) fn check<'tcx>(
}
false
}
/// Check for an implicit cast from reference to raw pointer outside an explicit `as`.
pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) {
if !expr.span.from_expansion()
&& let ExprKind::AddrOf(BorrowKind::Ref, _, pointee) = expr.kind
&& !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..)))
&& let [deref, borrow] = cx.typeck_results().expr_adjustments(expr)
&& matches!(deref.kind, Adjust::Deref(..))
&& let Adjust::Borrow(AutoBorrow::RawPtr(mutability)) = borrow.kind
// Do not suggest taking a raw pointer to a temporary value
&& !is_expr_temporary_value(cx, pointee)
{
span_lint_and_then(cx, BORROW_AS_PTR, expr.span, "implicit borrow as raw pointer", |diag| {
diag.span_suggestion_verbose(
expr.span.until(pointee.span),
"use a raw pointer instead",
format!("&raw {} ", mutability.ptr_str()),
Applicability::MachineApplicable,
);
});
}
}

View file

@ -36,7 +36,7 @@ pub(super) fn check(
span,
format!("casting the result of `{cast_from}::abs()` to {cast_to}"),
"replace with",
format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}

View file

@ -42,7 +42,7 @@ pub(super) fn check(
diag.span_suggestion_verbose(
expr.span,
"use `Into::into` instead",
format!("{}.into()", from_sugg.maybe_par()),
format!("{}.into()", from_sugg.maybe_paren()),
applicability,
);
},

View file

@ -64,11 +64,11 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX))
},
ExprKind::MethodCall(method, _, [lo, hi], _) => {
if method.ident.as_str() == "clamp" {
if method.ident.as_str() == "clamp"
//FIXME: make this a diagnostic item
if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
return lo_bits.max(hi_bits);
}
&& let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi))
{
return lo_bits.max(hi_bits);
}
nbits
},
@ -185,7 +185,7 @@ fn offer_suggestion(
) {
let cast_to_snip = snippet(cx, cast_to_span, "..");
let suggestion = if cast_to_snip == "_" {
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par())
format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_paren())
} else {
format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, ".."))
};

View file

@ -19,16 +19,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
cx.typeck_results().expr_ty(expr),
);
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind {
if method_path.ident.name.as_str() == "cast"
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
&& !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty())
{
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
} else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind
&& method_path.ident.name.as_str() == "cast"
&& let Some(generic_args) = method_path.args
&& let [GenericArg::Type(cast_to)] = generic_args.args
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
&& !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty())
{
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
}

View file

@ -21,42 +21,41 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv)
start_ty,
end_ty,
}) = expr_cast_chain_tys(cx, expr)
&& let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty))
{
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
format!(
"casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
start_ty.ty, end_ty.ty,
),
|diag| {
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
format!(
"casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
start_ty.ty, end_ty.ty,
),
|diag| {
let ptr_snippet = source::snippet(cx, left_cast.span, "..");
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
// T`, extract just the `T`
end_ty.ty
);
let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
// get just the ty from the TypeAndMut so that the printed type isn't something like `mut
// T`, extract just the `T`
end_ty.ty
);
diag.span_suggestion(
expr.span,
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
diag.span_suggestion(
expr.span,
format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
}
}

View file

@ -0,0 +1,82 @@
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 rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::MANUAL_DANGLING_PTR;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
if let TyKind::Ptr(ref ptr_ty) = to.kind {
let init_expr = expr_or_init(cx, from);
if is_expr_const_aligned(cx, init_expr, ptr_ty.ty)
&& let Some(std_or_core) = std_or_core(cx)
{
let sugg_fn = match ptr_ty.mutbl {
Mutability::Not => "ptr::dangling",
Mutability::Mut => "ptr::dangling_mut",
};
let sugg = if let TyKind::Infer(()) = ptr_ty.ty.kind {
format!("{std_or_core}::{sugg_fn}()")
} else if let Some(mut_ty_snip) = ptr_ty.ty.span.get_source_text(cx) {
format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()")
} else {
return;
};
span_lint_and_sugg(
cx,
MANUAL_DANGLING_PTR,
expr.span,
"manual creation of a dangling pointer",
"use",
sugg,
Applicability::MachineApplicable,
);
}
}
}
// Checks if the given expression is a call to `align_of` whose generic argument matches the target
// type, or a positive constant literal that matches the target type's alignment.
fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> bool {
match expr.kind {
ExprKind::Call(fun, _) => is_align_of_call(cx, fun, to),
ExprKind::Lit(lit) => is_literal_aligned(cx, lit, to),
_ => false,
}
}
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)
&& let Some(args) = path.segments.last().and_then(|seg| seg.args)
&& let [GenericArg::Type(generic_ty)] = args.args
{
let typeck = cx.typeck_results();
return typeck.node_type(generic_ty.hir_id) == typeck.node_type(to.hir_id);
}
false
}
fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>) -> bool {
let LitKind::Int(val, _) = lit.node else { return false };
if val == 0 {
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
})
}

View file

@ -17,6 +17,7 @@ mod char_lit_as_u8;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod manual_dangling_ptr;
mod ptr_as_ptr;
mod ptr_cast_constness;
mod ref_as_ptr;
@ -71,7 +72,7 @@ declare_clippy_lint! {
/// ### Example
/// ```no_run
/// let y: i8 = -1;
/// y as u128; // will return 18446744073709551615
/// y as u64; // will return 18446744073709551615
/// ```
#[clippy::version = "pre 1.29.0"]
pub CAST_SIGN_LOSS,
@ -759,6 +760,32 @@ declare_clippy_lint! {
"detects `as *mut _` and `as *const _` conversion"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of small constant literals or `mem::align_of` results to raw pointers.
///
/// ### Why is this bad?
/// This creates a dangling pointer and is better expressed as
/// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}.
///
/// ### Example
/// ```no_run
/// let ptr = 4 as *const u32;
/// let aligned = std::mem::align_of::<u32>() as *const u32;
/// let mut_ptr: *mut i64 = 8 as *mut _;
/// ```
/// Use instead:
/// ```no_run
/// let ptr = std::ptr::dangling::<u32>();
/// let aligned = std::ptr::dangling::<u32>();
/// let mut_ptr: *mut i64 = std::ptr::dangling_mut();
/// ```
#[clippy::version = "1.87.0"]
pub MANUAL_DANGLING_PTR,
style,
"casting small constant literals to pointers to create dangling pointers"
}
pub struct Casts {
msrv: Msrv,
}
@ -795,6 +822,7 @@ impl_lint_pass!(Casts => [
ZERO_PTR,
REF_AS_PTR,
AS_POINTER_UNDERSCORE,
MANUAL_DANGLING_PTR,
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
@ -823,6 +851,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
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);
if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) {
manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
}
if cast_to.is_numeric() {
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
if cast_from.is_numeric() {
@ -846,6 +878,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
}
}
if self.msrv.meets(cx, msrvs::RAW_REF_OP) {
borrow_as_ptr::check_implicit_cast(cx, expr);
}
cast_ptr_alignment::check(cx, expr);
char_lit_as_u8::check(cx, expr);
ptr_as_ptr::check(cx, expr, self.msrv);

View file

@ -81,7 +81,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) {
(
"try `pointer::cast`, a safer alternative",
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()),
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_paren()),
)
};

View file

@ -53,7 +53,8 @@ pub(super) fn check<'tcx>(
}
if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) {
let sugg = Sugg::hir(cx, cast_expr, "_");
let mut app = Applicability::MachineApplicable;
let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app);
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
@ -65,8 +66,8 @@ pub(super) fn check<'tcx>(
expr.span,
"`as` casting between raw pointers while changing only its constness",
format!("try `pointer::cast_{constness}`, a safer alternative"),
format!("{}.cast_{constness}()", sugg.maybe_par()),
Applicability::MachineApplicable,
format!("{}.cast_{constness}()", sugg.maybe_paren()),
app,
);
}
}

View file

@ -130,11 +130,11 @@ pub(super) fn check<'tcx>(
| LitKind::Float(_, LitFloatType::Suffixed(_))
if cast_from.kind() == cast_to.kind() =>
{
if let Some(src) = cast_expr.span.get_source_text(cx) {
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) {
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
}
if let Some(src) = cast_expr.span.get_source_text(cx)
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
{
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
}
},
_ => {},

View file

@ -253,11 +253,11 @@ fn get_types_from_cast<'a>(
match limit.kind {
// `from_type::from(_)`
ExprKind::Call(path, _) => {
if let ExprKind::Path(ref path) = path.kind {
if let ExprKind::Path(ref path) = path.kind
// `to_type`
if let Some(to_type) = get_implementing_type(path, types, func) {
return Some((from_type, to_type));
}
&& let Some(to_type) = get_implementing_type(path, types, func)
{
return Some((from_type, to_type));
}
},
// `to_type::MAX`

View file

@ -62,6 +62,7 @@ impl CognitiveComplexity {
let mut cc = 1u64;
let mut returns = 0u64;
let mut prev_expr: Option<&ExprKind<'tcx>> = None;
let _: Option<!> = for_each_expr_without_closures(expr, |e| {
match e.kind {
ExprKind::If(_, _, _) => {
@ -73,9 +74,14 @@ impl CognitiveComplexity {
}
cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
},
ExprKind::Ret(_) => returns += 1,
ExprKind::Ret(_) => {
if !matches!(prev_expr, Some(ExprKind::Ret(_))) {
returns += 1;
}
},
_ => {},
}
prev_expr = Some(&e.kind);
ControlFlow::Continue(())
});

View file

@ -1,10 +1,12 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability};
use clippy_utils::sugg::Sugg;
use rustc_ast::ast;
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability};
use rustc_ast::BinOpKind;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
@ -75,105 +77,152 @@ declare_clippy_lint! {
"nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
}
declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
pub struct CollapsibleIf {
let_chains_enabled: bool,
lint_commented_code: bool,
}
impl EarlyLintPass for CollapsibleIf {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let ast::ExprKind::If(cond, then, else_) = &expr.kind
impl CollapsibleIf {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
let_chains_enabled: tcx.features().let_chains(),
lint_commented_code: conf.lint_commented_code,
}
}
fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) {
if !block_starts_with_comment(cx, else_block)
&& let Some(else_) = expr_block(else_block)
&& cx.tcx.hir_attrs(else_.hir_id).is_empty()
&& !else_.span.from_expansion()
&& let ExprKind::If(..) = else_.kind
{
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let up_to_else = then_span.between(else_block.span);
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
!c.is_whitespace()
} else {
false
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
COLLAPSIBLE_ELSE_IF,
else_block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(else_block.span), &mut applicability)
),
applicability,
);
}
}
fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) {
if let Some(inner) = expr_block(then)
&& cx.tcx.hir_attrs(inner.hir_id).is_empty()
&& let ExprKind::If(check_inner, _, None) = &inner.kind
&& self.eligible_condition(check_inner)
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
&& (self.lint_commented_code || !block_starts_with_comment(cx, then))
{
span_lint_and_then(
cx,
COLLAPSIBLE_IF,
expr.span,
"this `if` statement can be collapsed",
|diag| {
let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span();
let then_closing_bracket = {
let end = then.span.shrink_to_hi();
end.with_lo(end.lo() - rustc_span::BytePos(1))
.with_leading_whitespace(cx)
.into_span()
};
let inner_if = inner.span.split_at(2).0;
let mut sugg = vec![
// Remove the outer then block `{`
(then_open_bracket, String::new()),
// Remove the outer then block '}'
(then_closing_bracket, String::new()),
// Replace inner `if` by `&&`
(inner_if, String::from("&&")),
];
sugg.extend(parens_around(check));
sugg.extend(parens_around(check_inner));
diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable);
},
);
}
}
pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool {
self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..))
}
}
impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
impl LateLintPass<'_> for CollapsibleIf {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::If(cond, then, else_) = &expr.kind
&& !expr.span.from_expansion()
{
if let Some(else_) = else_ {
check_collapsible_maybe_if_let(cx, then.span, else_);
} else if !matches!(cond.kind, ast::ExprKind::Let(..)) {
check_collapsible_no_if_let(cx, expr, cond, then);
if let Some(else_) = else_
&& let ExprKind::Block(else_, None) = else_.kind
{
Self::check_collapsible_else_if(cx, then.span, else_);
} else if else_.is_none()
&& self.eligible_condition(cond)
&& let ExprKind::Block(then, None) = then.kind
{
self.check_collapsible_if_if(cx, expr, cond, then);
}
}
}
}
fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
// We trim all opening braces and whitespaces and then check if the next string is a comment.
let trimmed_block_text = snippet_block(cx, expr.span, "..", None)
let trimmed_block_text = snippet_block(cx, block.span, "..", None)
.trim_start_matches(|c: char| c.is_whitespace() || c == '{')
.to_owned();
trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
}
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
if let ast::ExprKind::Block(ref block, _) = else_.kind
&& !block_starts_with_comment(cx, block)
&& let Some(else_) = expr_block(block)
&& else_.attrs.is_empty()
&& !else_.span.from_expansion()
&& let ast::ExprKind::If(..) = else_.kind
{
// Prevent "elseif"
// Check that the "else" is followed by whitespace
let up_to_else = then_span.between(block.span);
let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() {
!c.is_whitespace()
} else {
false
};
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
COLLAPSIBLE_ELSE_IF,
block.span,
"this `else { if .. }` block can be collapsed",
"collapse nested if block",
format!(
"{}{}",
if requires_space { " " } else { "" },
snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability)
),
applicability,
);
/// If `block` is a block with either one expression or a statement containing an expression,
/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional.
fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match block.stmts {
[] => block.expr,
[stmt] => {
if let StmtKind::Semi(expr) = stmt.kind {
Some(expr)
} else {
None
}
},
_ => None,
}
}
fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
if !block_starts_with_comment(cx, then)
&& let Some(inner) = expr_block(then)
&& inner.attrs.is_empty()
&& let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind
// Prevent triggering on `if c { if let a = b { .. } }`.
&& !matches!(check_inner.kind, ast::ExprKind::Let(..))
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
/// If the expression is a `||`, suggest parentheses around it.
fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> {
if let ExprKind::Binary(op, _, _) = expr.peel_drop_temps().kind
&& op.node == BinOpKind::Or
{
span_lint_and_then(
cx,
COLLAPSIBLE_IF,
expr.span,
"this `if` statement can be collapsed",
|diag| {
let mut app = Applicability::MachineApplicable;
let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app);
let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app);
diag.span_suggestion(
expr.span,
"collapse nested if block",
format!(
"if {} {}",
lhs.and(&rhs),
snippet_block(cx, content.span, "..", Some(expr.span)),
),
app, // snippet
);
},
);
}
}
/// If the block contains only one expression, return it.
fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
if let [stmt] = &*block.stmts
&& let ast::StmtKind::Expr(expr) | ast::StmtKind::Semi(expr) = &stmt.kind
{
Some(expr)
vec![
(expr.span.shrink_to_lo(), String::from("(")),
(expr.span.shrink_to_hi(), String::from(")")),
]
} else {
None
vec![]
}
}

View file

@ -125,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else {
unreachable!();
};
let lhs = Sugg::hir(cx, lhs, "..").maybe_par();
let lhs = Sugg::hir(cx, lhs, "..").maybe_paren();
let rhs = Sugg::hir(cx, rhs, "..").addr();
span_lint_and_sugg(
cx,

View file

@ -256,7 +256,7 @@ fn lint_branches_sharing_code<'tcx>(
let suggestion = reindent_multiline(&suggestion, true, indent);
let span = span.with_hi(last_block.span.hi());
// Improve formatting if the inner block has indention (i.e. normal Rust formatting)
// Improve formatting if the inner block has indentation (i.e. normal Rust formatting)
let span = span
.map_range(cx, |src, range| {
(range.start > 4 && src.get(range.start - 4..range.start)? == " ")
@ -539,10 +539,10 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
.filter(|stmt| !ignore_span.overlaps(stmt.span))
.try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
if let Some(expr) = block.expr {
if res.is_continue() {
res = intravisit::walk_expr(&mut walker, expr);
}
if let Some(expr) = block.expr
&& res.is_continue()
{
res = intravisit::walk_expr(&mut walker, expr);
}
res.is_break()

View file

@ -165,17 +165,4 @@ macro_rules! declare_clippy_lint {
$(, $eval_always)?
}
};
(
$(#[doc = $lit:literal])*
pub $lint_name:ident,
internal,
$desc:literal
) => {
declare_clippy_lint! {@
$(#[doc = $lit])*
pub $lint_name, Allow, crate::LintCategory::Internal, $desc,
None, "0.0.0"
}
};
}

View file

@ -3,36 +3,6 @@
// Manual edits will be overwritten.
pub static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")]
crate::utils::internal_lints::almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO,
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
crate::approx_const::APPROX_CONSTANT_INFO,
@ -52,6 +22,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
crate::attrs::DEPRECATED_SEMVER_INFO,
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
crate::attrs::IGNORE_WITHOUT_REASON_INFO,
crate::attrs::INLINE_ALWAYS_INFO,
crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO,
crate::attrs::NON_MINIMAL_CFG_INFO,
@ -96,6 +67,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_INFO,
crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO,
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::MANUAL_DANGLING_PTR_INFO,
crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
@ -286,6 +258,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::literal_representation::UNREADABLE_LITERAL_INFO,
crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO,
crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO,
crate::loops::CHAR_INDICES_AS_BYTE_INDICES_INFO,
crate::loops::EMPTY_LOOP_INFO,
crate::loops::EXPLICIT_COUNTER_LOOP_INFO,
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
@ -312,6 +285,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO,
crate::macro_use::MACRO_USE_IMPORTS_INFO,
crate::main_recursion::MAIN_RECURSION_INFO,
crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO,
crate::manual_assert::MANUAL_ASSERT_INFO,
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
crate::manual_bits::MANUAL_BITS_INFO,
@ -334,7 +308,6 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
crate::match_result_ok::MATCH_RESULT_OK_INFO,
@ -344,10 +317,10 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::matches::MANUAL_MAP_INFO,
crate::matches::MANUAL_OK_ERR_INFO,
crate::matches::MANUAL_UNWRAP_OR_INFO,
crate::matches::MANUAL_UNWRAP_OR_DEFAULT_INFO,
crate::matches::MATCH_AS_REF_INFO,
crate::matches::MATCH_BOOL_INFO,
crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO,
crate::matches::MATCH_ON_VEC_ITEMS_INFO,
crate::matches::MATCH_OVERLAPPING_ARM_INFO,
crate::matches::MATCH_REF_PATS_INFO,
crate::matches::MATCH_SAME_ARMS_INFO,
@ -488,6 +461,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
crate::methods::SUSPICIOUS_SPLITN_INFO,
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
crate::methods::SWAP_WITH_TEMPORARY_INFO,
crate::methods::TYPE_ID_ON_BOX_INFO,
crate::methods::UNBUFFERED_BYTES_INFO,
crate::methods::UNINIT_ASSUMED_INIT_INFO,
@ -664,6 +638,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
crate::redundant_test_prefix::REDUNDANT_TEST_PREFIX_INFO,
crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO,
crate::ref_option_ref::REF_OPTION_REF_INFO,
crate::ref_patterns::REF_PATTERNS_INFO,

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_ty_alias;
use clippy_utils::source::SpanRangeExt as _;
use hir::ExprKind;
use hir::def::Res;
use rustc_errors::Applicability;
@ -70,15 +71,26 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs {
&& let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant()
&& !var.is_field_list_non_exhaustive()
&& !expr.span.from_expansion() && !qpath.span().from_expansion()
// do not suggest replacing an expression by a type name with placeholders
&& !base.is_suggestable_infer_ty()
{
span_lint_and_sugg(
let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())];
if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) {
// Remove `<`, '>` has already been removed by the existing removal expression.
removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new()));
}
span_lint_and_then(
cx,
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
expr.span.with_lo(qpath.qself_span().hi()),
expr.span,
"use of `default` to create a unit struct",
"remove this call to `default`",
String::new(),
Applicability::MachineApplicable,
|diag| {
diag.multipart_suggestion(
"remove this call to `default`",
removals,
Applicability::MachineApplicable,
);
},
);
}
}

View file

@ -42,6 +42,8 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[
("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"),
#[clippy::version = "1.86.0"]
("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`
]}

View file

@ -1133,61 +1133,60 @@ fn report<'tcx>(
impl<'tcx> Dereferencing<'tcx> {
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
if let Some(pat) = outer_pat {
// Check for auto-deref
if !matches!(
cx.typeck_results().expr_adjustments(e),
[
Adjustment {
kind: Adjust::Deref(_),
..
},
Adjustment {
kind: Adjust::Deref(_),
..
},
if let Some(outer_pat) = self.ref_locals.get_mut(&local)
&& let Some(pat) = outer_pat
// Check for auto-deref
&& !matches!(
cx.typeck_results().expr_adjustments(e),
[
Adjustment {
kind: Adjust::Deref(_),
..
]
) {
match get_parent_expr(cx, e) {
// Field accesses are the same no matter the number of references.
Some(Expr {
kind: ExprKind::Field(..),
..
}) => (),
Some(&Expr {
span,
kind: ExprKind::Unary(UnOp::Deref, _),
..
}) if !span.from_expansion() => {
// Remove explicit deref.
let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((span, snip.into()));
},
Some(parent) if !parent.span.from_expansion() => {
// Double reference might be needed at this point.
if parent.precedence() == ExprPrecedence::Unambiguous {
// Parentheses would be needed here, don't lint.
*outer_pat = None;
} else {
pat.always_deref = false;
let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((e.span, format!("&{snip}")));
}
},
_ if !e.span.from_expansion() => {
// Double reference might be needed at this point.
pat.always_deref = false;
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
pat.replacements.push((e.span, format!("&{snip}")));
},
// Edge case for macros. The span of the identifier will usually match the context of the
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
// macros
_ => *outer_pat = None,
},
Adjustment {
kind: Adjust::Deref(_),
..
},
..
]
)
{
match get_parent_expr(cx, e) {
// Field accesses are the same no matter the number of references.
Some(Expr {
kind: ExprKind::Field(..),
..
}) => (),
Some(&Expr {
span,
kind: ExprKind::Unary(UnOp::Deref, _),
..
}) if !span.from_expansion() => {
// Remove explicit deref.
let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((span, snip.into()));
},
Some(parent) if !parent.span.from_expansion() => {
// Double reference might be needed at this point.
if parent.precedence() == ExprPrecedence::Unambiguous {
// Parentheses would be needed here, don't lint.
*outer_pat = None;
} else {
pat.always_deref = false;
let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
pat.replacements.push((e.span, format!("&{snip}")));
}
}
},
_ if !e.span.from_expansion() => {
// Double reference might be needed at this point.
pat.always_deref = false;
let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
pat.replacements.push((e.span, format!("&{snip}")));
},
// Edge case for macros. The span of the identifier will usually match the context of the
// binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
// macros
_ => *outer_pat = None,
}
}
}

View file

@ -94,18 +94,18 @@ fn check_struct<'tcx>(
ty_args: GenericArgsRef<'_>,
typeck_results: &'tcx TypeckResults<'tcx>,
) {
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind {
if let Some(PathSegment { args, .. }) = p.segments.last() {
let args = args.map(|a| a.args).unwrap_or(&[]);
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind
&& let Some(PathSegment { args, .. }) = p.segments.last()
{
let args = args.map(|a| a.args).unwrap_or(&[]);
// ty_args contains the generic parameters of the type declaration, while args contains the
// arguments used at instantiation time. If both len are not equal, it means that some
// parameters were not provided (which means that the default values were used); in this
// case we will not risk suggesting too broad a rewrite. We won't either if any argument
// is a type or a const.
if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
return;
}
// ty_args contains the generic parameters of the type declaration, while args contains the
// arguments used at instantiation time. If both len are not equal, it means that some
// parameters were not provided (which means that the default values were used); in this
// case we will not risk suggesting too broad a rewrite. We won't either if any argument
// is a type or a const.
if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
return;
}
}
@ -188,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
self_ty,
..
}) = item.kind
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
&& !cx.tcx.is_automatically_derived(item.owner_id.to_def_id())
&& !item.span.from_expansion()
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Default, def_id)

View file

@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
}) = item.kind
{
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
@ -235,7 +235,7 @@ fn check_hash_peq<'tcx>(
{
// Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
@ -278,7 +278,7 @@ fn check_ord_partial_ord<'tcx>(
{
// Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived);
let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id);
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
@ -349,6 +349,10 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h
{
return;
}
// The presence of `unsafe` fields prevents deriving `Clone` automatically
if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) {
return;
}
span_lint_and_note(
cx,
@ -426,10 +430,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
}
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
if let ExprKind::Block(block, _) = expr.kind {
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
return ControlFlow::Break(());
}
if let ExprKind::Block(block, _) = expr.kind
&& block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
{
return ControlFlow::Break(());
}
walk_expr(self, expr)
@ -479,7 +483,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
}
/// Creates the `ParamEnv` used for the give type's derived `Eq` impl.
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
// Initial map from generic index to param def.
// Vec<(param_def, needs_eq)>

View file

@ -4,6 +4,7 @@ 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 rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
@ -72,8 +73,15 @@ pub struct DisallowedMacros {
impl DisallowedMacros {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self {
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_macros,
|def_kind| matches!(def_kind, DefKind::Macro(_)),
"macro",
false,
);
Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_macros),
disallowed,
seen: FxHashSet::default(),
derive_src: None,
earlies,

View file

@ -63,9 +63,19 @@ pub struct DisallowedMethods {
impl DisallowedMethods {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
Self {
disallowed: create_disallowed_map(tcx, &conf.disallowed_methods),
}
let (disallowed, _) = create_disallowed_map(
tcx,
&conf.disallowed_methods,
|def_kind| {
matches!(
def_kind,
DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn
)
},
"function",
false,
);
Self { disallowed }
}
}
@ -74,12 +84,7 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (id, span) = match &expr.kind {
ExprKind::Path(path)
if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) =
cx.qpath_res(path, expr.hir_id) =>
{
(id, expr.span)
},
ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span),
ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => {
(id, name.ident.span)
},

View file

@ -1,8 +1,8 @@
use clippy_config::Conf;
use clippy_config::types::DisallowedPath;
use clippy_config::types::{DisallowedPath, create_disallowed_map};
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::Res;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::{AmbigArg, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind};
use rustc_lint::{LateContext, LateLintPass};
@ -60,22 +60,7 @@ pub struct DisallowedTypes {
impl DisallowedTypes {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
let mut def_ids = DefIdMap::default();
let mut prim_tys = FxHashMap::default();
for disallowed_path in &conf.disallowed_types {
let path: Vec<_> = disallowed_path.path().split("::").collect::<Vec<_>>();
for res in clippy_utils::def_path_res(tcx, &path) {
match res {
Res::Def(_, id) => {
def_ids.insert(id, (disallowed_path.path(), disallowed_path));
},
Res::PrimTy(ty) => {
prim_tys.insert(ty, (disallowed_path.path(), disallowed_path));
},
_ => {},
}
}
}
let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true);
Self { def_ids, prim_tys }
}
@ -95,6 +80,19 @@ impl DisallowedTypes {
}
}
pub fn def_kind_predicate(def_kind: DefKind) -> bool {
matches!(
def_kind,
DefKind::Struct
| DefKind::Union
| DefKind::Enum
| DefKind::Trait
| DefKind::TyAlias
| DefKind::ForeignTy
| DefKind::AssocTy
)
}
impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {

View file

@ -113,20 +113,20 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b
s != "-" && s.contains('-')
}
if let Ok(url) = Url::parse(word) {
if let Ok(url) = Url::parse(word)
// try to get around the fact that `foo::bar` parses as a valid URL
if !url.cannot_be_a_base() {
span_lint_and_sugg(
cx,
DOC_MARKDOWN,
span,
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
"try",
format!("<{word}>"),
Applicability::MachineApplicable,
);
return;
}
&& !url.cannot_be_a_base()
{
span_lint_and_sugg(
cx,
DOC_MARKDOWN,
span,
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
"try",
format!("<{word}>"),
Applicability::MachineApplicable,
);
return;
}
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)

View file

@ -1,11 +1,14 @@
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
use clippy_utils::ty::{implements_trait_with_env, is_type_diagnostic_item};
use clippy_utils::{is_doc_hidden, return_ty};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty};
use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{Span, sym};
use std::ops::ControlFlow;
pub fn check(
cx: &LateContext<'_>,
@ -13,7 +16,6 @@ pub fn check(
sig: FnSig<'_>,
headers: DocHeaders,
body_id: Option<BodyId>,
panic_info: Option<(Span, bool)>,
check_private_items: bool,
) {
if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) {
@ -46,13 +48,16 @@ pub fn check(
),
_ => (),
}
if !headers.panics && panic_info.is_some_and(|el| !el.1) {
if !headers.panics
&& let Some(body_id) = body_id
&& let Some(panic_span) = find_panic(cx, body_id)
{
span_lint_and_note(
cx,
MISSING_PANICS_DOC,
span,
"docs for function which may panic missing `# Panics` section",
panic_info.map(|el| el.0),
Some(panic_span),
"first possible panic found here",
);
}
@ -89,3 +94,39 @@ pub fn check(
}
}
}
fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> {
let mut panic_span = None;
let typeck = cx.tcx.typeck_body(body_id);
for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& (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)
))
&& !cx.tcx.hir_is_inside_const_context(expr.hir_id)
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
&& panic_span.is_none()
{
panic_span = Some(macro_call.span);
}
// check for `unwrap` and `expect` for both `Option` and `Result`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or_else(|| method_chain_args(expr, &["expect"]))
&& let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs()
&& matches!(
get_type_diagnostic_name(cx, receiver_ty),
Some(sym::Option | sym::Result)
)
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
&& panic_span.is_none()
{
panic_span = Some(expr.span);
}
// Visit all nodes to fulfill any `#[expect]`s after the first linted panic
ControlFlow::<!>::Continue(())
});
panic_span
}

View file

@ -3,11 +3,8 @@
use clippy_config::Conf;
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable;
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item};
use pulldown_cmark::Event::{
Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start,
TaskListMarker, Text,
@ -16,18 +13,15 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
use rustc_hir::{Attribute, ImplItemKind, ItemKind, Node, Safety, TraitItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty;
use rustc_resolve::rustdoc::{
DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range,
span_of_fragments,
};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::edition::Edition;
use rustc_span::{Span, sym};
use std::ops::Range;
use url::Url;
@ -194,6 +188,19 @@ declare_clippy_lint! {
/// }
/// }
/// ```
///
/// Individual panics within a function can be ignored with `#[expect]` or
/// `#[allow]`:
///
/// ```no_run
/// # use std::num::NonZeroUsize;
/// pub fn will_not_panic(x: usize) {
/// #[expect(clippy::missing_panics_doc, reason = "infallible")]
/// let y = NonZeroUsize::new(1).unwrap();
///
/// // If any panics are added in the future the lint will still catch them
/// }
/// ```
#[clippy::version = "1.51.0"]
pub MISSING_PANICS_DOC,
pedantic,
@ -657,20 +664,16 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
self.check_private_items,
);
match item.kind {
ItemKind::Fn { sig, body: body_id, .. } => {
ItemKind::Fn { sig, body, .. } => {
if !(is_entrypoint_fn(cx, item.owner_id.to_def_id())
|| item.span.in_external_macro(cx.tcx.sess.source_map()))
{
let body = cx.tcx.hir_body(body_id);
let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value);
missing_headers::check(
cx,
item.owner_id,
sig,
headers,
Some(body_id),
panic_info,
Some(body),
self.check_private_items,
);
}
@ -697,15 +700,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
if let TraitItemKind::Fn(sig, ..) = trait_item.kind
&& !trait_item.span.in_external_macro(cx.tcx.sess.source_map())
{
missing_headers::check(
cx,
trait_item.owner_id,
sig,
headers,
None,
None,
self.check_private_items,
);
missing_headers::check(cx, trait_item.owner_id, sig, headers, None, self.check_private_items);
}
},
Node::ImplItem(impl_item) => {
@ -713,16 +708,12 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
&& !impl_item.span.in_external_macro(cx.tcx.sess.source_map())
&& !is_trait_impl_item(cx, impl_item.hir_id())
{
let body = cx.tcx.hir_body(body_id);
let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(impl_item.owner_id), body.value);
missing_headers::check(
cx,
impl_item.owner_id,
sig,
headers,
Some(body_id),
panic_span,
self.check_private_items,
);
}
@ -880,19 +871,18 @@ fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a
if let Some(start) = code_starts_at
&& let Some(end) = code_ends_at
&& code_includes_link
&& let Some(span) = fragments.span(cx, start..end)
{
if let Some(span) = fragments.span(cx, start..end) {
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
diag.span_suggestion_verbose(
span,
"wrap the entire group in `<code>` tags",
sugg,
Applicability::MaybeIncorrect,
);
diag.help("separate code snippets will be shown with a gap");
});
}
span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| {
let sugg = format!("<code>{}</code>", doc[start..end].replace('`', ""));
diag.span_suggestion_verbose(
span,
"wrap the entire group in `<code>` tags",
sugg,
Applicability::MaybeIncorrect,
);
diag.help("separate code snippets will be shown with a gap");
});
}
code_includes_link = false;
code_starts_at = None;
@ -1169,72 +1159,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
headers
}
struct FindPanicUnwrap<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
is_const: bool,
panic_span: Option<Span>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
}
impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> {
pub fn find_span(
cx: &'a LateContext<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
body: impl Visitable<'tcx>,
) -> Option<(Span, bool)> {
let mut vis = Self {
cx,
is_const: false,
panic_span: None,
typeck_results,
};
body.visit(&mut vis);
vis.panic_span.map(|el| (el, vis.is_const))
}
}
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.panic_span.is_some() {
return;
}
if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) {
if is_panic(self.cx, macro_call.def_id)
|| matches!(
self.cx.tcx.item_name(macro_call.def_id).as_str(),
"assert" | "assert_eq" | "assert_ne"
)
{
self.is_const = self.cx.tcx.hir_is_inside_const_context(expr.hir_id);
self.panic_span = Some(macro_call.span);
}
}
// check for `unwrap` and `expect` for both `Option` and `Result`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
|| is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
{
self.panic_span = Some(expr.span);
}
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}
// Panics in const blocks will cause compilation to fail.
fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}
#[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type
fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
if range.end < range.start {

View file

@ -64,7 +64,10 @@ pub fn check(
match parser.parse_item(ForceCollect::No) {
Ok(Some(item)) => match &item.kind {
ItemKind::Fn(box Fn {
ident, sig, body: Some(block), ..
ident,
sig,
body: Some(block),
..
}) if ident.name == sym::main => {
if !ignore {
get_test_spans(&item, *ident, &mut test_attr_spans);

View file

@ -144,10 +144,10 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
// ..
// }
fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool {
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) {
if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) {
return body.hir_id == drop_expr.hir_id;
}
if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
&& let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id)
{
return body.hir_id == drop_expr.hir_id;
}
false
}

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{SpanRangeExt, snippet_indent};
use clippy_utils::tokenize_with_text;
@ -89,7 +91,7 @@ declare_clippy_lint! {
#[derive(Debug)]
struct ItemInfo {
kind: &'static str,
name: Symbol,
name: Option<Symbol>,
span: Span,
mod_items: Option<NodeId>,
}
@ -315,8 +317,12 @@ impl EmptyLineAfter {
for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) {
stop.comment_out(cx, &mut suggestions);
}
let name = match info.name {
Some(name) => format!("{} `{name}`", info.kind).into(),
None => Cow::from("the following item"),
};
diag.multipart_suggestion_verbose(
format!("if the doc comment should not document `{}` comment it out", info.name),
format!("if the doc comment should not document {name} then comment it out"),
suggestions,
Applicability::MaybeIncorrect,
);
@ -381,13 +387,10 @@ impl EmptyLineAfter {
) {
self.items.push(ItemInfo {
kind: kind.descr(),
// FIXME: this `sym::empty` can be leaked, see
// https://github.com/rust-lang/rust/pull/138740#discussion_r2021979899
name: if let Some(ident) = ident { ident.name } else { kw::Empty },
span: if let Some(ident) = ident {
span.with_hi(ident.span.hi())
} else {
span.with_hi(span.lo())
name: ident.map(|ident| ident.name),
span: match ident {
Some(ident) => span.with_hi(ident.span.hi()),
None => span.shrink_to_lo(),
},
mod_items: match kind {
ItemKind::Mod(_, _, ModKind::Loaded(items, _, _, _)) => items
@ -447,7 +450,7 @@ impl EarlyLintPass for EmptyLineAfter {
fn check_crate(&mut self, _: &EarlyContext<'_>, krate: &Crate) {
self.items.push(ItemInfo {
kind: "crate",
name: kw::Crate,
name: Some(kw::Crate),
span: krate.spans.inner_span.with_hi(krate.spans.inner_span.lo()),
mod_items: krate
.items

View file

@ -1,10 +1,15 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Item, ItemKind, Variant, VariantData};
use clippy_utils::attrs::span_contains_cfg;
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_lexer::TokenKind;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
use rustc_hir::def::CtorOf;
use rustc_hir::def::DefKind::Ctor;
use rustc_hir::def::Res::Def;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
@ -70,10 +75,23 @@ declare_clippy_lint! {
"finds enum variants with empty brackets"
}
declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
#[derive(Debug)]
enum Usage {
Unused { redundant_use_sites: Vec<Span> },
Used,
NoDefinition { redundant_use_sites: Vec<Span> },
}
impl EarlyLintPass for EmptyWithBrackets {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
#[derive(Default)]
pub struct EmptyWithBrackets {
// Value holds `Usage::Used` if the empty tuple variant was used as a function
empty_tuple_enum_variants: FxIndexMap<LocalDefId, Usage>,
}
impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
impl LateLintPass<'_> for EmptyWithBrackets {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if let ItemKind::Struct(ident, var_data, _) = &item.kind
&& has_brackets(var_data)
&& let span_after_ident = item.span.with_lo(ident.span.hi())
@ -96,70 +114,175 @@ impl EarlyLintPass for EmptyWithBrackets {
}
}
fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) {
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
// the span of the parentheses/braces
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) {
span_lint_and_then(
if has_no_fields(cx, &variant.data, span_after_ident) {
match variant.data {
VariantData::Struct { .. } => {
// Empty struct variants can be linted immediately
span_lint_and_then(
cx,
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
span_after_ident,
"enum variant has empty brackets",
|diagnostic| {
diagnostic.span_suggestion_hidden(
span_after_ident,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
},
);
},
VariantData::Tuple(.., local_def_id) => {
// Don't lint reachable tuple enums
if cx.effective_visibilities.is_reachable(variant.def_id) {
return;
}
if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) {
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the
// definition was encountered. Now that there's a definition, convert it
// to Usage::Unused.
if let Usage::NoDefinition { redundant_use_sites } = entry {
*entry = Usage::Unused {
redundant_use_sites: redundant_use_sites.clone(),
};
}
} else {
self.empty_tuple_enum_variants.insert(
local_def_id,
Usage::Unused {
redundant_use_sites: vec![],
},
);
}
},
VariantData::Unit(..) => {},
}
}
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(def_id) = check_expr_for_enum_as_function(expr) {
if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) {
// Do not count expressions from macro expansion as a redundant use site.
if expr.span.from_expansion() {
return;
}
match self.empty_tuple_enum_variants.get_mut(&def_id) {
Some(
&mut (Usage::Unused {
ref mut redundant_use_sites,
}
| Usage::NoDefinition {
ref mut redundant_use_sites,
}),
) => {
redundant_use_sites.push(parentheses_span);
},
None => {
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
self.empty_tuple_enum_variants.insert(
def_id,
Usage::NoDefinition {
redundant_use_sites: vec![parentheses_span],
},
);
},
_ => {},
}
} else {
// The parentheses are not redundant.
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
}
}
}
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
// Ignore all variants with Usage::Used or Usage::NoDefinition
let Usage::Unused { redundant_use_sites } = usage else {
continue;
};
// Attempt to fetch the Variant from LocalDefId.
let Node::Variant(variant) = cx.tcx.hir_node(
cx.tcx
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
) else {
continue;
};
// Span of the parentheses in variant definition
let span = variant.span.with_lo(variant.ident.span.hi());
span_lint_hir_and_then(
cx,
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
span_after_ident,
variant.hir_id,
span,
"enum variant has empty brackets",
|diagnostic| {
diagnostic.span_suggestion_hidden(
span_after_ident,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
if redundant_use_sites.is_empty() {
// If there's no redundant use sites, the definition is the only place to modify.
diagnostic.span_suggestion_hidden(
span,
"remove the brackets",
"",
Applicability::MaybeIncorrect,
);
} else {
let mut parentheses_spans: Vec<_> =
redundant_use_sites.iter().map(|span| (*span, String::new())).collect();
parentheses_spans.push((span, String::new()));
diagnostic.multipart_suggestion(
"remove the brackets",
parentheses_spans,
Applicability::MaybeIncorrect,
);
}
},
);
}
}
}
fn has_no_ident_token(braces_span_str: &str) -> bool {
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
fn has_brackets(var_data: &VariantData<'_>) -> bool {
!matches!(var_data, VariantData::Unit(..))
}
fn has_brackets(var_data: &VariantData) -> bool {
!matches!(var_data, VariantData::Unit(_))
}
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
if !var_data.fields().is_empty() {
return false;
}
fn has_no_fields(cx: &LateContext<'_>, var_data: &VariantData<'_>, braces_span: Span) -> bool {
var_data.fields().is_empty() &&
// there might still be field declarations hidden from the AST
// (conditionally compiled code using #[cfg(..)])
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
return false;
};
has_no_ident_token(braces_span_str.as_ref())
!span_contains_cfg(cx, braces_span)
}
#[cfg(test)]
mod unit_test {
use super::*;
#[test]
fn test_has_no_ident_token() {
let input = "{ field: u8 }";
assert!(!has_no_ident_token(input));
let input = "(u8, String);";
assert!(!has_no_ident_token(input));
let input = " {
// test = 5
}
";
assert!(has_no_ident_token(input));
let input = " ();";
assert!(has_no_ident_token(input));
// If expression HIR ID and callee HIR ID are same, returns the span of the parentheses, else,
// returns None.
fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
if let Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::Call(callee, ..) = parent.kind
&& callee.hir_id == expr.hir_id
{
Some(parent.span.with_lo(expr.span.hi()))
} else {
None
}
}
// Returns the LocalDefId of the variant being called as a function if it exists.
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
if let ExprKind::Path(QPath::Resolved(
_,
Path {
res: Def(Ctor(CtorOf::Variant, _), def_id),
..
},
)) = expr.kind
{
def_id.as_local()
} else {
None
}
}

View file

@ -95,14 +95,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
return;
};
if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
return;
}
if then_search.edits.is_empty() && else_search.edits.is_empty() {
// No insertions
return;
} else if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy {
// If there are other uses of the key, and the key is not copy,
// we cannot perform a fix automatically, but continue to emit a lint.
None
} else if then_search.edits.is_empty() || else_search.edits.is_empty() {
// if .. { insert } else { .. } or if .. { .. } else { insert }
let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
@ -123,10 +122,10 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
),
};
format!(
Some(format!(
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {then_str} else {else_str}",
map_ty.entry_path(),
)
))
} else {
// if .. { insert } else { insert }
let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
@ -142,13 +141,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
};
let indent_str = snippet_indent(cx, expr.span);
let indent_str = indent_str.as_deref().unwrap_or("");
format!(
Some(format!(
"match {map_str}.entry({key_str}) {{\n{indent_str} {entry}::{then_entry} => {}\n\
{indent_str} {entry}::{else_entry} => {}\n{indent_str}}}",
reindent_multiline(&then_str, true, Some(4 + indent_str.len())),
reindent_multiline(&else_str, true, Some(4 + indent_str.len())),
entry = map_ty.entry_path(),
)
))
}
} else {
if then_search.edits.is_empty() {
@ -163,17 +162,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
} else {
then_search.snippet_occupied(cx, then_expr.span, &mut app)
};
format!(
Some(format!(
"if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}",
map_ty.entry_path(),
)
))
} else if let Some(insertion) = then_search.as_single_insertion() {
let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
if contains_expr.negated {
if insertion.value.can_have_side_effects() {
format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});")
Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});"))
} else {
format!("{map_str}.entry({key_str}).or_insert({value_str});")
Some(format!("{map_str}.entry({key_str}).or_insert({value_str});"))
}
} else {
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
@ -183,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
} else {
let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
if contains_expr.negated {
format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});")
Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});"))
} else {
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
// This would need to be a different lint.
@ -192,7 +191,11 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
}
};
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
if let Some(sugg) = sugg {
span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app);
} else {
span_lint(cx, MAP_ENTRY, expr.span, lint_msg);
}
}
}

View file

@ -49,10 +49,10 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
.ok()
.map(|val| rustc_middle::mir::Const::from_value(val, ty));
if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) {
if let ty::Adt(adt, _) = ty.kind() {
if adt.is_enum() {
ty = adt.repr().discr_type().to_ty(cx.tcx);
}
if let ty::Adt(adt, _) = ty.kind()
&& adt.is_enum()
{
ty = adt.repr().discr_type().to_ty(cx.tcx);
}
match ty.kind() {
ty::Int(IntTy::Isize) => {

View file

@ -72,10 +72,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
_: Span,
fn_def_id: LocalDefId,
) {
if let Some(header) = fn_kind.header() {
if header.abi != ExternAbi::Rust {
return;
}
if let Some(header) = fn_kind.header()
&& header.abi != ExternAbi::Rust
{
return;
}
let parent_id = cx
@ -93,12 +93,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
// find `self` ty for this trait if relevant
if let ItemKind::Trait(_, _, _, _, _, items) = item.kind {
for trait_item in items {
if trait_item.id.owner_id.def_id == fn_def_id {
if trait_item.id.owner_id.def_id == fn_def_id
// be sure we have `self` parameter in this function
if trait_item.kind == (AssocItemKind::Fn { has_self: true }) {
trait_self_ty =
Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty());
}
&& trait_item.kind == (AssocItemKind::Fn { has_self: true })
{
trait_self_ty = Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty());
}
}
}
@ -142,22 +141,22 @@ fn is_argument(tcx: TyCtxt<'_>, id: HirId) -> bool {
impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
if cmt.place.projections.is_empty() {
if let PlaceBase::Local(lid) = cmt.place.base {
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
if cmt.place.projections.is_empty()
&& let PlaceBase::Local(lid) = cmt.place.base
{
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
}
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
if cmt.place.projections.is_empty() {
if let PlaceBase::Local(lid) = cmt.place.base {
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
if cmt.place.projections.is_empty()
&& let PlaceBase::Local(lid) = cmt.place.base
{
// FIXME(rust/#120456) - is `swap_remove` correct?
self.set.swap_remove(&lid);
}
}
@ -171,10 +170,11 @@ impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> {
// skip if there is a `self` parameter binding to a type
// that contains `Self` (i.e.: `self: Box<Self>`), see #4804
if let Some(trait_self_ty) = self.trait_self_ty {
if self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) {
return;
}
if let Some(trait_self_ty) = self.trait_self_ty
&& self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower
&& cmt.place.ty().contains(trait_self_ty)
{
return;
}
if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {

View file

@ -75,10 +75,10 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) {
if is_panic(self.lcx, macro_call.def_id) {
self.result.push(expr.span);
}
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr)
&& is_panic(self.lcx, macro_call.def_id)
{
self.result.push(expr.span);
}
// check for `unwrap`

View file

@ -154,7 +154,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
};
}
suggestion.maybe_par()
suggestion.maybe_paren()
}
fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
@ -165,7 +165,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, ar
expr.span,
"logarithm for bases 2, 10 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}
@ -228,24 +228,24 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> {
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
// Check receiver
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) {
if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver)
&& let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
Some("exp")
} else if F32(2.0) == value || F64(2.0) == value {
Some("exp2")
} else {
None
} {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"exponent for bases 2 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
Applicability::MachineApplicable,
);
}
{
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"exponent for bases 2 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
Applicability::MachineApplicable,
);
}
// Check argument
@ -254,13 +254,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
(
SUBOPTIMAL_FLOPS,
"square-root of a number can be computed more efficiently and accurately",
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
)
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
(
IMPRECISE_FLOPS,
"cube-root of a number can be computed more accurately",
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_par()),
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
)
} else if let Some(exponent) = get_integer_from_float_constant(&value) {
(
@ -268,7 +268,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
"exponentiation with integer powers can be computed more efficiently",
format!(
"{}.powi({})",
Sugg::hir(cx, receiver, "..").maybe_par(),
Sugg::hir(cx, receiver, "..").maybe_paren(),
numeric_literal::format(&exponent.to_string(), None, false)
),
)
@ -289,55 +289,53 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
}
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
if value == Int(2) {
if let Some(parent) = get_parent_expr(cx, expr) {
if let Some(grandparent) = get_parent_expr(cx, parent) {
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
{
if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
return;
}
}
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0])
&& value == Int(2)
&& let Some(parent) = get_parent_expr(cx, expr)
{
if let Some(grandparent) = get_parent_expr(cx, parent)
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind
&& method_name.as_str() == "sqrt"
&& detect_hypot(cx, receiver).is_some()
{
return;
}
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = parent.kind
{
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
// Negate expr if original code has subtraction and expr is on the right side
let maybe_neg_sugg = |expr, hir_id| {
let sugg = Sugg::hir(cx, expr, "..");
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
-sugg
} else {
sugg
}
};
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = parent.kind
{
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
// Negate expr if original code has subtraction and expr is on the right side
let maybe_neg_sugg = |expr, hir_id| {
let sugg = Sugg::hir(cx, expr, "..");
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
-sugg
} else {
sugg
}
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
parent.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!(
"{}.mul_add({}, {})",
Sugg::hir(cx, receiver, "..").maybe_par(),
maybe_neg_sugg(receiver, expr.hir_id),
maybe_neg_sugg(other_addend, other_addend.hir_id),
),
Applicability::MachineApplicable,
);
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
parent.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!(
"{}.mul_add({}, {})",
Sugg::hir(cx, receiver, "..").maybe_paren(),
maybe_neg_sugg(receiver, expr.hir_id),
maybe_neg_sugg(other_addend, other_addend.hir_id),
),
Applicability::MachineApplicable,
);
}
}
}
@ -371,7 +369,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, lmul_lhs, "..").maybe_par(),
Sugg::hir(cx, lmul_lhs, "..").maybe_paren(),
Sugg::hir(cx, rmul_lhs, "..")
));
}
@ -403,7 +401,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, largs_0, "..").maybe_par(),
Sugg::hir(cx, largs_0, "..").maybe_paren(),
Sugg::hir(cx, rargs_0, "..")
));
}
@ -449,7 +447,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()),
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}
@ -483,12 +481,12 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs,
) = &expr.kind
{
if let Some(parent) = get_parent_expr(cx, expr) {
if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind {
if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() {
return;
}
}
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind
&& method_name.as_str() == "sqrt"
&& detect_hypot(cx, receiver).is_some()
{
return;
}
let maybe_neg_sugg = |expr| {
@ -566,15 +564,15 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// If the two expressions are not negations of each other, then it
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind {
if eq_expr_value(cx, expr1_negated, expr2) {
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind
&& eq_expr_value(cx, expr1_negated, expr2)
{
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind {
if eq_expr_value(cx, expr1, expr2_negated) {
return Some((true, expr1));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind
&& eq_expr_value(cx, expr1, expr2_negated)
{
return Some((true, expr1));
}
None
}
@ -591,11 +589,11 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
{
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
@ -672,7 +670,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
"consider using",
format!(
"{}.log({})",
Sugg::hir(cx, largs_self, "..").maybe_par(),
Sugg::hir(cx, largs_self, "..").maybe_paren(),
Sugg::hir(cx, rargs_self, ".."),
),
Applicability::MachineApplicable,
@ -703,7 +701,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
{
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
@ -726,7 +724,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
{
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed

View file

@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
.into_owned()
} else {
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
format!("{}.to_string()", sugg.maybe_par())
format!("{}.to_string()", sugg.maybe_paren())
};
span_useless_format(cx, call_site, sugg, applicability);
}

View file

@ -141,7 +141,7 @@ declare_clippy_lint! {
/// format!("{var:.prec$}");
/// ```
///
/// If allow-mixed-uninlined-format-args is set to false in clippy.toml,
/// If `allow-mixed-uninlined-format-args` is set to `false` in clippy.toml,
/// the following code will also trigger the lint:
/// ```no_run
/// # let var = 42;
@ -159,7 +159,7 @@ declare_clippy_lint! {
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
#[clippy::version = "1.66.0"]
pub UNINLINED_FORMAT_ARGS,
pedantic,
style,
"using non-inlined variables in `format!` calls"
}

View file

@ -1,13 +1,13 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators, sym};
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Symbol;
use rustc_span::symbol::kw;
use rustc_span::{Symbol, sym};
declare_clippy_lint! {
/// ### What it does
@ -185,13 +185,13 @@ impl FormatImplExpr<'_, '_> {
&& let trait_name = match placeholder.format_trait {
FormatTrait::Display => sym::Display,
FormatTrait::Debug => sym::Debug,
FormatTrait::LowerExp => sym!(LowerExp),
FormatTrait::UpperExp => sym!(UpperExp),
FormatTrait::Octal => sym!(Octal),
FormatTrait::LowerExp => sym::LowerExp,
FormatTrait::UpperExp => sym::UpperExp,
FormatTrait::Octal => sym::Octal,
FormatTrait::Pointer => sym::Pointer,
FormatTrait::Binary => sym!(Binary),
FormatTrait::LowerHex => sym!(LowerHex),
FormatTrait::UpperHex => sym!(UpperHex),
FormatTrait::Binary => sym::Binary,
FormatTrait::LowerHex => sym::LowerHex,
FormatTrait::UpperHex => sym::UpperHex,
}
&& trait_name == self.format_trait_impl.name
&& let Ok(index) = placeholder.argument.index

View file

@ -138,27 +138,28 @@ impl EarlyLintPass for Formatting {
/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
if !lhs.span.from_expansion() && !rhs.span.from_expansion() {
let eq_span = lhs.span.between(rhs.span);
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
let op = op.as_str();
let eqop_span = lhs.span.between(sub_rhs.span);
if eq_snippet.ends_with('=') {
span_lint_and_note(
cx,
SUSPICIOUS_ASSIGNMENT_FORMATTING,
eqop_span,
format!(
"this looks like you are trying to use `.. {op}= ..`, but you \
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind
&& !lhs.span.from_expansion()
&& !rhs.span.from_expansion()
{
let eq_span = lhs.span.between(rhs.span);
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind
&& let Some(eq_snippet) = snippet_opt(cx, eq_span)
{
let op = op.as_str();
let eqop_span = lhs.span.between(sub_rhs.span);
if eq_snippet.ends_with('=') {
span_lint_and_note(
cx,
SUSPICIOUS_ASSIGNMENT_FORMATTING,
eqop_span,
format!(
"this looks like you are trying to use `.. {op}= ..`, but you \
really are doing `.. = ({op} ..)`"
),
None,
format!("to remove this lint, use either `{op}=` or `= {op}`"),
);
}
}
),
None,
format!("to remove this lint, use either `{op}=` or `= {op}`"),
);
}
}
}

View file

@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
};
let sugg =
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_paren();
span_lint_and_sugg(
cx,

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind};
use rustc_hir::{BlockCheckMode, Body, ExprKind, FnDecl, ImplicitSelfKind, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;
@ -40,14 +40,25 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
name
};
// Body must be &(mut) <self_data>.name
// Body must be `&(mut) <self_data>.name`, potentially in an `unsafe` block
// self_data is not necessarily self, to also lint sub-getters, etc…
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(block_expr) = block.expr
{
block_expr
if let ExprKind::Block(unsafe_block, _) = block_expr.kind
&& unsafe_block.stmts.is_empty()
&& matches!(
unsafe_block.rules,
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
)
&& let Some(unsafe_block_expr) = unsafe_block.expr
{
unsafe_block_expr
} else {
block_expr
}
} else {
return;
};

View file

@ -59,9 +59,7 @@ impl RenamedFnArgs {
let mut renamed: Vec<(Span, String)> = vec![];
debug_assert!(default_idents.size_hint() == current_idents.size_hint());
while let (Some(default_ident), Some(current_ident)) =
(default_idents.next(), current_idents.next())
{
while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) {
let has_name_to_check = |ident: Option<Ident>| {
if let Some(ident) = ident
&& ident.name != kw::Underscore

View file

@ -47,16 +47,16 @@ pub(super) fn check_fn(
}
pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>, too_many_arguments_threshold: u64) {
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind
// don't lint extern functions decls, it's not their fault
if sig.header.abi == ExternAbi::Rust {
check_arg_number(
cx,
sig.decl,
item.span.with_hi(sig.decl.output.span().hi()),
too_many_arguments_threshold,
);
}
&& sig.header.abi == ExternAbi::Rust
{
check_arg_number(
cx,
sig.decl,
item.span.with_hi(sig.decl.output.span().hi()),
too_many_arguments_threshold,
);
}
}

View file

@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
|diag| {
let mut app = Applicability::MachineApplicable;
let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app)
.maybe_par()
.maybe_paren()
.to_string();
let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let method_body = if let Some(first_stmt) = then_block.stmts.first() {

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context};
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro};
use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
@ -134,6 +134,10 @@ fn lint_implicit_returns(
},
ExprKind::Match(_, arms, _) => {
if let Some(await_expr) = desugar_await(expr) {
lint_return(cx, await_expr.hir_id, await_expr.span);
return LintLocation::Inner;
}
for arm in arms {
let res = lint_implicit_returns(
cx,
@ -153,18 +157,18 @@ fn lint_implicit_returns(
ExprKind::Loop(block, ..) => {
let mut add_return = false;
let _: Option<!> = for_each_expr_without_closures(block, |e| {
if let ExprKind::Break(dest, sub_expr) = e.kind {
if dest.target_id.ok() == Some(expr.hir_id) {
if call_site_span.is_none() && e.span.ctxt() == ctxt {
// At this point sub_expr can be `None` in async functions which either diverge, or return
// the unit type.
if let Some(sub_expr) = sub_expr {
lint_break(cx, e.hir_id, e.span, sub_expr.span);
}
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
if let ExprKind::Break(dest, sub_expr) = e.kind
&& dest.target_id.ok() == Some(expr.hir_id)
{
if call_site_span.is_none() && e.span.ctxt() == ctxt {
// At this point sub_expr can be `None` in async functions which either diverge, or return
// the unit type.
if let Some(sub_expr) = sub_expr {
lint_break(cx, e.hir_id, e.span, sub_expr.span);
}
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
}
}
ControlFlow::Continue(())
@ -241,6 +245,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
Some(e) => e,
None => return,
}
} else if let Some(expr) = get_async_closure_expr(cx.tcx, body.value) {
expr
} else {
body.value
};

View file

@ -239,7 +239,7 @@ fn check_subtraction(
// This part of the condition is voluntarily split from the one before to ensure that
// if `snippet_opt` fails, it won't try the next conditions.
if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST))
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par)
&& let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren)
&& let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr)
{
let sugg = format!(

View file

@ -8,7 +8,7 @@ use rustc_hir::{
};
use rustc_hir_analysis::lower_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt};
use rustc_middle::ty::{self, AssocItem, ClauseKind, Generics, Ty, TyCtxt};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
@ -315,7 +315,7 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) {
assocs
.filter_by_name_unhygienic(constraint.ident.name)
.next()
.is_some_and(|assoc| assoc.is_type())
.is_some_and(AssocItem::is_type)
})
{
emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound);

View file

@ -65,13 +65,13 @@ declare_clippy_lint! {
}
pub struct InconsistentStructConstructor {
lint_inconsistent_struct_field_initializers: bool,
check_inconsistent_struct_field_initializers: bool,
}
impl InconsistentStructConstructor {
pub fn new(conf: &'static Conf) -> Self {
Self {
lint_inconsistent_struct_field_initializers: conf.lint_inconsistent_struct_field_initializers,
check_inconsistent_struct_field_initializers: conf.check_inconsistent_struct_field_initializers,
}
}
}
@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
let all_fields_are_shorthand = fields.iter().all(|f| f.is_shorthand);
let applicability = if all_fields_are_shorthand {
Applicability::MachineApplicable
} else if self.lint_inconsistent_struct_field_initializers {
} else if self.check_inconsistent_struct_field_initializers {
Applicability::MaybeIncorrect
} else {
return;

View file

@ -2,13 +2,12 @@ use clippy_config::Conf;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::ty::{deref_chain, get_adt_inherent_method};
use clippy_utils::{higher, is_from_proc_macro, is_in_test};
use clippy_utils::{higher, is_from_proc_macro, is_in_test, sym};
use rustc_ast::ast::RangeLimits;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
@ -136,28 +135,28 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
let const_range = to_const_range(cx, range, size);
if let (Some(start), _) = const_range {
if start > size {
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.start.map_or(expr.span, |start| start.span),
"range is out of bounds",
);
return;
}
if let (Some(start), _) = const_range
&& start > size
{
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.start.map_or(expr.span, |start| start.span),
"range is out of bounds",
);
return;
}
if let (_, Some(end)) = const_range {
if end > size {
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.end.map_or(expr.span, |end| end.span),
"range is out of bounds",
);
return;
}
if let (_, Some(end)) = const_range
&& end > size
{
span_lint(
cx,
OUT_OF_BOUNDS_INDEXING,
range.end.map_or(expr.span, |end| end.span),
"range is out of bounds",
);
return;
}
if let (Some(_), Some(_)) = const_range {
@ -268,7 +267,7 @@ fn ty_has_applicable_get_function<'tcx>(
index_expr: &Expr<'_>,
) -> bool {
if let ty::Adt(_, _) = array_ty.kind()
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym!(get)).map(|m| {
&& let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym::get).map(|m| {
cx.tcx
.fn_sig(m.def_id)
.skip_binder()

View file

@ -156,11 +156,12 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
.and(cap);
}
}
if method.ident.name.as_str() == "flat_map" && args.len() == 1 {
if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
let body = cx.tcx.hir_body(body);
return is_infinite(cx, body.value);
}
if method.ident.name.as_str() == "flat_map"
&& args.len() == 1
&& let ExprKind::Closure(&Closure { body, .. }) = args[0].kind
{
let body = cx.tcx.hir_body(body);
return is_infinite(cx, body.value);
}
Finite
},

View file

@ -123,7 +123,7 @@ fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg
expr.span,
"manual implementation of `Instant::elapsed`",
"try",
format!("{}.elapsed()", sugg.maybe_par()),
format!("{}.elapsed()", sugg.maybe_paren()),
Applicability::MachineApplicable,
);
}

View file

@ -130,14 +130,14 @@ impl IntPlusOne {
BinOpKind::Le => "<",
_ => return None,
};
if let Some(snippet) = node.span.get_source_text(cx) {
if let Some(other_side_snippet) = other_side.span.get_source_text(cx) {
let rec = match side {
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
};
return rec;
}
if let Some(snippet) = node.span.get_source_text(cx)
&& let Some(other_side_snippet) = other_side.span.get_source_text(cx)
{
let rec = match side {
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
};
return rec;
}
None
}
@ -157,10 +157,10 @@ impl IntPlusOne {
impl EarlyLintPass for IntPlusOne {
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
Self::emit_warning(cx, item, rec);
}
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind
&& let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs)
{
Self::emit_warning(cx, item, rec);
}
}
}

View file

@ -91,49 +91,49 @@ fn upcast_comparison_bounds_err<'tcx>(
rhs: &'tcx Expr<'_>,
invert: bool,
) {
if let Some((lb, ub)) = lhs_bounds {
if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) {
if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub {
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
}
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val < lb
} else {
ub < norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val <= lb
} else {
ub <= norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, true);
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val >= ub
} else {
lb >= norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val > ub
} else {
lb > norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, false);
if let Some((lb, ub)) = lhs_bounds
&& let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs)
{
if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub {
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
}
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val < lb
} else {
ub < norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val <= lb
} else {
ub <= norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, true);
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val >= ub
} else {
lb >= norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val > ub
} else {
lb > norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, false);
}
}
}

View file

@ -377,22 +377,21 @@ impl ItemNameRepetitions {
"field name starts with the struct's name",
);
}
if field_words.len() > item_name_words.len() {
if field_words.len() > item_name_words.len()
// lint only if the end is not covered by the start
if field_words
&& field_words
.iter()
.rev()
.zip(item_name_words.iter().rev())
.all(|(a, b)| a == b)
{
span_lint_hir(
cx,
STRUCT_FIELD_NAMES,
field.hir_id,
field.span,
"field name ends with the struct's name",
);
}
{
span_lint_hir(
cx,
STRUCT_FIELD_NAMES,
field.hir_id,
field.span,
"field name ends with the struct's name",
);
}
}
}
@ -445,57 +444,56 @@ impl LateLintPass<'_> for ItemNameRepetitions {
let item_name = ident.name.as_str();
let item_camel = to_camel_case(item_name);
if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
if let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules {
// constants don't have surrounding modules
if !mod_camel.is_empty() {
if mod_name == &ident.name
&& let ItemKind::Mod(..) = item.kind
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
{
span_lint(
if !item.span.from_expansion() && is_present_in_source(cx, item.span)
&& let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules
// constants don't have surrounding modules
&& !mod_camel.is_empty()
{
if mod_name == &ident.name
&& let ItemKind::Mod(..) = item.kind
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
{
span_lint(
cx,
MODULE_INCEPTION,
item.span,
"module has the same name as its containing module",
);
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is 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()
{
let matching = count_match_start(mod_camel, &item_camel);
let rmatching = count_match_end(mod_camel, &item_camel);
let nchars = mod_camel.chars().count();
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
if matching.char_count == nchars {
match item_camel.chars().nth(nchars) {
Some(c) if is_word_beginning(c) => span_lint(
cx,
MODULE_INCEPTION,
item.span,
"module has the same name as its containing module",
);
}
// The `module_name_repetitions` lint should only trigger if the item has the module in its
// name. Having the same name is 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()
{
let matching = count_match_start(mod_camel, &item_camel);
let rmatching = count_match_end(mod_camel, &item_camel);
let nchars = mod_camel.chars().count();
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
if matching.char_count == nchars {
match item_camel.chars().nth(nchars) {
Some(c) if is_word_beginning(c) => span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name starts with its containing module's name",
),
_ => (),
}
}
if rmatching.char_count == nchars
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
{
span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name ends with its containing module's name",
);
}
MODULE_NAME_REPETITIONS,
ident.span,
"item name starts with its containing module's name",
),
_ => (),
}
}
if rmatching.char_count == nchars
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
{
span_lint(
cx,
MODULE_NAME_REPETITIONS,
ident.span,
"item name ends with its containing module's name",
);
}
}
}

View file

@ -2,13 +2,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, is_trait_method, peel_ref_operators};
use clippy_utils::{fulfill_or_allowed, get_item_name, get_parent_as_impl, is_trait_method, peel_ref_operators, sym};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::{
AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind,
AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind,
ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy,
QPath, TraitItemRef, TyKind,
};
@ -16,7 +16,6 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, FnSig, Ty};
use rustc_session::declare_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::sym;
use rustc_span::{Ident, Span, Symbol};
use rustc_trait_selection::traits::supertrait_def_ids;
@ -143,7 +142,6 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
&& let Some(local_id) = ty_id.as_local()
&& let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
&& !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id)
&& let Some(output) =
parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
{
@ -157,7 +155,17 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
},
_ => return,
};
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
check_for_is_empty(
cx,
sig.span,
sig.decl.implicit_self,
output,
ty_id,
name,
kind,
item.hir_id(),
ty_hir_id,
);
}
}
@ -180,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
let mut applicability = Applicability::MachineApplicable;
let lit1 = peel_ref_operators(cx, lt.init);
let lit_str = Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_par();
let lit_str = Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_paren();
span_lint_and_sugg(
cx,
@ -202,7 +210,11 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
expr.span,
lhs_expr,
peel_ref_operators(cx, rhs_expr),
(method.ident.name == sym::ne).then_some("!").unwrap_or_default(),
if method.ident.name == sym::ne {
"!"
} else {
Default::default()
},
);
}
@ -282,15 +294,10 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden
{
let mut current_and_super_traits = DefIdSet::default();
fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx);
let is_empty = sym!(is_empty);
let is_empty_method_found = current_and_super_traits
.items()
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(is_empty))
.any(|i| {
i.is_method()
&& cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1
});
.flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(sym::is_empty))
.any(|i| i.is_method() && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1);
if !is_empty_method_found {
span_lint(
@ -442,6 +449,7 @@ fn check_is_empty_sig<'tcx>(
}
/// Checks if the given type has an `is_empty` method with the appropriate signature.
#[expect(clippy::too_many_arguments)]
fn check_for_is_empty(
cx: &LateContext<'_>,
span: Span,
@ -450,6 +458,8 @@ fn check_for_is_empty(
impl_ty: DefId,
item_name: Symbol,
item_kind: &str,
len_method_hir_id: HirId,
ty_decl_hir_id: HirId,
) {
// Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
// find the correct inherent impls.
@ -459,12 +469,11 @@ fn check_for_is_empty(
return;
};
let is_empty = Symbol::intern("is_empty");
let is_empty = cx
.tcx
.inherent_impls(impl_ty)
.iter()
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(sym::is_empty))
.find(|item| item.is_fn());
let (msg, is_empty_span, self_kind) = match is_empty {
@ -505,14 +514,16 @@ fn check_for_is_empty(
Some(_) => return,
};
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
if let Some(span) = is_empty_span {
db.span_note(span, "`is_empty` defined here");
}
if let Some(self_kind) = self_kind {
db.note(output.expected_sig(self_kind));
}
});
if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
if let Some(span) = is_empty_span {
db.span_note(span, "`is_empty` defined here");
}
if let Some(self_kind) = self_kind {
db.note(output.expected_sig(self_kind));
}
});
}
}
fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
@ -522,10 +533,10 @@ fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>
if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) {
// check if we are in an is_empty() method
if let Some(name) = get_item_name(cx, method) {
if name.as_str() == "is_empty" {
return;
}
if let Some(name) = get_item_name(cx, method)
&& name.as_str() == "is_empty"
{
return;
}
check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to);
@ -572,7 +583,7 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex
let mut applicability = Applicability::MachineApplicable;
let lit1 = peel_ref_operators(cx, lit1);
let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_par();
let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren();
span_lint_and_sugg(
cx,
@ -587,11 +598,11 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex
}
fn is_empty_string(expr: &Expr<'_>) -> bool {
if let ExprKind::Lit(lit) = expr.kind {
if let LitKind::Str(lit, _) = lit.node {
let lit = lit.as_str();
return lit.is_empty();
}
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Str(lit, _) = lit.node
{
let lit = lit.as_str();
return lit.is_empty();
}
false
}
@ -618,11 +629,10 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
/// Checks the inherent impl's items for an `is_empty(self)` method.
fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
let is_empty = sym!(is_empty);
cx.tcx.inherent_impls(id).iter().any(|imp| {
cx.tcx
.associated_items(*imp)
.filter_by_name_unhygienic(is_empty)
.filter_by_name_unhygienic(sym::is_empty)
.any(|item| is_is_empty(cx, item))
})
}
@ -630,10 +640,9 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool {
match ty.kind() {
ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| {
let is_empty = sym!(is_empty);
cx.tcx
.associated_items(principal.def_id())
.filter_by_name_unhygienic(is_empty)
.filter_by_name_unhygienic(sym::is_empty)
.any(|item| is_is_empty(cx, item))
}),
ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id),

View file

@ -65,7 +65,6 @@ mod declare_clippy_lint;
#[macro_use]
extern crate clippy_utils;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
mod utils;
pub mod ctfe; // Very important lint, do not remove (rust#125116)
@ -205,6 +204,7 @@ mod loops;
mod macro_metavars_in_unsafe;
mod macro_use;
mod main_recursion;
mod manual_abs_diff;
mod manual_assert;
mod manual_async_fn;
mod manual_bits;
@ -226,7 +226,6 @@ mod manual_rotate;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod manual_unwrap_or_default;
mod map_unit_fn;
mod match_result_ok;
mod matches;
@ -320,6 +319,7 @@ mod redundant_locals;
mod redundant_pub_crate;
mod redundant_slicing;
mod redundant_static_lifetimes;
mod redundant_test_prefix;
mod redundant_type_annotations;
mod ref_option_ref;
mod ref_patterns;
@ -412,21 +412,6 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use utils::attr_collector::{AttrCollector, AttrStorage};
/// Register all pre expansion lints
///
/// Pre-expansion lints run before any macro expansion has happened.
///
/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate
/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
///
/// Used in `./src/driver.rs`.
pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
// NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf)));
store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf)));
}
#[derive(Default)]
struct RegistrationGroups {
all: Vec<LintId>,
@ -439,8 +424,6 @@ struct RegistrationGroups {
restriction: Vec<LintId>,
style: Vec<LintId>,
suspicious: Vec<LintId>,
#[cfg(feature = "internal")]
internal: Vec<LintId>,
}
impl RegistrationGroups {
@ -456,8 +439,6 @@ impl RegistrationGroups {
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), self.restriction);
store.register_group(true, "clippy::style", Some("clippy_style"), self.style);
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), self.suspicious);
#[cfg(feature = "internal")]
store.register_group(true, "clippy::internal", Some("clippy_internal"), self.internal);
}
}
@ -472,8 +453,6 @@ pub(crate) enum LintCategory {
Restriction,
Style,
Suspicious,
#[cfg(feature = "internal")]
Internal,
}
#[allow(clippy::enum_glob_use)]
@ -495,8 +474,6 @@ impl LintCategory {
Restriction => &mut groups.restriction,
Style => &mut groups.style,
Suspicious => &mut groups.suspicious,
#[cfg(feature = "internal")]
Internal => &mut groups.internal,
}
}
}
@ -530,8 +507,6 @@ impl LintInfo {
Restriction => "restriction",
Style => "style",
Suspicious => "suspicious",
#[cfg(feature = "internal")]
Internal => "internal",
}
}
}
@ -589,6 +564,13 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_removed(name, reason);
}
// NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
// Due to the architecture of the compiler, currently `cfg_attr` attributes on crate
// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf)));
store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf)));
let format_args_storage = FormatArgsStorage::default();
let format_args = format_args_storage.clone();
store.register_early_pass(move || {
@ -601,30 +583,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
let attrs = attr_storage.clone();
store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone())));
// all the internal lints
#[cfg(feature = "internal")]
{
store.register_early_pass(|| {
Box::new(utils::internal_lints::unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths)
});
store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce));
store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths));
store.register_late_pass(|_| {
Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default()
});
store.register_late_pass(|_| {
Box::<utils::internal_lints::lint_without_lint_pass::LintWithoutLintPass>::default()
});
store.register_late_pass(|_| Box::<utils::internal_lints::unnecessary_def_path::UnnecessaryDefPath>::default());
store.register_late_pass(|_| Box::new(utils::internal_lints::outer_expn_data_pass::OuterExpnDataPass));
store.register_late_pass(|_| Box::new(utils::internal_lints::msrv_attr_impl::MsrvAttrImpl));
store.register_late_pass(|_| {
Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new())
});
store.register_late_pass(|_| Box::new(utils::internal_lints::slow_symbol_comparisons::SlowSymbolComparisons));
}
store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe));
store.register_late_pass(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf)));
@ -772,7 +730,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall));
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, 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));
@ -857,7 +815,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(write::Write::new(conf, format_args.clone())));
store.register_late_pass(move |_| Box::new(cargo::Cargo::new(conf)));
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
store.register_early_pass(|| Box::new(empty_with_brackets::EmptyWithBrackets));
store.register_late_pass(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default()));
store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
store.register_early_pass(|| Box::new(pub_use::PubUse));
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));
@ -879,6 +837,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf)));
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf)));
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf)));
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf)));
store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew));
store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable));
@ -960,7 +919,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations));
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf)));
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf)));
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf)));
@ -971,7 +929,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock));
store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf)));
store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg));
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
@ -984,5 +942,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(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));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -150,10 +150,10 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
} = item.kind
{
check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv);
} else if let ItemKind::Impl(impl_) = item.kind {
if !item.span.from_expansion() {
report_extra_impl_lifetimes(cx, impl_);
}
} else if let ItemKind::Impl(impl_) = item.kind
&& !item.span.from_expansion()
{
report_extra_impl_lifetimes(cx, impl_);
}
}
@ -300,8 +300,8 @@ fn could_use_elision<'tcx>(
let input_lts = input_visitor.lts;
let output_lts = output_visitor.lts;
if let Some(trait_sig) = trait_sig
&& non_elidable_self_type(cx, func, trait_sig.first().copied(), msrv)
if let Some(&[trait_sig]) = trait_sig
&& non_elidable_self_type(cx, func, trait_sig, msrv)
{
return None;
}
@ -310,11 +310,11 @@ fn could_use_elision<'tcx>(
let body = cx.tcx.hir_body(body_id);
let first_ident = body.params.first().and_then(|param| param.pat.simple_ident());
if non_elidable_self_type(cx, func, Some(first_ident), msrv) {
if non_elidable_self_type(cx, func, first_ident, msrv) {
return None;
}
let mut checker = BodyLifetimeChecker;
let mut checker = BodyLifetimeChecker::new(cx);
if checker.visit_expr(body.value).is_break() {
return None;
}
@ -384,8 +384,8 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet<LocalDefI
}
// elision doesn't work for explicit self types before Rust 1.81, see rust-lang/rust#69064
fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Option<Ident>>, msrv: Msrv) -> bool {
if let Some(Some(ident)) = ident
fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>, msrv: Msrv) -> bool {
if let Some(ident) = ident
&& ident.name == kw::SelfLower
&& !func.implicit_self.has_implicit_self()
&& let Some(self_ty) = func.inputs.first()
@ -911,10 +911,23 @@ fn elision_suggestions(
Some(suggestions)
}
struct BodyLifetimeChecker;
struct BodyLifetimeChecker<'tcx> {
tcx: TyCtxt<'tcx>,
}
impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
impl<'tcx> BodyLifetimeChecker<'tcx> {
fn new(cx: &LateContext<'tcx>) -> Self {
Self { tcx: cx.tcx }
}
}
impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker<'tcx> {
type Result = ControlFlow<()>;
type NestedFilter = middle_nested_filter::OnlyBodies;
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}
// for lifetimes as parameters of generics
fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) -> ControlFlow<()> {
if !lifetime.is_anonymous() && lifetime.ident.name != kw::StaticLifetime {

View file

@ -387,12 +387,11 @@ impl LiteralDigitGrouping {
let first = groups.next().expect("At least one group");
if radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal {
if let Some(second_size) = groups.next() {
if !groups.all(|i| i == second_size) || first > second_size {
return Err(WarningType::UnusualByteGroupings);
}
}
if (radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal)
&& let Some(second_size) = groups.next()
&& (!groups.all(|i| i == second_size) || first > second_size)
{
return Err(WarningType::UnusualByteGroupings);
}
if let Some(second) = groups.next() {

View file

@ -45,15 +45,14 @@ fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, spans: &[(Span, Option<Strin
let spans = spans
.iter()
.filter_map(|(span, name)| {
if let Some(name) = name {
if let Some(name) = name
// We need to check that the name is a local.
if !mir
&& !mir
.var_debug_info
.iter()
.any(|local| !local.source_info.span.from_expansion() && local.name.as_str() == name)
{
return None;
}
{
return None;
}
Some(*span)
})

View file

@ -0,0 +1,141 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{eq_expr_value, higher, path_to_local_id};
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::{Expr, ExprKind, LangItem, Node, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::{Span, sym};
use super::CHAR_INDICES_AS_BYTE_INDICES;
// The list of `str` methods we want to lint that have a `usize` argument representing a byte index.
// Note: `String` also has methods that work with byte indices,
// but they all take `&mut self` and aren't worth considering since the user couldn't have called
// them while the chars iterator is live anyway.
const BYTE_INDEX_METHODS: &[&str] = &[
"is_char_boundary",
"floor_char_boundary",
"ceil_char_boundary",
"get",
"index",
"index_mut",
"get_mut",
"get_unchecked",
"get_unchecked_mut",
"slice_unchecked",
"slice_mut_unchecked",
"split_at",
"split_at_mut",
"split_at_checked",
"split_at_mut_checked",
];
const CONTINUE: ControlFlow<!, ()> = ControlFlow::Continue(());
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, iterable: &Expr<'_>, body: &'tcx Expr<'tcx>) {
if let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = iterable.kind
&& let Some(method_id) = cx.typeck_results().type_dependent_def_id(iterable.hir_id)
&& cx.tcx.is_diagnostic_item(sym::enumerate_method, method_id)
&& let ExprKind::MethodCall(_, chars_recv, _, chars_span) = enumerate_recv.kind
&& let Some(method_id) = cx.typeck_results().type_dependent_def_id(enumerate_recv.hir_id)
&& cx.tcx.is_diagnostic_item(sym::str_chars, method_id)
{
if let PatKind::Tuple([pat, _], _) = pat.kind
&& let PatKind::Binding(_, binding_id, ..) = pat.kind
{
// Destructured iterator element `(idx, _)`, look for uses of the binding
for_each_expr(cx, body, |expr| {
if path_to_local_id(expr, binding_id) {
check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv);
}
CONTINUE
});
} else if let PatKind::Binding(_, binding_id, ..) = pat.kind {
// Bound as a tuple, look for `tup.0`
for_each_expr(cx, body, |expr| {
if let ExprKind::Field(e, field) = expr.kind
&& path_to_local_id(e, binding_id)
&& field.name == sym::integer(0)
{
check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv);
}
CONTINUE
});
}
}
}
fn check_index_usage<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
pat: &Pat<'_>,
enumerate_span: Span,
chars_span: Span,
chars_recv: &Expr<'_>,
) {
let Some(parent_expr) = index_consumed_at(cx, expr) else {
return;
};
let is_string_like = |ty: Ty<'_>| ty.is_str() || is_type_lang_item(cx, ty, LangItem::String);
let message = match parent_expr.kind {
ExprKind::MethodCall(segment, recv, ..)
// We currently only lint `str` methods (which `String` can deref to), so a `.is_str()` check is sufficient here
// (contrary to the `ExprKind::Index` case which needs to handle both with `is_string_like` because `String` implements
// `Index` directly and no deref to `str` would happen in that case).
if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_str()
&& BYTE_INDEX_METHODS.contains(&segment.ident.name.as_str())
&& eq_expr_value(cx, chars_recv, recv) =>
{
"passing a character position to a method that expects a byte index"
},
ExprKind::Index(target, ..)
if is_string_like(cx.typeck_results().expr_ty_adjusted(target).peel_refs())
&& eq_expr_value(cx, chars_recv, target) =>
{
"indexing into a string with a character position where a byte index is expected"
},
_ => return,
};
span_lint_hir_and_then(
cx,
CHAR_INDICES_AS_BYTE_INDICES,
expr.hir_id,
expr.span,
message,
|diag| {
diag.note("a character can take up more than one byte, so they are not interchangeable")
.span_note(
MultiSpan::from_spans(vec![pat.span, enumerate_span]),
"position comes from the enumerate iterator",
)
.span_suggestion_verbose(
chars_span.to(enumerate_span),
"consider using `.char_indices()` instead",
"char_indices()",
Applicability::MaybeIncorrect,
);
},
);
}
/// Returns the expression which ultimately consumes the index.
/// This is usually the parent expression, i.e. `.split_at(idx)` for `idx`,
/// but for `.get(..idx)` we want to consider the method call the consuming expression,
/// which requires skipping past the range expression.
fn index_consumed_at<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
match node {
Node::Expr(expr) if higher::Range::hir(expr).is_some() => {},
Node::ExprField(_) => {},
Node::Expr(expr) => return Some(expr),
_ => break,
}
}
None
}

View file

@ -2,6 +2,7 @@ use super::EXPLICIT_ITER_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sym;
use clippy_utils::ty::{
implements_trait, implements_trait_with_env, is_copy, is_type_lang_item, make_normalized_projection,
make_normalized_projection_with_regions, normalize_with_regions,
@ -11,7 +12,6 @@ use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, EarlyBinder, Ty};
use rustc_span::sym;
pub(super) fn check(
cx: &LateContext<'_>,
@ -119,7 +119,7 @@ fn is_ref_iterable<'tcx>(
&& let typing_env = ty::TypingEnv::non_body_analysis(cx.tcx, fn_id)
&& implements_trait_with_env(cx.tcx, typing_env, req_self_ty, trait_id, Some(fn_id), &[])
&& let Some(into_iter_ty) =
make_normalized_projection_with_regions(cx.tcx, typing_env, trait_id, sym!(IntoIter), [req_self_ty])
make_normalized_projection_with_regions(cx.tcx, typing_env, trait_id, sym::IntoIter, [req_self_ty])
&& let req_res_ty = normalize_with_regions(cx.tcx, typing_env, req_res_ty)
&& into_iter_ty == req_res_ty
{
@ -152,7 +152,7 @@ fn is_ref_iterable<'tcx>(
// Using by value won't consume anything
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [self_ty])
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty])
&& ty == res_ty
{
return Some((AdjustKind::None, self_ty));
@ -169,7 +169,7 @@ fn is_ref_iterable<'tcx>(
};
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [self_ty])
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty])
&& ty == res_ty
{
return Some((AdjustKind::reborrow(mutbl), self_ty));
@ -183,7 +183,7 @@ fn is_ref_iterable<'tcx>(
let self_ty = Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, self_ty, mutbl);
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [self_ty])
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty])
&& ty == res_ty
{
return Some((AdjustKind::borrow(mutbl), self_ty));
@ -206,7 +206,7 @@ fn is_ref_iterable<'tcx>(
&& target != self_ty
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [target])
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target])
&& ty == res_ty
{
Some((AdjustKind::auto_reborrow(mutbl), target))
@ -224,7 +224,7 @@ fn is_ref_iterable<'tcx>(
if is_copy(cx, target)
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [target])
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target])
&& ty == res_ty
{
Some((AdjustKind::Deref, target))
@ -242,7 +242,7 @@ fn is_ref_iterable<'tcx>(
if self_ty.is_ref()
&& implements_trait(cx, target, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [target])
make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target])
&& ty == res_ty
{
Some((AdjustKind::auto_borrow(mutbl), target))

View file

@ -13,45 +13,45 @@ use rustc_span::sym;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
let pat_span = pat.span;
if let PatKind::Tuple(pat, _) = pat.kind {
if pat.len() == 2 {
let arg_span = arg.span;
let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
(key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl),
(_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not),
_ => return,
},
if let PatKind::Tuple(pat, _) = pat.kind
&& pat.len() == 2
{
let arg_span = arg.span;
let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
(key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl),
(_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not),
_ => return,
};
let mutbl = match mutbl {
Mutability::Not => "",
Mutability::Mut => "_mut",
};
let arg = match arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr,
_ => arg,
};
},
_ => return,
};
let mutbl = match mutbl {
Mutability::Not => "",
Mutability::Mut => "_mut",
};
let arg = match arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr,
_ => arg,
};
if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
span_lint_and_then(
cx,
FOR_KV_MAP,
arg_span,
format!("you seem to want to iterate on a map's {kind}s"),
|diag| {
let map = sugg::Sugg::hir(cx, arg, "map");
diag.multipart_suggestion(
"use the corresponding method",
vec![
(pat_span, snippet(cx, new_pat_span, kind).into_owned()),
(arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_par())),
],
Applicability::MachineApplicable,
);
},
);
}
if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) {
span_lint_and_then(
cx,
FOR_KV_MAP,
arg_span,
format!("you seem to want to iterate on a map's {kind}s"),
|diag| {
let map = sugg::Sugg::hir(cx, arg, "map");
diag.multipart_suggestion(
"use the corresponding method",
vec![
(pat_span, snippet(cx, new_pat_span, kind).into_owned()),
(arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_paren())),
],
Applicability::MachineApplicable,
);
},
);
}
}
}

View file

@ -3,6 +3,7 @@ use super::utils::make_iterator_snippet;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::implements_trait;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -35,6 +36,7 @@ pub(super) fn check<'tcx>(
&& let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind
&& is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome)
&& path_res(cx, inner_ret) == Res::Local(binding_id)
&& !contains_return_break_continue_macro(cond)
&& let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr)
{
let mut applicability = Applicability::MachineApplicable;

View file

@ -28,37 +28,37 @@ pub(super) fn check<'tcx>(
end: Some(end),
limits,
}) = higher::Range::hir(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
let mut starts = vec![Start {
id: canonical_id,
kind: StartKind::Range,
}];
&& let PatKind::Binding(_, canonical_id, _, _) = pat.kind
{
let mut starts = vec![Start {
id: canonical_id,
kind: StartKind::Range,
}];
// This is one of few ways to return different iterators
// derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
let mut iter_a = None;
let mut iter_b = None;
// This is one of few ways to return different iterators
// derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
let mut iter_a = None;
let mut iter_b = None;
if let ExprKind::Block(block, _) = body.kind {
if let Some(loop_counters) = get_loop_counters(cx, block, expr) {
starts.extend(loop_counters);
}
iter_a = Some(get_assignments(block, &starts));
} else {
iter_b = Some(get_assignment(body));
if let ExprKind::Block(block, _) = body.kind {
if let Some(loop_counters) = get_loop_counters(cx, block, expr) {
starts.extend(loop_counters);
}
iter_a = Some(get_assignments(block, &starts));
} else {
iter_b = Some(get_assignment(body));
}
let assignments = iter_a.into_iter().flatten().chain(iter_b);
let assignments = iter_a.into_iter().flatten().chain(iter_b);
let big_sugg = assignments
// The only statements in the for loops can be indexed assignments from
// indexed retrievals (except increments of loop counters).
.map(|o| {
o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs);
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
let big_sugg = assignments
// The only statements in the for loops can be indexed assignments from
// indexed retrievals (except increments of loop counters).
.map(|o| {
o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs);
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind
&& let ExprKind::Index(base_right, idx_right, _) = rhs.kind
&& let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left))
&& get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some()
@ -68,42 +68,41 @@ pub(super) fn check<'tcx>(
&& !local_used_in(cx, canonical_id, base_right)
// Source and destination must be different
&& path_to_local(base_left) != path_to_local(base_right)
{
Some((
ty,
IndexExpr {
base: base_left,
idx: start_left,
idx_offset: offset_left,
},
IndexExpr {
base: base_right,
idx: start_right,
idx_offset: offset_right,
},
))
} else {
None
}
})
{
Some((
ty,
IndexExpr {
base: base_left,
idx: start_left,
idx_offset: offset_left,
},
IndexExpr {
base: base_right,
idx: start_right,
idx_offset: offset_right,
},
))
} else {
None
}
})
.map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src)))
.collect::<Option<Vec<_>>>()
.filter(|v| !v.is_empty())
.map(|v| v.join("\n "));
})
.map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src)))
.collect::<Option<Vec<_>>>()
.filter(|v| !v.is_empty())
.map(|v| v.join("\n "));
if let Some(big_sugg) = big_sugg {
span_lint_and_sugg(
cx,
MANUAL_MEMCPY,
expr.span,
"it looks like you're manually copying between slices",
"try replacing the loop by",
big_sugg,
Applicability::Unspecified,
);
return true;
}
if let Some(big_sugg) = big_sugg {
span_lint_and_sugg(
cx,
MANUAL_MEMCPY,
expr.span,
"it looks like you're manually copying between slices",
"try replacing the loop by",
big_sugg,
Applicability::Unspecified,
);
return true;
}
}
false
@ -184,7 +183,12 @@ fn build_manual_memcpy_suggestion<'tcx>(
{
dst_base_str
} else {
format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into()
format!(
"{dst_base_str}[{}..{}]",
dst_offset.maybe_paren(),
dst_limit.maybe_paren()
)
.into()
};
let method_str = if is_copy(cx, elem_ty) {
@ -196,7 +200,12 @@ fn build_manual_memcpy_suggestion<'tcx>(
let src = if is_array_length_equal_to_range(cx, start, end, src.base) {
src_base_str
} else {
format!("{src_base_str}[{}..{}]", src_offset.maybe_par(), src_limit.maybe_par()).into()
format!(
"{src_base_str}[{}..{}]",
src_offset.maybe_paren(),
src_limit.maybe_paren()
)
.into()
};
format!("{dst}.{method_str}(&{src});")

View file

@ -81,15 +81,15 @@ fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>,
}
fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, loop_span: Span) {
if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind {
if let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind {
let offending_arg = args
.iter()
.find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span));
if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind
&& let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind
{
let offending_arg = args
.iter()
.find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span));
if let Some(offending_arg) = offending_arg {
report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span);
}
if let Some(offending_arg) = offending_arg {
report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span);
}
}
}

View file

@ -1,3 +1,4 @@
mod char_indices_as_byte_indices;
mod empty_loop;
mod explicit_counter_loop;
mod explicit_into_iter_loop;
@ -740,6 +741,49 @@ declare_clippy_lint! {
"manually filling a slice with a value"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of a character position yielded by `.chars().enumerate()` in a context where a **byte index** is expected,
/// such as an argument to a specific `str` method or indexing into a `str` or `String`.
///
/// ### Why is this bad?
/// A character (more specifically, a Unicode scalar value) that is yielded by `str::chars` can take up multiple bytes,
/// so a character position does not necessarily have the same byte index at which the character is stored.
/// Thus, using the character position where a byte index is expected can unexpectedly return wrong values
/// or panic when the string consists of multibyte characters.
///
/// For example, the character `a` in `äa` is stored at byte index 2 but has the character position 1.
/// Using the character position 1 to index into the string will lead to a panic as it is in the middle of the first character.
///
/// Instead of `.chars().enumerate()`, the correct iterator to use is `.char_indices()`, which yields byte indices.
///
/// This pattern is technically fine if the strings are known to only use the ASCII subset,
/// though in those cases it would be better to use `bytes()` directly to make the intent clearer,
/// but there is also no downside to just using `.char_indices()` directly and supporting non-ASCII strings.
///
/// You may also want to read the [chapter on strings in the Rust Book](https://doc.rust-lang.org/book/ch08-02-strings.html)
/// which goes into this in more detail.
///
/// ### Example
/// ```no_run
/// # let s = "...";
/// for (idx, c) in s.chars().enumerate() {
/// let _ = s[idx..]; // ⚠️ Panics for strings consisting of multibyte characters
/// }
/// ```
/// Use instead:
/// ```no_run
/// # let s = "...";
/// for (idx, c) in s.char_indices() {
/// let _ = s[idx..];
/// }
/// ```
#[clippy::version = "1.83.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"
}
pub struct Loops {
msrv: Msrv,
enforce_iter_loop_reborrow: bool,
@ -777,6 +821,7 @@ impl_lint_pass!(Loops => [
UNUSED_ENUMERATE_INDEX,
INFINITE_LOOP,
MANUAL_SLICE_FILL,
CHAR_INDICES_AS_BYTE_INDICES,
]);
impl<'tcx> LateLintPass<'tcx> for Loops {
@ -860,6 +905,7 @@ impl Loops {
manual_flatten::check(cx, pat, arg, body, span, self.msrv);
manual_find::check(cx, pat, arg, body, span, expr);
unused_enumerate_index::check(cx, pat, arg, body);
char_indices_as_byte_indices::check(cx, pat, arg, body);
}
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {

View file

@ -82,14 +82,14 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if bk == ty::BorrowKind::Mutable {
if let PlaceBase::Local(id) = cmt.place.base {
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id));
}
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id));
}
if bk == ty::BorrowKind::Mutable
&& let PlaceBase::Local(id) = cmt.place.base
{
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id));
}
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id));
}
}
}

View file

@ -31,155 +31,154 @@ pub(super) fn check<'tcx>(
ref end,
limits,
}) = higher::Range::hir(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
let mut visitor = VarVisitor {
cx,
var: canonical_id,
indexed_mut: FxHashSet::default(),
indexed_indirectly: FxHashMap::default(),
indexed_directly: FxIndexMap::default(),
referenced: FxHashSet::default(),
nonindex: false,
prefer_mutable: false,
&& let PatKind::Binding(_, canonical_id, ident, _) = pat.kind
{
let mut visitor = VarVisitor {
cx,
var: canonical_id,
indexed_mut: FxHashSet::default(),
indexed_indirectly: FxHashMap::default(),
indexed_directly: FxIndexMap::default(),
referenced: FxHashSet::default(),
nonindex: false,
prefer_mutable: false,
};
walk_expr(&mut visitor, body);
// linting condition: we only indexed one variable, and indexed it directly
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
let (indexed, (indexed_extent, indexed_ty)) = visitor
.indexed_directly
.into_iter()
.next()
.expect("already checked that we have exactly 1 element");
// ensure that the indexed variable was declared before the loop, see #601
if let Some(indexed_extent) = indexed_extent {
let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id);
let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
return;
}
}
// don't lint if the container that is indexed does not have .iter() method
let has_iter = has_iter_method(cx, indexed_ty);
if has_iter.is_none() {
return;
}
// don't lint if the container that is indexed into is also used without
// indexing
if visitor.referenced.contains(&indexed) {
return;
}
let starts_at_zero = is_integer_const(cx, start, 0);
let skip = if starts_at_zero {
String::new()
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start, cx) {
return;
} else {
format!(".skip({})", snippet(cx, start.span, ".."))
};
walk_expr(&mut visitor, body);
// linting condition: we only indexed one variable, and indexed it directly
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
let (indexed, (indexed_extent, indexed_ty)) = visitor
.indexed_directly
.into_iter()
.next()
.expect("already checked that we have exactly 1 element");
let mut end_is_start_plus_val = false;
// ensure that the indexed variable was declared before the loop, see #601
if let Some(indexed_extent) = indexed_extent {
let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id);
let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
return;
let take = if let Some(end) = *end {
let mut take_expr = end;
if let ExprKind::Binary(ref op, left, right) = end.kind
&& op.node == BinOpKind::Add
{
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
if start_equal_left {
take_expr = right;
} else if start_equal_right {
take_expr = left;
}
end_is_start_plus_val = start_equal_left | start_equal_right;
}
// don't lint if the container that is indexed does not have .iter() method
let has_iter = has_iter_method(cx, indexed_ty);
if has_iter.is_none() {
return;
}
// don't lint if the container that is indexed into is also used without
// indexing
if visitor.referenced.contains(&indexed) {
return;
}
let starts_at_zero = is_integer_const(cx, start, 0);
let skip = if starts_at_zero {
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
String::new()
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start, cx) {
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr, cx) {
return;
} else {
format!(".skip({})", snippet(cx, start.span, ".."))
};
let mut end_is_start_plus_val = false;
let take = if let Some(end) = *end {
let mut take_expr = end;
if let ExprKind::Binary(ref op, left, right) = end.kind {
if op.node == BinOpKind::Add {
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
if start_equal_left {
take_expr = right;
} else if start_equal_right {
take_expr = left;
}
end_is_start_plus_val = start_equal_left | start_equal_right;
}
}
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
String::new()
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr, cx) {
return;
} else {
match limits {
ast::RangeLimits::Closed => {
let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
format!(".take({})", take_expr + sugg::ONE)
},
ast::RangeLimits::HalfOpen => {
format!(".take({})", snippet(cx, take_expr.span, ".."))
},
}
}
} else {
String::new()
};
let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
("mut ", "iter_mut")
} else {
("", "iter")
};
let take_is_empty = take.is_empty();
let mut method_1 = take;
let mut method_2 = skip;
if end_is_start_plus_val {
mem::swap(&mut method_1, &mut method_2);
}
if visitor.nonindex {
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator and enumerate()",
vec![
(pat.span, format!("({}, <item>)", ident.name)),
(
arg.span,
format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
),
],
Applicability::HasPlaceholders,
);
match limits {
ast::RangeLimits::Closed => {
let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
format!(".take({})", take_expr + sugg::ONE)
},
);
} else {
let repl = if starts_at_zero && take_is_empty {
format!("&{ref_mut}{indexed}")
} else {
format!("{indexed}.{method}(){method_1}{method_2}")
};
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is only used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator",
vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
Applicability::HasPlaceholders,
);
ast::RangeLimits::HalfOpen => {
format!(".take({})", snippet(cx, take_expr.span, ".."))
},
);
}
}
} else {
String::new()
};
let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
("mut ", "iter_mut")
} else {
("", "iter")
};
let take_is_empty = take.is_empty();
let mut method_1 = take;
let mut method_2 = skip;
if end_is_start_plus_val {
mem::swap(&mut method_1, &mut method_2);
}
if visitor.nonindex {
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator and enumerate()",
vec![
(pat.span, format!("({}, <item>)", ident.name)),
(
arg.span,
format!("{indexed}.{method}().enumerate(){method_1}{method_2}"),
),
],
Applicability::HasPlaceholders,
);
},
);
} else {
let repl = if starts_at_zero && take_is_empty {
format!("&{ref_mut}{indexed}")
} else {
format!("{indexed}.{method}(){method_1}{method_2}")
};
span_lint_and_then(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
format!("the loop variable `{}` is only used to index `{indexed}`", ident.name),
|diag| {
diag.multipart_suggestion(
"consider using an iterator",
vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
Applicability::HasPlaceholders,
);
},
);
}
}
}
@ -346,10 +345,10 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> {
for expr in args {
let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
self.prefer_mutable = false;
if let ty::Ref(_, _, mutbl) = *ty.kind() {
if mutbl == Mutability::Mut {
self.prefer_mutable = true;
}
if let ty::Ref(_, _, mutbl) = *ty.kind()
&& mutbl == Mutability::Mut
{
self.prefer_mutable = true;
}
self.visit_expr(expr);
}
@ -361,10 +360,10 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> {
iter::once(receiver).chain(args.iter()),
) {
self.prefer_mutable = false;
if let ty::Ref(_, _, mutbl) = *ty.kind() {
if mutbl == Mutability::Mut {
self.prefer_mutable = true;
}
if let ty::Ref(_, _, mutbl) = *ty.kind()
&& mutbl == Mutability::Mut
{
self.prefer_mutable = true;
}
self.visit_expr(expr);
}

View file

@ -244,10 +244,10 @@ fn never_loop_expr<'tcx>(
});
combine_seq(first, || {
// checks if break targets a block instead of a loop
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind {
if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) {
*reachable = true;
}
if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind
&& let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t)
{
*reachable = true;
}
NeverLoopResult::Diverging
})

View file

@ -3,9 +3,7 @@ use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_loc
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{Visitor, walk_expr, walk_local};
use rustc_hir::{
AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind
};
use rustc_hir::{AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
@ -265,7 +263,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
if impls_iterator {
format!(
"{}",
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren()
)
} else {
// (&x).into_iter() ==> x.iter()
@ -283,12 +281,12 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
};
format!(
"{}.{method_name}()",
sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(),
sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_paren(),
)
},
_ => format!(
"{}.into_iter()",
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren()
),
}
}

View file

@ -5,7 +5,8 @@ use itertools::Itertools;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt};
use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
use rustc_middle::lint::LevelAndSource;
use rustc_session::impl_lint_pass;
use rustc_span::{Span, SyntaxContext, sym};
use std::collections::BTreeMap;
@ -249,6 +250,20 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
})
.flatten()
.copied()
.inspect(|&unsafe_block| {
if let LevelAndSource {
level: Level::Expect,
lint_id: Some(id),
..
} = cx.tcx.lint_level_at_node(MACRO_METAVARS_IN_UNSAFE, unsafe_block)
{
// Since we're going to deduplicate expanded unsafe blocks by its enclosing macro definition soon,
// which would lead to unfulfilled `#[expect()]`s in all other unsafe blocks that are filtered out
// except for the one we emit the warning at, we must manually fulfill the lint
// for all unsafe blocks here.
cx.fulfill_expectation(id);
}
})
.map(|id| {
// Remove the syntax context to hide "in this macro invocation" in the diagnostic.
// The invocation doesn't matter. Also we want to dedupe by the unsafe block and not by anything

View file

@ -0,0 +1,152 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::If;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::HasSession as _;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::impl_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detects patterns like `if a > b { a - b } else { b - a }` and suggests using `a.abs_diff(b)`.
///
/// ### Why is this bad?
/// Using `abs_diff` is shorter, more readable, and avoids control flow.
///
/// ### Examples
/// ```no_run
/// # let (a, b) = (5_usize, 3_usize);
/// if a > b {
/// a - b
/// } else {
/// b - a
/// }
/// # ;
/// ```
/// Use instead:
/// ```no_run
/// # let (a, b) = (5_usize, 3_usize);
/// a.abs_diff(b)
/// # ;
/// ```
#[clippy::version = "1.86.0"]
pub MANUAL_ABS_DIFF,
complexity,
"using an if-else pattern instead of `abs_diff`"
}
impl_lint_pass!(ManualAbsDiff => [MANUAL_ABS_DIFF]);
pub struct ManualAbsDiff {
msrv: Msrv,
}
impl ManualAbsDiff {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !expr.span.from_expansion()
&& let Some(if_expr) = If::hir(expr)
&& let Some(r#else) = if_expr.r#else
&& let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind
&& let (BinOpKind::Gt | BinOpKind::Ge, mut a, mut b) | (BinOpKind::Lt | BinOpKind::Le, mut b, mut a) =
(op.node, rhs, lhs)
&& let Some(ty) = self.are_ty_eligible(cx, a, b)
&& is_sub_expr(cx, if_expr.then, a, b, ty)
&& is_sub_expr(cx, r#else, b, a, ty)
{
span_lint_and_then(
cx,
MANUAL_ABS_DIFF,
expr.span,
"manual absolute difference pattern without using `abs_diff`",
|diag| {
if is_unsuffixed_numeral_lit(a) && !is_unsuffixed_numeral_lit(b) {
(a, b) = (b, a);
}
let applicability = {
let source_map = cx.sess().source_map();
if span_contains_comment(source_map, if_expr.then.span)
|| span_contains_comment(source_map, r#else.span)
{
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
}
};
let sugg = format!(
"{}.abs_diff({})",
Sugg::hir(cx, a, "..").maybe_paren(),
Sugg::hir(cx, b, "..")
);
diag.span_suggestion(expr.span, "replace with `abs_diff`", sugg, applicability);
},
);
}
}
}
impl ManualAbsDiff {
/// Returns a type if `a` and `b` are both of it, and this lint can be applied to that
/// type (currently, any primitive int, or a `Duration`)
fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<Ty<'tcx>> {
let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF);
let is_duration =
|ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF);
let a_ty = cx.typeck_results().expr_ty(a).peel_refs();
(a_ty == cx.typeck_results().expr_ty(b).peel_refs() && (is_int(a_ty) || is_duration(a_ty))).then_some(a_ty)
}
}
/// Checks if the given expression is a subtraction operation between two expected expressions,
/// i.e. if `expr` is `{expected_a} - {expected_b}`.
///
/// If `expected_ty` is a signed primitive integer, this function will only return `Some` if the
/// subtraction expr is wrapped in a cast to the equivalent unsigned int.
fn is_sub_expr(
cx: &LateContext<'_>,
expr: &Expr<'_>,
expected_a: &Expr<'_>,
expected_b: &Expr<'_>,
expected_ty: Ty<'_>,
) -> bool {
let expr = peel_blocks(expr).kind;
if let ty::Int(ty) = expected_ty.kind() {
let unsigned = Ty::new_uint(cx.tcx, ty.to_unsigned());
return if let ExprKind::Cast(expr, cast_ty) = expr
&& cx.typeck_results().node_type(cast_ty.hir_id) == unsigned
{
is_sub_expr(cx, expr, expected_a, expected_b, unsigned)
} else {
false
};
}
if let ExprKind::Binary(op, a, b) = expr
&& let BinOpKind::Sub = op.node
&& eq_expr_value(cx, a, expected_a)
&& eq_expr_value(cx, b, expected_b)
{
true
} else {
false
}
}
fn is_unsuffixed_numeral_lit(expr: &Expr<'_>) -> bool {
matches!(expr.kind, ExprKind::Lit(lit) if lit.node.is_numeric() && lit.node.is_unsuffixed())
}

View file

@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert {
ExprKind::Unary(UnOp::Not, e) => (e, ""),
_ => (cond, "!"),
};
let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par();
let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_paren();
let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" };
let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}");
// we show to the user the suggestion without the comments, but when applying the fix, include the

View file

@ -181,7 +181,7 @@ fn maybe_emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggest
make_assignment,
hir_with_ignore_attr,
} = suggestion;
let input = Sugg::hir(cx, input, "..").maybe_par();
let input = Sugg::hir(cx, input, "..").maybe_paren();
let min = Sugg::hir(cx, min, "..");
let max = Sugg::hir(cx, max, "..");
let semicolon = if make_assignment.is_some() { ";" } else { "" };

View file

@ -1,8 +1,9 @@
use clippy_utils::SpanlessEq;
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::sugg::{Sugg, has_enclosing_paren};
use clippy_utils::{SpanlessEq, sym};
use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp};
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
@ -11,9 +12,6 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self};
use rustc_session::impl_lint_pass;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::Symbol;
use clippy_config::Conf;
declare_clippy_lint! {
/// ### What it does
@ -141,8 +139,7 @@ fn check_int_ty_and_feature(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr);
match expr_ty.peel_refs().kind() {
ty::Uint(_) => true,
ty::Int(_) => cx.tcx.features().enabled(Symbol::intern("int_roundings")),
ty::Int(_) => cx.tcx.features().enabled(sym::int_roundings),
_ => false,
}
}
@ -167,7 +164,7 @@ fn build_suggestion(
rhs: &Expr<'_>,
applicability: &mut Applicability,
) {
let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_par();
let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_paren();
let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric()
&& matches!(
lhs.kind,

View file

@ -148,7 +148,7 @@ fn check_is_ascii(
};
let default_snip = "..";
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_paren();
let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))];
if let Some((ty_span, ty)) = ty_sugg {
suggestion.push((ty_span, format!("{recv}: {ty}")));

View file

@ -1,13 +1,14 @@
use clippy_utils::SpanlessEq;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use rustc_ast::LitKind;
use rustc_data_structures::packed::Pu128;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::ty_from_hir_ty;
use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Uint;
use rustc_session::declare_lint_pass;
use rustc_middle::ty;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
@ -33,112 +34,111 @@ declare_clippy_lint! {
"manually reimplementing `is_power_of_two`"
}
declare_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
pub struct ManualIsPowerOfTwo {
msrv: Msrv,
}
impl_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
impl ManualIsPowerOfTwo {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
fn build_sugg(&self, cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
if is_in_const_context(cx) && !self.msrv.meets(cx, msrvs::CONST_IS_POWER_OF_TWO) {
return;
}
impl LateLintPass<'_> for ManualIsPowerOfTwo {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let mut applicability = Applicability::MachineApplicable;
let snippet = Sugg::hir_with_applicability(cx, receiver, "_", &mut applicability);
if let ExprKind::Binary(bin_op, left, right) = expr.kind
&& bin_op.node == BinOpKind::Eq
span_lint_and_sugg(
cx,
MANUAL_IS_POWER_OF_TWO,
expr.span,
"manually reimplementing `is_power_of_two`",
"consider using `.is_power_of_two()`",
format!("{}.is_power_of_two()", snippet.maybe_paren()),
applicability,
);
}
}
impl<'tcx> LateLintPass<'tcx> for ManualIsPowerOfTwo {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if !expr.span.from_expansion()
&& let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq)
{
// a.count_ones() == 1
if let ExprKind::MethodCall(method_name, receiver, [], _) = left.kind
&& method_name.ident.as_str() == "count_ones"
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
&& check_lit(right, 1)
if let Some(a) = count_ones_receiver(cx, lhs)
&& is_integer_literal(rhs, 1)
{
build_sugg(cx, expr, receiver, &mut applicability);
}
// 1 == a.count_ones()
if let ExprKind::MethodCall(method_name, receiver, [], _) = right.kind
&& method_name.ident.as_str() == "count_ones"
&& let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind()
&& check_lit(left, 1)
self.build_sugg(cx, expr, a);
} else if let Some(a) = count_ones_receiver(cx, rhs)
&& is_integer_literal(lhs, 1)
{
build_sugg(cx, expr, receiver, &mut applicability);
}
// a & (a - 1) == 0
if let ExprKind::Binary(op1, left1, right1) = left.kind
&& op1.node == BinOpKind::BitAnd
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
&& op2.node == BinOpKind::Sub
&& check_eq_expr(cx, left1, left2)
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
&& check_lit(right2, 1)
&& check_lit(right, 0)
self.build_sugg(cx, expr, a);
} else if is_integer_literal(rhs, 0)
&& let Some(a) = is_and_minus_one(cx, lhs)
{
build_sugg(cx, expr, left1, &mut applicability);
}
// (a - 1) & a == 0;
if let ExprKind::Binary(op1, left1, right1) = left.kind
&& op1.node == BinOpKind::BitAnd
&& let ExprKind::Binary(op2, left2, right2) = left1.kind
&& op2.node == BinOpKind::Sub
&& check_eq_expr(cx, right1, left2)
&& let &Uint(_) = cx.typeck_results().expr_ty(right1).kind()
&& check_lit(right2, 1)
&& check_lit(right, 0)
self.build_sugg(cx, expr, a);
} else if is_integer_literal(lhs, 0)
&& let Some(a) = is_and_minus_one(cx, rhs)
{
build_sugg(cx, expr, right1, &mut applicability);
}
// 0 == a & (a - 1);
if let ExprKind::Binary(op1, left1, right1) = right.kind
&& op1.node == BinOpKind::BitAnd
&& let ExprKind::Binary(op2, left2, right2) = right1.kind
&& op2.node == BinOpKind::Sub
&& check_eq_expr(cx, left1, left2)
&& let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
&& check_lit(right2, 1)
&& check_lit(left, 0)
{
build_sugg(cx, expr, left1, &mut applicability);
}
// 0 == (a - 1) & a
if let ExprKind::Binary(op1, left1, right1) = right.kind
&& op1.node == BinOpKind::BitAnd
&& let ExprKind::Binary(op2, left2, right2) = left1.kind
&& op2.node == BinOpKind::Sub
&& check_eq_expr(cx, right1, left2)
&& let &Uint(_) = cx.typeck_results().expr_ty(right1).kind()
&& check_lit(right2, 1)
&& check_lit(left, 0)
{
build_sugg(cx, expr, right1, &mut applicability);
self.build_sugg(cx, expr, a);
}
}
}
}
fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, applicability: &mut Applicability) {
let snippet = snippet_with_applicability(cx, receiver.span, "..", applicability);
span_lint_and_sugg(
cx,
MANUAL_IS_POWER_OF_TWO,
expr.span,
"manually reimplementing `is_power_of_two`",
"consider using `.is_power_of_two()`",
format!("{snippet}.is_power_of_two()"),
*applicability,
);
}
fn check_lit(expr: &Expr<'_>, expected_num: u128) -> bool {
if let ExprKind::Lit(lit) = expr.kind
&& let LitKind::Int(Pu128(num), _) = lit.node
&& num == expected_num
/// Return the unsigned integer receiver of `.count_ones()` or the argument of
/// `<int-type>::count_ones(…)`.
fn count_ones_receiver<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
let (method, ty, receiver) = if let ExprKind::MethodCall(method_name, receiver, [], _) = expr.kind {
(method_name, cx.typeck_results().expr_ty_adjusted(receiver), receiver)
} else if let ExprKind::Call(func, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, func_name)) = func.kind
{
return true;
}
false
(func_name, ty_from_hir_ty(cx, ty), arg)
} else {
return None;
};
(method.ident.as_str() == "count_ones" && matches!(ty.kind(), ty::Uint(_))).then_some(receiver)
}
fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool {
SpanlessEq::new(cx).eq_expr(lhs, rhs)
/// Return `greater` if `smaller == greater - 1`
fn is_one_less<'tcx>(
cx: &LateContext<'tcx>,
greater: &'tcx Expr<'tcx>,
smaller: &Expr<'tcx>,
) -> Option<&'tcx Expr<'tcx>> {
if let Some((lhs, rhs)) = unexpanded_binop_operands(smaller, BinOpKind::Sub)
&& SpanlessEq::new(cx).eq_expr(greater, lhs)
&& is_integer_literal(rhs, 1)
&& matches!(cx.typeck_results().expr_ty_adjusted(greater).kind(), ty::Uint(_))
{
Some(greater)
} else {
None
}
}
/// Return `v` if `expr` is `v & (v - 1)` or `(v - 1) & v`
fn is_and_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
let (lhs, rhs) = unexpanded_binop_operands(expr, BinOpKind::BitAnd)?;
is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs))
}
/// Return the operands of the `expr` binary operation if the operator is `op` and none of the
/// operands come from expansion.
fn unexpanded_binop_operands<'hir>(expr: &Expr<'hir>, op: BinOpKind) -> Option<(&'hir Expr<'hir>, &'hir Expr<'hir>)> {
if let ExprKind::Binary(binop, lhs, rhs) = expr.kind
&& binop.node == op
&& !lhs.span.from_expansion()
&& !rhs.span.from_expansion()
{
Some((lhs, rhs))
} else {
None
}
}

View file

@ -34,7 +34,7 @@ declare_clippy_lint! {
/// _ = opt.as_slice();
/// _ = opt.as_slice();
/// ```
#[clippy::version = "1.85.0"]
#[clippy::version = "1.86.0"]
pub MANUAL_OPTION_AS_SLICE,
complexity,
"manual `Option::as_slice`"

View file

@ -92,10 +92,10 @@ fn check_into_iter(
&& let [filter_params] = filter_body.params
{
if match_map_type(cx, left_expr) {
if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind {
if let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) {
make_span_lint_and_sugg(cx, parent_expr_span, sugg);
}
if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind
&& let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body)
{
make_span_lint_and_sugg(cx, parent_expr_span, sugg);
}
// Cannot lint other cases because `retain` requires two parameters
} else {
@ -196,22 +196,21 @@ fn check_to_owned(
&& let filter_body = cx.tcx.hir_body(closure.body)
&& let [filter_params] = filter_body.params
&& msrv.meets(cx, msrvs::STRING_RETAIN)
&& let hir::PatKind::Ref(pat, _) = filter_params.pat.kind
{
if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind {
make_span_lint_and_sugg(
cx,
parent_expr_span,
format!(
"{}.retain(|{}| {})",
snippet(cx, left_expr.span, ".."),
snippet(cx, pat.span, ".."),
snippet(cx, filter_body.value.span, "..")
),
);
}
// Be conservative now. Do nothing for the `Binding` case.
// TODO: Ideally, we can rewrite the lambda by stripping one level of reference
make_span_lint_and_sugg(
cx,
parent_expr_span,
format!(
"{}.retain(|{}| {})",
snippet(cx, left_expr.span, ".."),
snippet(cx, pat.span, ".."),
snippet(cx, filter_body.value.span, "..")
),
);
}
// Be conservative now. Do nothing for the `Binding` case.
// TODO: Ideally, we can rewrite the lambda by stripping one level of reference
}
fn make_sugg(

View file

@ -101,7 +101,7 @@ impl LateLintPass<'_> for ManualRotate {
(r_shift_dir, r_amount)
};
let mut applicability = Applicability::MachineApplicable;
let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_par();
let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_paren();
span_lint_and_sugg(
cx,
MANUAL_ROTATE,

View file

@ -113,15 +113,14 @@ fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, arg: &Expr<'_>)
&& is_expr_kind_empty_str(&arg.kind)
{
warn_then_suggest(cx, span);
} else if let QPath::Resolved(_, path) = qpath {
} else if let QPath::Resolved(_, path) = qpath
// From::from(...) or TryFrom::try_from(...)
if let [path_seg1, path_seg2] = path.segments
&& is_expr_kind_empty_str(&arg.kind)
&& ((path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from)
|| (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from))
{
warn_then_suggest(cx, span);
}
&& let [path_seg1, path_seg2] = path.segments
&& is_expr_kind_empty_str(&arg.kind)
&& ((path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from)
|| (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from))
{
warn_then_suggest(cx, span);
}
}
}

View file

@ -1,212 +0,0 @@
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::GenericArgKind;
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{expr_type_is_certain, implements_trait};
use clippy_utils::{is_default_equivalent, is_in_const_context, path_res, peel_blocks, span_contains_comment};
declare_clippy_lint! {
/// ### What it does
/// Checks if a `match` or `if let` expression can be simplified using
/// `.unwrap_or_default()`.
///
/// ### Why is this bad?
/// It can be done in one call with `.unwrap_or_default()`.
///
/// ### Example
/// ```no_run
/// let x: Option<String> = Some(String::new());
/// let y: String = match x {
/// Some(v) => v,
/// None => String::new(),
/// };
///
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = if let Some(v) = x {
/// v
/// } else {
/// Vec::new()
/// };
/// ```
/// Use instead:
/// ```no_run
/// let x: Option<String> = Some(String::new());
/// let y: String = x.unwrap_or_default();
///
/// let x: Option<Vec<String>> = Some(Vec::new());
/// let y: Vec<String> = x.unwrap_or_default();
/// ```
#[clippy::version = "1.79.0"]
pub MANUAL_UNWRAP_OR_DEFAULT,
suspicious,
"check if a `match` or `if let` can be simplified with `unwrap_or_default`"
}
declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]);
fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> {
if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind
&& let PatKind::Binding(_, 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.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& (cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id)
|| cx.tcx.lang_items().get(LangItem::ResultOk) == Some(def_id))
{
Some(pat_id)
} else {
None
}
}
fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.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.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id)
{
Some(arm.body)
} else if let PatKind::TupleStruct(QPath::Resolved(_, path), _, _)= arm.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.
&& let Some(def_id) = cx.tcx.opt_parent(def_id)
&& cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id)
{
Some(arm.body)
} else if let PatKind::Wild = arm.pat.kind {
// We consider that the `Some` check will filter it out if it's not right.
Some(arm.body)
} else {
None
}
}
fn get_some_and_none_bodies<'tcx>(
cx: &LateContext<'tcx>,
arm1: &'tcx Arm<'tcx>,
arm2: &'tcx Arm<'tcx>,
) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> {
if let Some(binding_id) = get_some(cx, arm1.pat)
&& let Some(body_none) = get_none(cx, arm2)
{
Some(((arm1.body, binding_id), body_none))
} else if let Some(binding_id) = get_some(cx, arm2.pat)
&& let Some(body_none) = get_none(cx, arm1)
{
Some(((arm2.body, binding_id), body_none))
} else {
None
}
}
#[allow(clippy::needless_pass_by_value)]
fn handle<'tcx>(cx: &LateContext<'tcx>, if_let_or_match: IfLetOrMatch<'tcx>, expr: &'tcx Expr<'tcx>) {
// Get expr_name ("if let" or "match" depending on kind of expression), the condition, the body for
// the some arm, the body for the none arm and the binding id of the some arm
let (expr_name, condition, body_some, body_none, binding_id) = match if_let_or_match {
IfLetOrMatch::Match(condition, [arm1, arm2], MatchSource::Normal | MatchSource::ForLoopDesugar)
// Make sure there are no guards to keep things simple
if arm1.guard.is_none()
&& arm2.guard.is_none()
// Get the some and none bodies and the binding id of the some arm
&& let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2) =>
{
("match", condition, body_some, body_none, binding_id)
},
IfLetOrMatch::IfLet(condition, pat, if_expr, Some(else_expr), _)
if let Some(binding_id) = get_some(cx, pat) =>
{
("if let", condition, if_expr, else_expr, binding_id)
},
_ => {
// All other cases (match with number of arms != 2, if let without else, etc.)
return;
},
};
// We check if the return type of the expression implements Default.
let expr_type = cx.typeck_results().expr_ty(expr);
if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
&& implements_trait(cx, expr_type, default_trait_id, &[])
// We check if the initial condition implements Default.
&& let Some(condition_ty) = cx.typeck_results().expr_ty(condition).walk().nth(1)
&& let GenericArgKind::Type(condition_ty) = condition_ty.unpack()
&& implements_trait(cx, condition_ty, default_trait_id, &[])
// We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`.
&& let ExprKind::Path(QPath::Resolved(_, path)) = peel_blocks(body_some).kind
&& let Res::Local(local_id) = path.res
&& local_id == binding_id
// We now check the `None` arm is calling a method equivalent to `Default::default`.
&& let body_none = peel_blocks(body_none)
&& is_default_equivalent(cx, body_none)
&& let Some(receiver) = Sugg::hir_opt(cx, condition).map(Sugg::maybe_par)
{
// Machine applicable only if there are no comments present
let applicability = if span_contains_comment(cx.sess().source_map(), expr.span) {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
// We now check if the condition is a None variant, in which case we need to specify the type
if path_res(cx, condition)
.opt_def_id()
.is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant())
{
return span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
"replace it with",
format!("{receiver}::<{expr_type}>.unwrap_or_default()"),
applicability,
);
}
// We check if the expression type is still uncertain, in which case we ask the user to specify it
if !expr_type_is_certain(cx, condition) {
return span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
format!("ascribe the type {expr_type} and replace your expression with"),
format!("{receiver}.unwrap_or_default()"),
Applicability::Unspecified,
);
}
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR_DEFAULT,
expr.span,
format!("{expr_name} can be simplified with `.unwrap_or_default()`"),
"replace it with",
format!("{receiver}.unwrap_or_default()"),
applicability,
);
}
}
impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr)
&& !expr.span.from_expansion()
&& !is_in_const_context(cx)
{
handle(cx, if_let_or_match, expr);
}
}
}

View file

@ -101,10 +101,10 @@ fn is_unit_type(ty: Ty<'_>) -> bool {
fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
if let ty::FnDef(id, _) = *ty.kind() {
if let Some(fn_type) = cx.tcx.fn_sig(id).instantiate_identity().no_bound_vars() {
return is_unit_type(fn_type.output());
}
if let ty::FnDef(id, _) = *ty.kind()
&& let Some(fn_type) = cx.tcx.fn_sig(id).instantiate_identity().no_bound_vars()
{
return is_unit_type(fn_type.output());
}
false
}

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