Merge commit '0621446356' into clippy-subtree-update
This commit is contained in:
parent
ed892e72dd
commit
ff428d91c2
746 changed files with 13925 additions and 7188 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}"))
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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, ".."))
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
clippy_lints/src/casts/manual_dangling_ptr.rs
Normal file
82
clippy_lints/src/casts/manual_dangling_ptr.rs
Normal 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
|
||||
})
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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![]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
]}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
141
clippy_lints/src/loops/char_indices_as_byte_indices.rs
Normal file
141
clippy_lints/src/loops/char_indices_as_byte_indices.rs
Normal 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
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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});")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<'_>) {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
152
clippy_lints/src/manual_abs_diff.rs
Normal file
152
clippy_lints/src/manual_abs_diff.rs
Normal 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())
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 { "" };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}")));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`"
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue