Auto merge of #11750 - Alexendoo:let-chains, r=flip1995

Replace if_chain with let chains

Closes #9353

Let chains are now supported by rustfmt 🎉

The PR is split into two commits
1. The result of running [`if-to-let-chain clippy*/**/*.rs`](https://github.com/Alexendoo/if-to-let-chain)
2. The manual clean up: fixing some errors/formatting, dogfood lints, removing the if_chain internal lint

r? `@flip1995`

changelog: none
This commit is contained in:
bors 2023-11-10 18:09:19 +00:00
commit 9a4dd106d9
245 changed files with 8185 additions and 9444 deletions

View file

@ -14,7 +14,6 @@ cargo_metadata = "0.15.3"
clippy_config = { path = "../clippy_config" }
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
if_chain = "1.0"
itertools = "0.10.1"
quine-mc_cluskey = "0.2"
regex-syntax = "0.7"

View file

@ -52,24 +52,22 @@ declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]);
impl LateLintPass<'_> for AllowAttribute {
// Separate each crate's features.
fn check_attribute<'cx>(&mut self, cx: &LateContext<'cx>, attr: &'cx Attribute) {
if_chain! {
if !in_external_macro(cx.sess(), attr.span);
if cx.tcx.features().lint_reasons;
if let AttrStyle::Outer = attr.style;
if let Some(ident) = attr.ident();
if ident.name == rustc_span::symbol::sym::allow;
if !is_from_proc_macro(cx, &attr);
then {
span_lint_and_sugg(
cx,
ALLOW_ATTRIBUTES,
ident.span,
"#[allow] attribute found",
"replace it with",
"expect".into(),
Applicability::MachineApplicable,
);
}
if !in_external_macro(cx.sess(), attr.span)
&& cx.tcx.features().lint_reasons
&& let AttrStyle::Outer = attr.style
&& let Some(ident) = attr.ident()
&& ident.name == rustc_span::symbol::sym::allow
&& !is_from_proc_macro(cx, &attr)
{
span_lint_and_sugg(
cx,
ALLOW_ATTRIBUTES,
ident.span,
"#[allow] attribute found",
"replace it with",
"expect".into(),
Applicability::MachineApplicable,
);
}
}
}

View file

@ -5,7 +5,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sug
use clippy_utils::is_from_proc_macro;
use clippy_utils::macros::{is_panic, macro_backtrace};
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
use if_chain::if_chain;
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::TokenTree;
use rustc_ast::{
@ -471,13 +470,11 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
return;
}
for item in items {
if_chain! {
if let NestedMetaItem::MetaItem(mi) = &item;
if let MetaItemKind::NameValue(lit) = &mi.kind;
if mi.has_name(sym::since);
then {
check_semver(cx, item.span(), lit);
}
if let NestedMetaItem::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{
check_semver(cx, item.span(), lit);
}
}
}
@ -580,15 +577,13 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
/// Returns the lint name if it is clippy lint.
fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
if_chain! {
if let Some(meta_item) = lint.meta_item();
if meta_item.path.segments.len() > 1;
if let tool_name = meta_item.path.segments[0].ident;
if tool_name.name == sym::clippy;
then {
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name);
}
if let Some(meta_item) = lint.meta_item()
&& meta_item.path.segments.len() > 1
&& let tool_name = meta_item.path.segments[0].ident
&& tool_name.name == sym::clippy
{
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name);
}
None
}
@ -857,18 +852,17 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
}
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
if_chain! {
if msrv.meets(msrvs::TOOL_ATTRIBUTES);
if msrv.meets(msrvs::TOOL_ATTRIBUTES)
// check cfg_attr
if attr.has_name(sym::cfg_attr);
if let Some(items) = attr.meta_item_list();
if items.len() == 2;
&& attr.has_name(sym::cfg_attr)
&& let Some(items) = attr.meta_item_list()
&& items.len() == 2
// check for `rustfmt`
if let Some(feature_item) = items[0].meta_item();
if feature_item.has_name(sym::rustfmt);
&& let Some(feature_item) = items[0].meta_item()
&& feature_item.has_name(sym::rustfmt)
// check for `rustfmt_skip` and `rustfmt::skip`
if let Some(skip_item) = &items[1].meta_item();
if skip_item.has_name(sym!(rustfmt_skip))
&& let Some(skip_item) = &items[1].meta_item()
&& (skip_item.has_name(sym!(rustfmt_skip))
|| skip_item
.path
.segments
@ -876,21 +870,20 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
.expect("empty path in attribute")
.ident
.name
== sym::skip;
== sym::skip)
// Only lint outer attributes, because custom inner attributes are unstable
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
if attr.style == AttrStyle::Outer;
then {
span_lint_and_sugg(
cx,
DEPRECATED_CFG_ATTR,
attr.span,
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
"use",
"#[rustfmt::skip]".to_string(),
Applicability::MachineApplicable,
);
}
&& attr.style == AttrStyle::Outer
{
span_lint_and_sugg(
cx,
DEPRECATED_CFG_ATTR,
attr.span,
"`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
"use",
"#[rustfmt::skip]".to_string(),
Applicability::MachineApplicable,
);
}
}
@ -990,12 +983,10 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
mismatched.extend(find_mismatched_target_os(list));
},
MetaItemKind::Word => {
if_chain! {
if let Some(ident) = meta.ident();
if let Some(os) = find_os(ident.name.as_str());
then {
mismatched.push((os, ident.span));
}
if let Some(ident) = meta.ident()
&& let Some(os) = find_os(ident.name.as_str())
{
mismatched.push((os, ident.span));
}
},
MetaItemKind::NameValue(..) => {},
@ -1006,30 +997,28 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
mismatched
}
if_chain! {
if attr.has_name(sym::cfg);
if let Some(list) = attr.meta_item_list();
let mismatched = find_mismatched_target_os(&list);
if !mismatched.is_empty();
then {
let mess = "operating system used in target family position";
if attr.has_name(sym::cfg)
&& let Some(list) = attr.meta_item_list()
&& let mismatched = find_mismatched_target_os(&list)
&& !mismatched.is_empty()
{
let mess = "operating system used in target family position";
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems
let mut unix_suggested = false;
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems
let mut unix_suggested = false;
for (os, span) in mismatched {
let sugg = format!("target_os = \"{os}\"");
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
for (os, span) in mismatched {
let sugg = format!("target_os = \"{os}\"");
diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
if !unix_suggested && is_unix(os) {
diag.help("did you mean `unix`?");
unix_suggested = true;
}
if !unix_suggested && is_unix(os) {
diag.help("did you mean `unix`?");
unix_suggested = true;
}
});
}
}
});
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{get_parent_expr, higher};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BlockCheckMode, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -114,15 +113,13 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
let _: Option<!> = for_each_expr(cond, |e| {
if let ExprKind::Closure(closure) = e.kind {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(cx, e);
if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind;
let caller = cx.typeck_results().expr_ty(self_arg);
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(cx, caller, iter_id, &[]);
then {
return ControlFlow::Continue(Descend::No);
}
if let Some(parent) = get_parent_expr(cx, e)
&& let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind
&& let caller = cx.typeck_results().expr_ty(self_arg)
&& let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& implements_trait(cx, caller, iter_id, &[])
{
return ControlFlow::Continue(Descend::No);
}
let body = cx.tcx.hir().body(closure.body);

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::eq_expr_value;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
@ -152,17 +151,15 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
return Ok(Bool::Term(n as u8));
}
if_chain! {
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind;
if implements_ord(self.cx, e_lhs);
if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
if negate(e_binop.node) == Some(expr_binop.node);
if eq_expr_value(self.cx, e_lhs, expr_lhs);
if eq_expr_value(self.cx, e_rhs, expr_rhs);
then {
#[expect(clippy::cast_possible_truncation)]
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
}
if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind
&& implements_ord(self.cx, e_lhs)
&& let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind
&& negate(e_binop.node) == Some(expr_binop.node)
&& eq_expr_value(self.cx, e_lhs, expr_lhs)
&& eq_expr_value(self.cx, e_rhs, expr_rhs)
{
#[expect(clippy::cast_possible_truncation)]
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
}
}
let n = self.terminals.len();

View file

@ -49,69 +49,62 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
if_chain! {
if !e.span.from_expansion();
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind;
if !addrof_target.span.from_expansion();
if let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind;
if !deref_target.span.from_expansion();
if !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..) );
let ref_ty = cx.typeck_results().expr_ty(deref_target);
if let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind();
if !is_from_proc_macro(cx, e);
then{
if let Some(parent_expr) = get_parent_expr(cx, e){
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..)) &&
!is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id) {
return;
}
// modification to `&mut &*x` is different from `&mut x`
if matches!(deref_target.kind, ExprKind::Path(..)
| ExprKind::Field(..)
| ExprKind::Index(..)
| ExprKind::Unary(UnOp::Deref, ..))
&& matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _)) {
return;
}
if !e.span.from_expansion()
&& let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind
&& !addrof_target.span.from_expansion()
&& let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind
&& !deref_target.span.from_expansion()
&& !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..))
&& let ref_ty = cx.typeck_results().expr_ty(deref_target)
&& let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind()
&& !is_from_proc_macro(cx, e)
{
if let Some(parent_expr) = get_parent_expr(cx, e) {
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..))
&& !is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id)
{
return;
}
span_lint_and_then(
cx,
BORROW_DEREF_REF,
e.span,
"deref on an immutable reference",
|diag| {
diag.span_suggestion(
e.span,
"if you would like to reborrow, try removing `&*`",
snippet_opt(cx, deref_target.span).unwrap(),
Applicability::MachineApplicable
);
// 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;
}
}
diag.span_suggestion(
e.span,
"if you would like to deref, try using `&**`",
format!(
"&**{}",
&snippet_opt(cx, deref_target.span).unwrap(),
),
Applicability::MaybeIncorrect
);
}
);
// modification to `&mut &*x` is different from `&mut x`
if matches!(
deref_target.kind,
ExprKind::Path(..) | ExprKind::Field(..) | ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, ..)
) && matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _))
{
return;
}
}
span_lint_and_then(
cx,
BORROW_DEREF_REF,
e.span,
"deref on an immutable reference",
|diag| {
diag.span_suggestion(
e.span,
"if you would like to reborrow, try removing `&*`",
snippet_opt(cx, deref_target.span).unwrap(),
Applicability::MachineApplicable,
);
// 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;
}
}
diag.span_suggestion(
e.span,
"if you would like to deref, try using `&**`",
format!("&**{}", &snippet_opt(cx, deref_target.span).unwrap()),
Applicability::MaybeIncorrect,
);
},
);
}
}
}

View file

@ -2,7 +2,6 @@
use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId};
use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use itertools::Itertools;
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_lint::LateContext;
@ -15,31 +14,33 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
let mut packages = metadata.packages.clone();
packages.sort_by(|a, b| a.name.cmp(&b.name));
if_chain! {
if let Some(resolve) = &metadata.resolve;
if let Some(local_id) = packages
.iter()
.find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None });
then {
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
let group: Vec<&Package> = group.collect();
if let Some(resolve) = &metadata.resolve
&& let Some(local_id) = packages.iter().find_map(|p| {
if p.name == local_name.as_str() {
Some(&p.id)
} else {
None
}
})
{
for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
let group: Vec<&Package> = group.collect();
if group.len() <= 1 {
continue;
}
if group.len() <= 1 {
continue;
}
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
versions.sort();
let versions = versions.iter().join(", ");
if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
versions.sort();
let versions = versions.iter().join(", ");
span_lint(
cx,
MULTIPLE_CRATE_VERSIONS,
DUMMY_SP,
&format!("multiple versions for dependency `{name}`: {versions}"),
);
}
span_lint(
cx,
MULTIPLE_CRATE_VERSIONS,
DUMMY_SP,
&format!("multiple versions for dependency `{name}`: {versions}"),
);
}
}
}

View file

@ -1,6 +1,5 @@
use cargo_metadata::Metadata;
use clippy_utils::diagnostics::span_lint;
use if_chain::if_chain;
use rustc_lint::LateContext;
use rustc_span::source_map::DUMMY_SP;
@ -9,19 +8,17 @@ use super::WILDCARD_DEPENDENCIES;
pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
for dep in &metadata.packages[0].dependencies {
// VersionReq::any() does not work
if_chain! {
if let Ok(wildcard_ver) = semver::VersionReq::parse("*");
if let Some(ref source) = dep.source;
if !source.starts_with("git");
if dep.req == wildcard_ver;
then {
span_lint(
cx,
WILDCARD_DEPENDENCIES,
DUMMY_SP,
&format!("wildcard dependency for `{}`", dep.name),
);
}
if let Ok(wildcard_ver) = semver::VersionReq::parse("*")
&& let Some(ref source) = dep.source
&& !source.starts_with("git")
&& dep.req == wildcard_ver
{
span_lint(
cx,
WILDCARD_DEPENDENCIES,
DUMMY_SP,
&format!("wildcard dependency for `{}`", dep.name),
);
}
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{method_chain_args, sext};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
@ -28,13 +27,11 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
// Don't lint for positive constants.
let const_val = constant(cx, cx.typeck_results(), cast_op);
if_chain! {
if let Some(Constant::Int(n)) = const_val;
if let ty::Int(ity) = *cast_from.kind();
if sext(cx.tcx, n, ity) >= 0;
then {
return false;
}
if let Some(Constant::Int(n)) = const_val
&& let ty::Int(ity) = *cast_from.kind()
&& sext(cx.tcx, n, ity) >= 0
{
return false;
}
// Don't lint for the result of methods that always return non-negative values.
@ -42,13 +39,11 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
let mut method_name = path.ident.name.as_str();
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
if_chain! {
if method_name == "unwrap";
if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]);
if let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind;
then {
method_name = inner_path.ident.name.as_str();
}
if method_name == "unwrap"
&& let Some(arglist) = method_chain_args(cast_op, &["unwrap"])
&& let ExprKind::MethodCall(inner_path, ..) = &arglist[0].0.kind
{
method_name = inner_path.ident.name.as_str();
}
if allowed_methods.iter().any(|&name| method_name == name) {

View file

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source;
use if_chain::if_chain;
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
@ -69,26 +68,24 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let map = cx.tcx.hir();
if_chain! {
if let Some(parent_id) = map.opt_parent_id(expr.hir_id);
if let Some(parent) = map.find(parent_id);
then {
let expr = match parent {
Node::Block(block) => {
if let Some(parent_expr) = block.expr {
parent_expr
} else {
return false;
}
},
Node::Expr(expr) => expr,
_ => return false,
};
if let Some(parent_id) = map.opt_parent_id(expr.hir_id)
&& let Some(parent) = map.find(parent_id)
{
let expr = match parent {
Node::Block(block) => {
if let Some(parent_expr) = block.expr {
parent_expr
} else {
return false;
}
},
Node::Expr(expr) => expr,
_ => return false,
};
matches!(expr.kind, ExprKind::Cast(..))
} else {
false
}
matches!(expr.kind, ExprKind::Cast(..))
} else {
false
}
}

View file

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind};
@ -25,34 +24,32 @@ fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option<RawPartsKind> {
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>, msrv: &Msrv) {
if_chain! {
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS);
if let ty::RawPtr(ptrty) = cast_to.kind();
if let ty::Slice(_) = ptrty.ty.kind();
if let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind;
if let ExprKind::Path(ref qpath) = fun.kind;
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
if let Some(rpk) = raw_parts_kind(cx, fun_def_id);
let ctxt = expr.span.ctxt();
if cast_expr.span.ctxt() == ctxt;
then {
let func = match rpk {
RawPartsKind::Immutable => "from_raw_parts",
RawPartsKind::Mutable => "from_raw_parts_mut"
};
let span = expr.span;
let mut applicability = Applicability::MachineApplicable;
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
span,
&format!("casting the result of `{func}` to {cast_to}"),
"replace with",
format!("core::ptr::slice_{func}({ptr}, {len})"),
applicability
);
}
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS)
&& let ty::RawPtr(ptrty) = cast_to.kind()
&& let ty::Slice(_) = ptrty.ty.kind()
&& let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind
&& let ExprKind::Path(ref qpath) = fun.kind
&& let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
&& let Some(rpk) = raw_parts_kind(cx, fun_def_id)
&& let ctxt = expr.span.ctxt()
&& cast_expr.span.ctxt() == ctxt
{
let func = match rpk {
RawPartsKind::Immutable => "from_raw_parts",
RawPartsKind::Mutable => "from_raw_parts_mut",
};
let span = expr.span;
let mut applicability = Applicability::MachineApplicable;
let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
span,
&format!("casting the result of `{func}` to {cast_to}"),
"replace with",
format!("core::ptr::slice_{func}({ptr}, {len})"),
applicability,
);
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
@ -10,32 +9,31 @@ use rustc_middle::ty::{self, UintTy};
use super::CHAR_LIT_AS_U8;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Cast(e, _) = &expr.kind;
if let ExprKind::Lit(l) = &e.kind;
if let LitKind::Char(c) = l.node;
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind();
then {
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
if let ExprKind::Cast(e, _) = &expr.kind
&& let ExprKind::Lit(l) = &e.kind
&& let LitKind::Char(c) = l.node
&& ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind()
{
let mut applicability = Applicability::MachineApplicable;
let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability);
span_lint_and_then(
cx,
CHAR_LIT_AS_U8,
expr.span,
"casting a character literal to `u8` truncates",
|diag| {
diag.note("`char` is four bytes wide, but `u8` is a single byte");
span_lint_and_then(
cx,
CHAR_LIT_AS_U8,
expr.span,
"casting a character literal to `u8` truncates",
|diag| {
diag.note("`char` is four bytes wide, but `u8` is a single byte");
if c.is_ascii() {
diag.span_suggestion(
expr.span,
"use a byte literal instead",
format!("b{snippet}"),
applicability,
);
}
});
}
if c.is_ascii() {
diag.span_suggestion(
expr.span,
"use a byte literal instead",
format!("b{snippet}"),
applicability,
);
}
},
);
}
}

View file

@ -1,7 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
@ -17,29 +16,35 @@ pub(super) fn check<'tcx>(
cast_to: Ty<'tcx>,
msrv: &Msrv,
) {
if_chain! {
if msrv.meets(msrvs::POINTER_CAST_CONSTNESS);
if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, ty: from_ty }) = cast_from.kind();
if let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, ty: to_ty }) = cast_to.kind();
if matches!((from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not));
if from_ty == to_ty;
then {
let sugg = Sugg::hir(cx, cast_expr, "_");
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
if msrv.meets(msrvs::POINTER_CAST_CONSTNESS)
&& let ty::RawPtr(TypeAndMut {
mutbl: from_mutbl,
ty: from_ty,
}) = cast_from.kind()
&& let ty::RawPtr(TypeAndMut {
mutbl: to_mutbl,
ty: to_ty,
}) = cast_to.kind()
&& matches!(
(from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not)
)
&& from_ty == to_ty
{
let sugg = Sugg::hir(cx, cast_expr, "_");
let constness = match *to_mutbl {
Mutability::Not => "const",
Mutability::Mut => "mut",
};
span_lint_and_sugg(
cx,
PTR_CAST_CONSTNESS,
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,
);
}
span_lint_and_sugg(
cx,
PTR_CAST_CONSTNESS,
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,
);
}
}

View file

@ -3,7 +3,6 @@ use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt;
use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::{get_parent_expr, get_parent_node, is_hir_ty_cfg_dependant, is_ty_alias, path_to_local};
use if_chain::if_chain;
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -25,40 +24,40 @@ pub(super) fn check<'tcx>(
) -> bool {
let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
if_chain! {
if let ty::RawPtr(..) = cast_from.kind();
if let ty::RawPtr(..) = cast_from.kind()
// check both mutability and type are the same
if cast_from.kind() == cast_to.kind();
if let ExprKind::Cast(_, cast_to_hir) = expr.kind;
&& cast_from.kind() == cast_to.kind()
&& let ExprKind::Cast(_, cast_to_hir) = expr.kind
// Ignore casts to e.g. type aliases and infer types
// - p as pointer_alias
// - p as _
if let TyKind::Ptr(to_pointee) = cast_to_hir.kind;
then {
match to_pointee.ty.kind {
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false;
}
},
// Ignore `p as *const _`
TyKind::Infer => return false,
_ => {},
}
span_lint_and_sugg(
cx,
UNNECESSARY_CAST,
expr.span,
&format!("casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"),
"try",
cast_str.clone(),
Applicability::MaybeIncorrect,
);
&& let TyKind::Ptr(to_pointee) = cast_to_hir.kind
{
match to_pointee.ty.kind {
// Ignore casts to pointers that are aliases or cfg dependant, e.g.
// - p as *const std::ffi::c_char (alias)
// - p as *const std::os::raw::c_char (cfg dependant)
TyKind::Path(qpath) => {
if is_ty_alias(&qpath) || is_hir_ty_cfg_dependant(cx, to_pointee.ty) {
return false;
}
},
// Ignore `p as *const _`
TyKind::Infer => return false,
_ => {},
}
span_lint_and_sugg(
cx,
UNNECESSARY_CAST,
expr.span,
&format!(
"casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)"
),
"try",
cast_str.clone(),
Applicability::MaybeIncorrect,
);
}
// skip cast of local that is a type alias
@ -86,14 +85,12 @@ pub(super) fn check<'tcx>(
}
// skip cast to non-primitive type
if_chain! {
if let ExprKind::Cast(_, cast_to) = expr.kind;
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
if let Res::PrimTy(_) = path.res;
then {}
else {
return false;
}
if let ExprKind::Cast(_, cast_to) = expr.kind
&& let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind
&& let Res::PrimTy(_) = path.res
{
} else {
return false;
}
// skip cast of fn call that returns type alias
@ -106,18 +103,19 @@ pub(super) fn check<'tcx>(
if let Some(lit) = get_numeric_literal(cast_expr) {
let literal_str = &cast_str;
if_chain! {
if let LitKind::Int(n, _) = lit.node;
if let Some(src) = snippet_opt(cx, cast_expr.span);
if cast_to.is_floating_point();
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node);
let from_nbits = 128 - n.leading_zeros();
let to_nbits = fp_ty_mantissa_nbits(cast_to);
if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal();
then {
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true
}
if let LitKind::Int(n, _) = lit.node
&& let Some(src) = snippet_opt(cx, cast_expr.span)
&& cast_to.is_floating_point()
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node)
&& let from_nbits = 128 - n.leading_zeros()
&& let to_nbits = fp_ty_mantissa_nbits(cast_to)
&& from_nbits != 0
&& to_nbits != 0
&& from_nbits <= to_nbits
&& num_lit.is_decimal()
{
lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to);
return true;
}
match lit.node {

View file

@ -4,7 +4,6 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -55,20 +54,17 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
return;
}
let result = if_chain! {
if !in_constant(cx, item.hir_id);
if !in_external_macro(cx.sess(), item.span);
if let ExprKind::Binary(op, left, right) = &item.kind;
then {
match op.node {
BinOpKind::Ge | BinOpKind::Le => single_check(item),
BinOpKind::And => double_check(cx, left, right),
_ => None,
}
} else {
None
let result = if !in_constant(cx, item.hir_id)
&& !in_external_macro(cx.sess(), item.span)
&& let ExprKind::Binary(op, left, right) = &item.kind
{
match op.node {
BinOpKind::Ge | BinOpKind::Le => single_check(item),
BinOpKind::And => double_check(cx, left, right),
_ => None,
}
} else {
None
};
if let Some(cv) = result {
@ -193,16 +189,13 @@ impl ConversionType {
/// Check for `expr <= (to_type::MAX as from_type)`
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
if_chain! {
if let ExprKind::Binary(ref op, left, right) = &expr.kind;
if let Some((candidate, check)) = normalize_le_ge(op, left, right);
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
then {
Conversion::try_new(candidate, from, to)
} else {
None
}
if let ExprKind::Binary(ref op, left, right) = &expr.kind
&& let Some((candidate, check)) = normalize_le_ge(op, left, right)
&& let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX")
{
Conversion::try_new(candidate, from, to)
} else {
None
}
}
@ -243,33 +236,27 @@ fn get_types_from_cast<'a>(
) -> Option<(&'a str, &'a str)> {
// `to_type::max_value() as from_type`
// or `to_type::MAX as from_type`
let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind
// to_type::max_value(), from_type
if let ExprKind::Cast(limit, from_type) = &expr.kind;
if let TyKind::Path(ref from_type_path) = &from_type.kind;
if let Some(from_sym) = int_ty_to_sym(from_type_path);
then {
Some((limit, from_sym))
} else {
None
}
&& let TyKind::Path(ref from_type_path) = &from_type.kind
&& let Some(from_sym) = int_ty_to_sym(from_type_path)
{
Some((limit, from_sym))
} else {
None
};
// `from_type::from(to_type::max_value())`
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
if_chain! {
if let ExprKind::Call(from_func, [limit]) = &expr.kind
// `from_type::from, to_type::max_value()`
if let ExprKind::Call(from_func, [limit]) = &expr.kind;
// `from_type::from`
if let ExprKind::Path(ref path) = &from_func.kind;
if let Some(from_sym) = get_implementing_type(path, INTS, "from");
then {
Some((limit, from_sym))
} else {
None
}
&& let ExprKind::Path(ref path) = &from_func.kind
&& let Some(from_sym) = get_implementing_type(path, INTS, "from")
{
Some((limit, from_sym))
} else {
None
}
});
@ -298,31 +285,27 @@ fn get_types_from_cast<'a>(
/// Gets the type which implements the called function
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
if_chain! {
if let QPath::TypeRelative(ty, path) = &path;
if path.ident.name.as_str() == function;
if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
if let [int] = tp.segments;
then {
let name = int.ident.name.as_str();
candidates.iter().find(|c| &name == *c).copied()
} else {
None
}
if let QPath::TypeRelative(ty, path) = &path
&& path.ident.name.as_str() == function
&& let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind
&& let [int] = tp.segments
{
let name = int.ident.name.as_str();
candidates.iter().find(|c| &name == *c).copied()
} else {
None
}
}
/// Gets the type as a string, if it is a supported integer
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! {
if let QPath::Resolved(_, path) = *path;
if let [ty] = path.segments;
then {
let name = ty.ident.name.as_str();
INTS.iter().find(|c| &name == *c).copied()
} else {
None
}
if let QPath::Resolved(_, path) = *path
&& let [ty] = path.segments
{
let name = ty.ident.name.as_str();
INTS.iter().find(|c| &name == *c).copied()
} else {
None
}
}

View file

@ -15,7 +15,6 @@
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 if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@ -121,49 +120,55 @@ fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
}
fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) {
if_chain! {
if let ast::ExprKind::Block(ref block, _) = else_.kind;
if !block_starts_with_comment(cx, block);
if let Some(else_) = expr_block(block);
if else_.attrs.is_empty();
if !else_.span.from_expansion();
if let ast::ExprKind::If(..) = else_.kind;
then {
// 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 };
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,
);
}
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,
);
}
}
fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
if_chain! {
if !block_starts_with_comment(cx, then);
if let Some(inner) = expr_block(then);
if inner.attrs.is_empty();
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
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 { .. } }`.
if !matches!(check_inner.kind, ast::ExprKind::Let(..));
let ctxt = expr.span.ctxt();
if inner.span.ctxt() == ctxt;
then {
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
&& !matches!(check_inner.kind, ast::ExprKind::Let(..))
&& let ctxt = expr.span.ctxt()
&& inner.span.ctxt() == ctxt
{
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);
@ -177,8 +182,8 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
),
app, // snippet
);
});
}
},
);
}
}

View file

@ -5,8 +5,6 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use if_chain::if_chain;
declare_clippy_lint! {
/// ### What it does
/// Checks for types that implement `Copy` as well as
@ -38,25 +36,23 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]);
impl<'tcx> LateLintPass<'tcx> for CopyIterator {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
..
}) = item.kind;
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
if is_copy(cx, ty);
if let Some(trait_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Iterator, trait_id);
then {
span_lint_and_note(
cx,
COPY_ITERATOR,
item.span,
"you are implementing `Iterator` on a `Copy` type",
None,
"consider implementing `IntoIterator` instead",
);
}
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
..
}) = item.kind
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& is_copy(cx, ty)
&& let Some(trait_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Iterator, trait_id)
{
span_lint_and_note(
cx,
COPY_ITERATOR,
item.span,
"you are implementing `Iterator` on a `Copy` type",
None,
"consider implementing `IntoIterator` instead",
);
}
}
}

View file

@ -53,35 +53,31 @@ declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
impl EarlyLintPass for CrateInMacroDef {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if_chain! {
if item.attrs.iter().any(is_macro_export);
if let ItemKind::MacroDef(macro_def) = &item.kind;
let tts = macro_def.body.tokens.clone();
if let Some(span) = contains_unhygienic_crate_reference(&tts);
then {
span_lint_and_sugg(
cx,
CRATE_IN_MACRO_DEF,
span,
"`crate` references the macro call's crate",
"to reference the macro definition's crate, use",
String::from("$crate"),
Applicability::MachineApplicable,
);
}
if item.attrs.iter().any(is_macro_export)
&& let ItemKind::MacroDef(macro_def) = &item.kind
&& let tts = macro_def.body.tokens.clone()
&& let Some(span) = contains_unhygienic_crate_reference(&tts)
{
span_lint_and_sugg(
cx,
CRATE_IN_MACRO_DEF,
span,
"`crate` references the macro call's crate",
"to reference the macro definition's crate, use",
String::from("$crate"),
Applicability::MachineApplicable,
);
}
}
}
fn is_macro_export(attr: &Attribute) -> bool {
if_chain! {
if let AttrKind::Normal(normal) = &attr.kind;
if let [segment] = normal.item.path.segments.as_slice();
then {
segment.ident.name == sym::macro_export
} else {
false
}
if let AttrKind::Normal(normal) = &attr.kind
&& let [segment] = normal.item.path.segments.as_slice()
{
segment.ident.name == sym::macro_export
} else {
false
}
}
@ -89,14 +85,12 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
let mut prev_is_dollar = false;
let mut cursor = tts.trees();
while let Some(curr) = cursor.next() {
if_chain! {
if !prev_is_dollar;
if let Some(span) = is_crate_keyword(curr);
if let Some(next) = cursor.look_ahead(0);
if is_token(next, &TokenKind::ModSep);
then {
return Some(span);
}
if !prev_is_dollar
&& let Some(span) = is_crate_keyword(curr)
&& let Some(next) = cursor.look_ahead(0)
&& is_token(next, &TokenKind::ModSep)
{
return Some(span);
}
if let TokenTree::Delimited(_, _, tts) = &curr {
let span = contains_unhygienic_crate_reference(tts);
@ -110,10 +104,18 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
}
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
if_chain! {
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }, _) = tt;
if symbol.as_str() == "crate";
then { Some(*span) } else { None }
if let TokenTree::Token(
Token {
kind: TokenKind::Ident(symbol, _),
span,
},
_,
) = tt
&& symbol.as_str() == "crate"
{
Some(*span)
} else {
None
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -33,22 +32,20 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Call(func, [arg, ..]) = expr.kind;
if let ExprKind::Path(ref path) = func.kind;
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id);
then {
span_lint_and_sugg(
cx,
CREATE_DIR,
expr.span,
"calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead",
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
Applicability::MaybeIncorrect,
)
}
if let ExprKind::Call(func, [arg, ..]) = expr.kind
&& let ExprKind::Path(ref path) = func.kind
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id)
{
span_lint_and_sugg(
cx,
CREATE_DIR,
expr.span,
"calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead",
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
Applicability::MaybeIncorrect,
);
}
}
}

View file

@ -10,8 +10,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
#[cfg(feature = "internal")]
crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::if_chain_style::IF_CHAIN_STYLE_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,

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -81,33 +80,31 @@ impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
impl<'tcx> LateLintPass<'tcx> for Default {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if !expr.span.from_expansion();
if !expr.span.from_expansion()
// Avoid cases already linted by `field_reassign_with_default`
if !self.reassigned_linted.contains(&expr.span);
if let ExprKind::Call(path, ..) = expr.kind;
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
if let ExprKind::Path(ref qpath) = path.kind;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id);
if !is_update_syntax_base(cx, expr);
&& !self.reassigned_linted.contains(&expr.span)
&& let ExprKind::Call(path, ..) = expr.kind
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
&& let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
&& !is_update_syntax_base(cx, expr)
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
if let QPath::Resolved(None, _path) = qpath;
let expr_ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, ..) = expr_ty.kind();
if !is_from_proc_macro(cx, expr);
then {
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
span_lint_and_sugg(
cx,
DEFAULT_TRAIT_ACCESS,
expr.span,
&format!("calling `{replacement}` is more clear than this expression"),
"try",
replacement,
Applicability::Unspecified, // First resolve the TODO above
);
}
&& let QPath::Resolved(None, _path) = qpath
&& let expr_ty = cx.typeck_results().expr_ty(expr)
&& let ty::Adt(def, ..) = expr_ty.kind()
&& !is_from_proc_macro(cx, expr)
{
let replacement = with_forced_trimmed_paths!(format!("{}::default()", cx.tcx.def_path_str(def.did())));
span_lint_and_sugg(
cx,
DEFAULT_TRAIT_ACCESS,
expr.span,
&format!("calling `{replacement}` is more clear than this expression"),
"try",
replacement,
Applicability::Unspecified, // First resolve the TODO above
);
}
}
@ -124,38 +121,36 @@ impl<'tcx> LateLintPass<'tcx> for Default {
// find all binding statements like `let mut _ = T::default()` where `T::default()` is the
// `default` method of the `Default` trait, and store statement index in current block being
// checked and the name of the bound variable
let (local, variant, binding_name, binding_type, span) = if_chain! {
let (local, variant, binding_name, binding_type, span) = if let StmtKind::Local(local) = stmt.kind
// only take `let ...` statements
if let StmtKind::Local(local) = stmt.kind;
if let Some(expr) = local.init;
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
if !expr.span.from_expansion();
&& let Some(expr) = local.init
&& !any_parent_is_automatically_derived(cx.tcx, expr.hir_id)
&& !expr.span.from_expansion()
// only take bindings to identifiers
if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
&& let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind
// only when assigning `... = Default::default()`
if is_expr_default(expr, cx);
let binding_type = cx.typeck_results().node_type(binding_id);
if let Some(adt) = binding_type.ty_adt_def();
if adt.is_struct();
let variant = adt.non_enum_variant();
if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
let module_did = cx.tcx.parent_module(stmt.hir_id);
if variant
&& is_expr_default(expr, cx)
&& let binding_type = cx.typeck_results().node_type(binding_id)
&& let Some(adt) = binding_type.ty_adt_def()
&& adt.is_struct()
&& let variant = adt.non_enum_variant()
&& (adt.did().is_local() || !variant.is_field_list_non_exhaustive())
&& let module_did = cx.tcx.parent_module(stmt.hir_id)
&& variant
.fields
.iter()
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
let all_fields_are_copy = variant
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx))
&& let all_fields_are_copy = variant
.fields
.iter()
.all(|field| {
is_copy(cx, cx.tcx.type_of(field.did).instantiate_identity())
});
if !has_drop(cx, binding_type) || all_fields_are_copy;
then {
(local, variant, ident.name, binding_type, expr.span)
} else {
continue;
}
})
&& (!has_drop(cx, binding_type) || all_fields_are_copy)
{
(local, variant, ident.name, binding_type, expr.span)
} else {
continue;
};
let init_ctxt = local.span.ctxt();
@ -216,21 +211,19 @@ impl<'tcx> LateLintPass<'tcx> for Default {
.join(", ");
// give correct suggestion if generics are involved (see #6944)
let binding_type = if_chain! {
if let ty::Adt(adt_def, args) = binding_type.kind();
if !args.is_empty();
then {
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
let generic_args = args.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
format!("{adt_def_ty_name}::<{}>", &tys_str)
} else {
binding_type.to_string()
}
let binding_type = if let ty::Adt(adt_def, args) = binding_type.kind()
&& !args.is_empty()
{
let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
let generic_args = args.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
format!("{adt_def_ty_name}::<{}>", &tys_str)
} else {
binding_type.to_string()
};
let sugg = if ext_with_default {
@ -260,48 +253,42 @@ impl<'tcx> LateLintPass<'tcx> for Default {
/// Checks if the given expression is the `default` method belonging to the `Default` trait.
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
if_chain! {
if let ExprKind::Call(fn_expr, _) = &expr.kind;
if let ExprKind::Path(qpath) = &fn_expr.kind;
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
then {
// right hand side of assignment is `Default::default`
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
} else {
false
}
if let ExprKind::Call(fn_expr, _) = &expr.kind
&& let ExprKind::Path(qpath) = &fn_expr.kind
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
{
// right hand side of assignment is `Default::default`
cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
} else {
false
}
}
/// Returns the reassigned field and the assigning expression (right-hand side of assign).
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
if_chain! {
if let StmtKind::Semi(later_expr) = this.kind
// only take assignments
if let StmtKind::Semi(later_expr) = this.kind;
if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
&& let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind
// only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement
if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
if let Some(second_binding_name) = path.segments.last();
if second_binding_name.ident.name == binding_name;
then {
Some((field_ident, assign_rhs))
} else {
None
}
&& let ExprKind::Field(binding, field_ident) = assign_lhs.kind
&& let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind
&& let Some(second_binding_name) = path.segments.last()
&& second_binding_name.ident.name == binding_name
{
Some((field_ident, assign_rhs))
} else {
None
}
}
/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }`
fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
if_chain! {
if let Some(parent) = get_parent_expr(cx, expr);
if let ExprKind::Struct(_, _, Some(base)) = parent.kind;
then {
base.hir_id == expr.hir_id
} else {
false
}
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::Struct(_, _, Some(base)) = parent.kind
{
base.hir_id == expr.hir_id
} else {
false
}
}

View file

@ -56,32 +56,30 @@ fn is_alias(ty: hir::Ty<'_>) -> bool {
impl LateLintPass<'_> for DefaultConstructedUnitStructs {
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
if_chain!(
if let hir::ExprKind::Call(fn_expr, &[]) = expr.kind
// make sure we have a call to `Default::default`
if let hir::ExprKind::Call(fn_expr, &[]) = expr.kind;
if let ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(base, _)) = fn_expr.kind;
&& let ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(base, _)) = fn_expr.kind
// make sure this isn't a type alias:
// `<Foo as Bar>::Assoc` cannot be used as a constructor
if !is_alias(*base);
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
if cx.tcx.is_diagnostic_item(sym::default_fn, def_id);
&& !is_alias(*base)
&& let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
// make sure we have a struct with no fields (unit struct)
if let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind();
if def.is_struct();
if let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant();
if !var.is_field_list_non_exhaustive();
if !expr.span.from_expansion() && !qpath.span().from_expansion();
then {
span_lint_and_sugg(
cx,
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
expr.span.with_lo(qpath.qself_span().hi()),
"use of `default` to create a unit struct",
"remove this call to `default`",
String::new(),
Applicability::MachineApplicable,
)
}
);
&& let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind()
&& def.is_struct()
&& 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()
{
span_lint_and_sugg(
cx,
DEFAULT_CONSTRUCTED_UNIT_STRUCTS,
expr.span.with_lo(qpath.qself_span().hi()),
"use of `default` to create a unit struct",
"remove this call to `default`",
String::new(),
Applicability::MachineApplicable,
);
};
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::{get_parent_node, numeric_literal};
use if_chain::if_chain;
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_stmt, Visitor};
@ -82,40 +81,40 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
/// Check whether a passed literal has potential to cause fallback or not.
fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) {
if_chain! {
if !in_external_macro(self.cx.sess(), lit.span);
if matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)));
if matches!(lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
then {
let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false),
ty::Float(FloatTy::F64) => ("f64", true),
// Default numeric fallback never results in other types.
_ => return,
};
if !in_external_macro(self.cx.sess(), lit.span)
&& matches!(self.ty_bounds.last(), Some(ExplicitTyBound(false)))
&& matches!(
lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
)
{
let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false),
ty::Float(FloatTy::F64) => ("f64", true),
// Default numeric fallback never results in other types.
_ => return,
};
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => return,
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
span_lint_hir_and_then(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
emit_hir_id,
lit.span,
"default numeric fallback might occur",
|diag| {
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
}
);
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => return,
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
span_lint_hir_and_then(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
emit_hir_id,
lit.span,
"default numeric fallback might occur",
|diag| {
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
},
);
}
}
}
@ -149,36 +148,33 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
ExprKind::Struct(_, fields, base) => {
let ty = self.cx.typeck_results().expr_ty(expr);
if_chain! {
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants().iter().next();
then {
let fields_def = &variant.fields;
if let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_struct()
&& let Some(variant) = adt_def.variants().iter().next()
{
let fields_def = &variant.fields;
// Push field type then visit each field expr.
for field in *fields {
let bound =
fields_def
.iter()
.find_map(|f_def| {
if f_def.ident(self.cx.tcx) == field.ident
{ Some(self.cx.tcx.type_of(f_def.did).instantiate_identity()) }
else { None }
});
self.ty_bounds.push(bound.into());
self.visit_expr(field.expr);
self.ty_bounds.pop();
}
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(ExplicitTyBound(false));
self.visit_expr(base);
self.ty_bounds.pop();
}
return;
// Push field type then visit each field expr.
for field in *fields {
let bound = fields_def.iter().find_map(|f_def| {
if f_def.ident(self.cx.tcx) == field.ident {
Some(self.cx.tcx.type_of(f_def.did).instantiate_identity())
} else {
None
}
});
self.ty_bounds.push(bound.into());
self.visit_expr(field.expr);
self.ty_bounds.pop();
}
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(ExplicitTyBound(false));
self.visit_expr(base);
self.ty_bounds.pop();
}
return;
}
},

View file

@ -597,26 +597,24 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
return;
}
if_chain! {
if !pat.span.from_expansion();
if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
if !pat.span.from_expansion()
&& let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind()
// only lint immutable refs, because borrowed `&mut T` cannot be moved out
if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
then {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
self.current_body = self.current_body.or(cx.enclosing_body);
self.ref_locals.insert(
id,
Some(RefPat {
always_deref: true,
spans: vec![pat.span],
app,
replacements: vec![(pat.span, snip.into())],
hir_id: pat.hir_id,
}),
);
}
&& let ty::Ref(_, _, Mutability::Not) = *tam.kind()
{
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
self.current_body = self.current_body.or(cx.enclosing_body);
self.ref_locals.insert(
id,
Some(RefPat {
always_deref: true,
spans: vec![pat.span],
app,
replacements: vec![(pat.span, snip.into())],
hir_id: pat.hir_id,
}),
);
}
}
}

View file

@ -148,83 +148,65 @@ fn check_struct<'tcx>(
}
fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) {
if_chain! {
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind;
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res;
if let variant_id = cx.tcx.parent(id);
if let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id);
if variant_def.fields.is_empty();
if !variant_def.is_field_list_non_exhaustive();
then {
let enum_span = cx.tcx.def_span(adt_def.did());
let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
let variant_span = cx.tcx.def_span(variant_def.def_id);
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
span_lint_and_then(
cx,
DERIVABLE_IMPLS,
if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
&& let variant_id = cx.tcx.parent(id)
&& let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id)
&& variant_def.fields.is_empty()
&& !variant_def.is_field_list_non_exhaustive()
{
let enum_span = cx.tcx.def_span(adt_def.did());
let indent_enum = indent_of(cx, enum_span).unwrap_or(0);
let variant_span = cx.tcx.def_span(variant_def.def_id);
let indent_variant = indent_of(cx, variant_span).unwrap_or(0);
span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| {
diag.span_suggestion_hidden(
item.span,
"this `impl` can be derived",
|diag| {
diag.span_suggestion_hidden(
item.span,
"remove the manual implementation...",
String::new(),
Applicability::MachineApplicable
);
diag.span_suggestion(
enum_span.shrink_to_lo(),
"...and instead derive it...",
format!(
"#[derive(Default)]\n{indent}",
indent = " ".repeat(indent_enum),
),
Applicability::MachineApplicable
);
diag.span_suggestion(
variant_span.shrink_to_lo(),
"...and mark the default variant",
format!(
"#[default]\n{indent}",
indent = " ".repeat(indent_variant),
),
Applicability::MachineApplicable
);
}
"remove the manual implementation...",
String::new(),
Applicability::MachineApplicable,
);
}
diag.span_suggestion(
enum_span.shrink_to_lo(),
"...and instead derive it...",
format!("#[derive(Default)]\n{indent}", indent = " ".repeat(indent_enum),),
Applicability::MachineApplicable,
);
diag.span_suggestion(
variant_span.shrink_to_lo(),
"...and mark the default variant",
format!("#[default]\n{indent}", indent = " ".repeat(indent_variant),),
Applicability::MachineApplicable,
);
});
}
}
impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
self_ty,
..
}) = item.kind;
if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
if !item.span.from_expansion();
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
if let impl_item_hir = child.id.hir_id();
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
if let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind();
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|attr| attr.doc_str().is_some());
if cx.tcx.hir().attrs(impl_item_hir).is_empty();
then {
if adt_def.is_struct() {
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
}
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
self_ty,
..
}) = item.kind
&& !cx.tcx.has_attr(item.owner_id, sym::automatically_derived)
&& !item.span.from_expansion()
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Default, def_id)
&& let impl_item_hir = child.id.hir_id()
&& let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
&& let ImplItemKind::Fn(_, b) = &impl_item.kind
&& let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
&& let &Adt(adt_def, args) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
&& let attrs = cx.tcx.hir().attrs(item.hir_id())
&& !attrs.iter().any(|attr| attr.doc_str().is_some())
&& cx.tcx.hir().attrs(impl_item_hir).is_empty()
{
if adt_def.is_struct() {
check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b));
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
}
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{is_lint_allowed, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
@ -233,42 +232,37 @@ fn check_hash_peq<'tcx>(
ty: Ty<'tcx>,
hash_is_automatically_derived: bool,
) {
if_chain! {
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Hash, def_id);
then {
// 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);
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Hash, def_id)
{
// 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);
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
}
if !hash_is_automatically_derived || peq_is_automatically_derived {
return;
}
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
// Only care about `impl PartialEq<Foo> for Foo`
// For `impl PartialEq<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
span_lint_and_then(
cx,
DERIVED_HASH_WITH_MANUAL_EQ,
span,
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(
cx.tcx.hir().span(hir_id),
"`PartialEq` implemented here"
);
}
// Only care about `impl PartialEq<Foo> for Foo`
// For `impl PartialEq<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
span_lint_and_then(
cx,
DERIVED_HASH_WITH_MANUAL_EQ,
span,
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(cx.tcx.hir().span(hir_id), "`PartialEq` implemented here");
}
);
}
});
}
},
);
}
});
}
}
@ -280,49 +274,38 @@ fn check_ord_partial_ord<'tcx>(
ty: Ty<'tcx>,
ord_is_automatically_derived: bool,
) {
if_chain! {
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord);
if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
if let Some(def_id) = &trait_ref.trait_def_id();
if *def_id == ord_trait_def_id;
then {
// 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);
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
&& let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait()
&& let Some(def_id) = &trait_ref.trait_def_id()
&& *def_id == ord_trait_def_id
{
// 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);
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
}
if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return;
}
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
// Only care about `impl PartialOrd<Foo> for Foo`
// For `impl PartialOrd<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
let mess = if partial_ord_is_automatically_derived {
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
} else {
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
};
// Only care about `impl PartialOrd<Foo> for Foo`
// For `impl PartialOrd<B> for A, input_types is [A, B]
if trait_ref.instantiate_identity().args.type_at(1) == ty {
let mess = if partial_ord_is_automatically_derived {
"you are implementing `Ord` explicitly but have derived `PartialOrd`"
} else {
"you are deriving `Ord` but have implemented `PartialOrd` explicitly"
};
span_lint_and_then(
cx,
DERIVE_ORD_XOR_PARTIAL_ORD,
span,
mess,
|diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(
cx.tcx.hir().span(hir_id),
"`PartialOrd` implemented here"
);
}
}
);
}
});
}
span_lint_and_then(cx, DERIVE_ORD_XOR_PARTIAL_ORD, span, mess, |diag| {
if let Some(local_def_id) = impl_id.as_local() {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
diag.span_note(cx.tcx.hir().span(hir_id), "`PartialOrd` implemented here");
}
});
}
});
}
}
@ -395,27 +378,27 @@ fn check_unsafe_derive_deserialize<'tcx>(
visitor.has_unsafe
}
if_chain! {
if let Some(trait_def_id) = trait_ref.trait_def_id();
if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
if let ty::Adt(def, _) = ty.kind();
if let Some(local_def_id) = def.did().as_local();
let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
if cx.tcx.inherent_impls(def.did())
if let Some(trait_def_id) = trait_ref.trait_def_id()
&& match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE)
&& let ty::Adt(def, _) = ty.kind()
&& let Some(local_def_id) = def.did().as_local()
&& let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id)
&& !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id)
&& cx
.tcx
.inherent_impls(def.did())
.iter()
.map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
.any(|imp| has_unsafe(cx, imp));
then {
span_lint_and_help(
cx,
UNSAFE_DERIVE_DESERIALIZE,
item.span,
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
None,
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
);
}
.any(|imp| has_unsafe(cx, imp))
{
span_lint_and_help(
cx,
UNSAFE_DERIVE_DESERIALIZE,
item.span,
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
None,
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html",
);
}
}
@ -432,12 +415,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
return;
}
if_chain! {
if let Some(header) = kind.header();
if header.unsafety == Unsafety::Unsafe;
then {
self.has_unsafe = true;
}
if let Some(header) = kind.header()
&& header.unsafety == Unsafety::Unsafe
{
self.has_unsafe = true;
}
walk_fn(self, kind, decl, body_id, id);
@ -464,30 +445,28 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
if_chain! {
if let ty::Adt(adt, args) = ty.kind();
if cx.tcx.visibility(adt.did()).is_public();
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id);
if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]);
if let ty::Adt(adt, args) = ty.kind()
&& cx.tcx.visibility(adt.did()).is_public()
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
&& let Some(def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
&& let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
&& !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])
// If all of our fields implement `Eq`, we can implement `Eq` too
if adt
&& adt
.all_fields()
.map(|f| f.ty(cx.tcx, args))
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]));
then {
span_lint_and_sugg(
cx,
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
span.ctxt().outer_expn_data().call_site,
"you are deriving `PartialEq` and can implement `Eq`",
"consider deriving `Eq` as well",
"PartialEq, Eq".to_string(),
Applicability::MachineApplicable,
)
}
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]))
{
span_lint_and_sugg(
cx,
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
span.ctxt().outer_expn_data().call_site,
"you are deriving `PartialEq` and can implement `Eq`",
"consider deriving `Eq` as well",
"PartialEq, Eq".to_string(),
Applicability::MachineApplicable,
);
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty};
use if_chain::if_chain;
use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
};
@ -428,25 +427,21 @@ fn lint_for_missing_headers(
span,
"docs for function returning `Result` missing `# Errors` section",
);
} else {
if_chain! {
if let Some(body_id) = body_id;
if let Some(future) = cx.tcx.lang_items().future_trait();
let typeck = cx.tcx.typeck_body(body_id);
let body = cx.tcx.hir().body(body_id);
let ret_ty = typeck.expr_ty(body.value);
if implements_trait(cx, ret_ty, future, &[]);
if let ty::Coroutine(_, subs, _) = ret_ty.kind();
if is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result);
then {
span_lint(
cx,
MISSING_ERRORS_DOC,
span,
"docs for function returning `Result` missing `# Errors` section",
);
}
}
} else if let Some(body_id) = body_id
&& let Some(future) = cx.tcx.lang_items().future_trait()
&& let typeck = cx.tcx.typeck_body(body_id)
&& let body = cx.tcx.hir().body(body_id)
&& let ret_ty = typeck.expr_ty(body.value)
&& implements_trait(cx, ret_ty, future, &[])
&& let ty::Coroutine(_, subs, _) = ret_ty.kind()
&& is_type_diagnostic_item(cx, subs.as_coroutine().return_ty(), sym::Result)
{
span_lint(
cx,
MISSING_ERRORS_DOC,
span,
"docs for function returning `Result` missing `# Errors` section",
);
}
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::peel_blocks;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
@ -36,31 +35,30 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]);
impl LateLintPass<'_> for EmptyDrop {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
..
}) = item.kind;
if trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait();
if let impl_item_hir = child.id.hir_id();
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
let func_expr = peel_blocks(func_expr);
if let ExprKind::Block(block, _) = func_expr.kind;
if block.stmts.is_empty() && block.expr.is_none();
then {
span_lint_and_sugg(
cx,
EMPTY_DROP,
item.span,
"empty drop implementation",
"try removing this impl",
String::new(),
Applicability::MaybeIncorrect
);
}
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
..
}) = item.kind
&& trait_ref.trait_def_id() == cx.tcx.lang_items().drop_trait()
&& let impl_item_hir = child.id.hir_id()
&& let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir)
&& let ImplItemKind::Fn(_, b) = &impl_item.kind
&& let Body { value: func_expr, .. } = cx.tcx.hir().body(*b)
&& let func_expr = peel_blocks(func_expr)
&& let ExprKind::Block(block, _) = func_expr.kind
&& block.stmts.is_empty()
&& block.expr.is_none()
{
span_lint_and_sugg(
cx,
EMPTY_DROP,
item.span,
"empty drop implementation",
"try removing this impl",
String::new(),
Applicability::MaybeIncorrect,
);
}
}
}

View file

@ -114,27 +114,23 @@ impl LateLintPass<'_> for EndianBytes {
return;
}
if_chain! {
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind;
if args.is_empty();
let ty = cx.typeck_results().expr_ty(receiver);
if ty.is_primitive_ty();
if maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty);
then {
return;
}
if let ExprKind::MethodCall(method_name, receiver, args, ..) = expr.kind
&& args.is_empty()
&& let ty = cx.typeck_results().expr_ty(receiver)
&& ty.is_primitive_ty()
&& maybe_lint_endian_bytes(cx, expr, Prefix::To, method_name.ident.name, ty)
{
return;
}
if_chain! {
if let ExprKind::Call(function, ..) = expr.kind;
if let ExprKind::Path(qpath) = function.kind;
if let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id();
if let Some(function_name) = cx.get_def_path(def_id).last();
let ty = cx.typeck_results().expr_ty(expr);
if ty.is_primitive_ty();
then {
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
}
if let ExprKind::Call(function, ..) = expr.kind
&& let ExprKind::Path(qpath) = function.kind
&& let Some(def_id) = cx.qpath_res(&qpath, function.hir_id).opt_def_id()
&& let Some(function_name) = cx.get_def_path(def_id).last()
&& let ty = cx.typeck_results().expr_ty(expr)
&& ty.is_primitive_ty()
{
maybe_lint_endian_bytes(cx, expr, Prefix::From, *function_name, ty);
}
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::indent_of;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -71,40 +70,31 @@ declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]);
impl LateLintPass<'_> for ExhaustiveItems {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if_chain! {
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind;
if cx.effective_visibilities.is_exported(item.owner_id.def_id);
let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
then {
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
if v.fields().iter().any(|f| {
!cx.tcx.visibility(f.def_id).is_public()
}) {
// skip structs with private fields
return;
}
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
} else {
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
};
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
span_lint_and_then(
cx,
lint,
item.span,
msg,
|diag| {
let sugg = format!("#[non_exhaustive]\n{indent}");
diag.span_suggestion(suggestion_span,
"try adding #[non_exhaustive]",
sugg,
Applicability::MaybeIncorrect);
}
if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& let attrs = cx.tcx.hir().attrs(item.hir_id())
&& !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
{
let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
if v.fields().iter().any(|f| !cx.tcx.visibility(f.def_id).is_public()) {
// skip structs with private fields
return;
}
(EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
} else {
(EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
};
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
span_lint_and_then(cx, lint, item.span, msg, |diag| {
let sugg = format!("#[non_exhaustive]\n{indent}");
diag.span_suggestion(
suggestion_span,
"try adding #[non_exhaustive]",
sugg,
Applicability::MaybeIncorrect,
);
}
});
}
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_entrypoint_fn;
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -42,19 +41,17 @@ declare_lint_pass!(Exit => [EXIT]);
impl<'tcx> LateLintPass<'tcx> for Exit {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(path_expr, _args) = e.kind;
if let ExprKind::Path(ref path) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::process_exit, def_id);
let parent = cx.tcx.hir().get_parent_item(e.hir_id).def_id;
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find_by_def_id(parent);
if let ExprKind::Call(path_expr, _args) = e.kind
&& let ExprKind::Path(ref path) = path_expr.kind
&& let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::process_exit, def_id)
&& let parent = cx.tcx.hir().get_parent_item(e.hir_id).def_id
&& let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find_by_def_id(parent)
// If the next item up is a function we check if it is an entry point
// and only then emit a linter warning
if !is_entrypoint_fn(cx, parent.to_def_id());
then {
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
}
&& !is_entrypoint_fn(cx, parent.to_def_id())
{
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
}
}
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, path_def_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
@ -101,30 +100,28 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
/// If `kind` is a block that looks like `{ let result = $expr; result }` then
/// returns $expr. Otherwise returns `kind`.
fn look_in_block<'tcx, 'hir>(cx: &LateContext<'tcx>, kind: &'tcx ExprKind<'hir>) -> &'tcx ExprKind<'hir> {
if_chain! {
if let ExprKind::Block(block, _label @ None) = kind;
if let Block {
if let ExprKind::Block(block, _label @ None) = kind
&& let Block {
stmts: [Stmt { kind: StmtKind::Local(local), .. }],
expr: Some(expr_end_of_block),
rules: BlockCheckMode::DefaultBlock,
..
} = block;
} = block
// Find id of the local that expr_end_of_block resolves to
if let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind;
if let Res::Local(expr_res) = expr_path.res;
if let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res);
&& let ExprKind::Path(QPath::Resolved(None, expr_path)) = expr_end_of_block.kind
&& let Res::Local(expr_res) = expr_path.res
&& let Some(Node::Pat(res_pat)) = cx.tcx.hir().find(expr_res)
// Find id of the local we found in the block
if let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind;
&& let PatKind::Binding(BindingAnnotation::NONE, local_hir_id, _ident, None) = local.pat.kind
// If those two are the same hir id
if res_pat.hir_id == local_hir_id;
&& res_pat.hir_id == local_hir_id
if let Some(init) = local.init;
then {
return &init.kind;
}
&& let Some(init) = local.init
{
return &init.kind;
}
kind
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::method_chain_args;
use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
@ -53,13 +52,13 @@ declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
// check for `impl From<???> for ..`
if_chain! {
if let hir::ItemKind::Impl(impl_) = &item.kind;
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id);
if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id);
then {
lint_impl_body(cx, item.span, impl_.items);
}
if let hir::ItemKind::Impl(impl_) = &item.kind
&& let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id)
&& cx
.tcx
.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id)
{
lint_impl_body(cx, item.span, impl_.items);
}
}
}
@ -98,34 +97,33 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl
}
for impl_item in impl_items {
if_chain! {
if impl_item.ident.name == sym::from;
if let ImplItemKind::Fn(_, body_id) =
cx.tcx.hir().impl_item(impl_item.id).kind;
then {
// check the body for `begin_panic` or `unwrap`
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
result: Vec::new(),
};
fpu.visit_expr(body.value);
if impl_item.ident.name == sym::from
&& let ImplItemKind::Fn(_, body_id) = cx.tcx.hir().impl_item(impl_item.id).kind
{
// check the body for `begin_panic` or `unwrap`
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id),
result: Vec::new(),
};
fpu.visit_expr(body.value);
// if we've found one, lint
if !fpu.result.is_empty() {
span_lint_and_then(
cx,
FALLIBLE_IMPL_FROM,
impl_span,
"consider implementing `TryFrom` instead",
move |diag| {
diag.help(
"`From` is intended for infallible conversions only. \
Use `TryFrom` if there's a possibility for the conversion to fail");
diag.span_note(fpu.result, "potential failure(s)");
});
}
// if we've found one, lint
if !fpu.result.is_empty() {
span_lint_and_then(
cx,
FALLIBLE_IMPL_FROM,
impl_span,
"consider implementing `TryFrom` instead",
move |diag| {
diag.help(
"`From` is intended for infallible conversions only. \
Use `TryFrom` if there's a possibility for the conversion to fail",
);
diag.span_note(fpu.result, "potential failure(s)");
},
);
}
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal;
use if_chain::if_chain;
use rustc_ast::ast::{self, LitFloatType, LitKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -64,73 +63,70 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if_chain! {
if let ty::Float(fty) = *ty.kind();
if let hir::ExprKind::Lit(lit) = expr.kind;
if let LitKind::Float(sym, lit_float_ty) = lit.node;
then {
let sym_str = sym.as_str();
let formatter = FloatFormat::new(sym_str);
// Try to bail out if the float is for sure fine.
// If its within the 2 decimal digits of being out of precision we
// check if the parsed representation is the same as the string
// since we'll need the truncated string anyway.
let digits = count_digits(sym_str);
let max = max_digits(fty);
let type_suffix = match lit_float_ty {
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
LitFloatType::Unsuffixed => None
};
let (is_whole, is_inf, mut float_str) = match fty {
FloatTy::F32 => {
let value = sym_str.parse::<f32>().unwrap();
if let ty::Float(fty) = *ty.kind()
&& let hir::ExprKind::Lit(lit) = expr.kind
&& let LitKind::Float(sym, lit_float_ty) = lit.node
{
let sym_str = sym.as_str();
let formatter = FloatFormat::new(sym_str);
// Try to bail out if the float is for sure fine.
// If its within the 2 decimal digits of being out of precision we
// check if the parsed representation is the same as the string
// since we'll need the truncated string anyway.
let digits = count_digits(sym_str);
let max = max_digits(fty);
let type_suffix = match lit_float_ty {
LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
LitFloatType::Unsuffixed => None,
};
let (is_whole, is_inf, mut float_str) = match fty {
FloatTy::F32 => {
let value = sym_str.parse::<f32>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
FloatTy::F64 => {
let value = sym_str.parse::<f64>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
FloatTy::F64 => {
let value = sym_str.parse::<f64>().unwrap();
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
};
(value.fract() == 0.0, value.is_infinite(), formatter.format(value))
},
};
if is_inf {
return;
}
if is_inf {
return;
}
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
// Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str {
// If the type suffix is missing the suggestion would be
// incorrectly interpreted as an integer so adding a `.0`
// suffix to prevent that.
if type_suffix.is_none() {
float_str.push_str(".0");
}
span_lint_and_sugg(
cx,
LOSSY_FLOAT_LITERAL,
expr.span,
"literal cannot be represented as the underlying type without loss of precision",
"consider changing the type or replacing it with",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
);
if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
// Normalize the literal by stripping the fractional portion
if sym_str.split('.').next().unwrap() != float_str {
// If the type suffix is missing the suggestion would be
// incorrectly interpreted as an integer so adding a `.0`
// suffix to prevent that.
if type_suffix.is_none() {
float_str.push_str(".0");
}
} else if digits > max as usize && float_str.len() < sym_str.len() {
span_lint_and_sugg(
cx,
EXCESSIVE_PRECISION,
LOSSY_FLOAT_LITERAL,
expr.span,
"float has excessive precision",
"consider changing the type or truncating it to",
"literal cannot be represented as the underlying type without loss of precision",
"consider changing the type or replacing it with",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
);
}
} else if digits > max as usize && float_str.len() < sym_str.len() {
span_lint_and_sugg(
cx,
EXCESSIVE_PRECISION,
expr.span,
"float has excessive precision",
"consider changing the type or truncating it to",
numeric_literal::format(&float_str, type_suffix, true),
Applicability::MachineApplicable,
);
}
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{
eq_expr_value, get_parent_expr, higher, in_constant, is_no_std_crate, numeric_literal, peel_blocks, sugg,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -133,30 +132,25 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
expr = inner_expr;
}
if_chain! {
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind()
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind();
if let ExprKind::Lit(lit) = &expr.kind;
if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
then {
let op = format!(
"{suggestion}{}{}",
// Check for float literals without numbers following the decimal
// separator such as `2.` and adds a trailing zero
if sym.as_str().ends_with('.') {
"0"
} else {
""
},
float_ty.name_str()
).into();
&& let ExprKind::Lit(lit) = &expr.kind
&& let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node
{
let op = format!(
"{suggestion}{}{}",
// Check for float literals without numbers following the decimal
// separator such as `2.` and adds a trailing zero
if sym.as_str().ends_with('.') { "0" } else { "" },
float_ty.name_str()
)
.into();
suggestion = match suggestion {
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op)
};
}
suggestion = match suggestion {
Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op),
};
}
suggestion.maybe_par()
@ -359,35 +353,59 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
) = receiver.kind
{
// check if expression of the form x * x + y * y
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
if eq_expr_value(cx, lmul_lhs, lmul_rhs);
if eq_expr_value(cx, rmul_lhs, rmul_rhs);
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, "..").maybe_par(), Sugg::hir(cx, rmul_lhs, "..")));
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lmul_lhs,
lmul_rhs,
) = add_lhs.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
rmul_lhs,
rmul_rhs,
) = add_rhs.kind
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, lmul_lhs, "..").maybe_par(),
Sugg::hir(cx, rmul_lhs, "..")
));
}
// check if expression of the form x.powi(2) + y.powi(2)
if_chain! {
if let ExprKind::MethodCall(
PathSegment { ident: lmethod_name, .. },
largs_0, [largs_1, ..],
_
) = &add_lhs.kind;
if let ExprKind::MethodCall(
PathSegment { ident: rmethod_name, .. },
rargs_0, [rargs_1, ..],
_
) = &add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
if let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1);
if let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1);
if Int(2) == lvalue && Int(2) == rvalue;
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
}
if let ExprKind::MethodCall(
PathSegment {
ident: lmethod_name, ..
},
largs_0,
[largs_1, ..],
_,
) = &add_lhs.kind
&& let ExprKind::MethodCall(
PathSegment {
ident: rmethod_name, ..
},
rargs_0,
[rargs_1, ..],
_,
) = &add_rhs.kind
&& lmethod_name.as_str() == "powi"
&& rmethod_name.as_str() == "powi"
&& let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1)
&& let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1)
&& Int(2) == lvalue
&& Int(2) == rvalue
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, largs_0, "..").maybe_par(),
Sugg::hir(cx, rargs_0, "..")
));
}
}
@ -411,39 +429,44 @@ fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
// and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some(value) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind;
if cx.typeck_results().expr_ty(self_arg).is_floating_point();
if path.ident.name.as_str() == "exp";
then {
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!(
"{}.exp_m1()",
Sugg::hir(cx, self_arg, "..").maybe_par()
),
Applicability::MachineApplicable,
);
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
lhs,
rhs,
) = expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& let Some(value) = constant(cx, cx.typeck_results(), rhs)
&& (F32(1.0) == value || F64(1.0) == value)
&& let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind
&& cx.typeck_results().expr_ty(self_arg).is_floating_point()
&& path.ident.name.as_str() == "exp"
{
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()),
Applicability::MachineApplicable,
);
}
}
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point();
if cx.typeck_results().expr_ty(rhs).is_floating_point();
then {
return Some((lhs, rhs));
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lhs,
rhs,
) = &expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
return Some((lhs, rhs));
}
None
@ -553,60 +576,72 @@ fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a
}
fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr);
let if_body_expr = peel_blocks(then);
let else_body_expr = peel_blocks(r#else);
if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
then {
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
positive_abs_sugg
} else {
negative_abs_sugg
}
} else if is_testing_negative(cx, cond, body) {
if if_expr_positive {
negative_abs_sugg
} else {
positive_abs_sugg
}
if let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let if_body_expr = peel_blocks(then)
&& let else_body_expr = peel_blocks(r#else)
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
{
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
positive_abs_sugg
} else {
return;
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
sugg.0,
"try",
sugg.1,
Applicability::MachineApplicable,
);
}
negative_abs_sugg
}
} else if is_testing_negative(cx, cond, body) {
if if_expr_positive {
negative_abs_sugg
} else {
positive_abs_sugg
}
} else {
return;
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
sugg.0,
"try",
sugg.1,
Applicability::MachineApplicable,
);
}
}
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
then {
return method_name_a.as_str() == method_name_b.as_str() &&
args_a.len() == args_b.len() &&
(
["ln", "log2", "log10"].contains(&method_name_a.as_str()) ||
method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0])
);
}
if let ExprKind::MethodCall(
PathSegment {
ident: method_name_a, ..
},
_,
args_a,
_,
) = expr_a.kind
&& let ExprKind::MethodCall(
PathSegment {
ident: method_name_b, ..
},
_,
args_b,
_,
) = expr_b.kind
{
return method_name_a.as_str() == method_name_b.as_str()
&& args_a.len() == args_b.len()
&& (["ln", "log2", "log10"].contains(&method_name_a.as_str())
|| method_name_a.as_str() == "log" && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
}
false
@ -614,103 +649,98 @@ fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>
fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
// check if expression of the form x.logN() / y.logN()
if_chain! {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
lhs,
rhs,
) = &expr.kind;
if are_same_base_logs(cx, lhs, rhs);
if let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind;
if let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind;
then {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"log base can be expressed more clearly",
"consider using",
format!("{}.log({})", Sugg::hir(cx, largs_self, "..").maybe_par(), Sugg::hir(cx, rargs_self, ".."),),
Applicability::MachineApplicable,
);
}
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
lhs,
rhs,
) = &expr.kind
&& are_same_base_logs(cx, lhs, rhs)
&& let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind
&& let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind
{
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"log base can be expressed more clearly",
"consider using",
format!(
"{}.log({})",
Sugg::hir(cx, largs_self, "..").maybe_par(),
Sugg::hir(cx, rargs_self, ".."),
),
Applicability::MachineApplicable,
);
}
}
fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
div_lhs,
div_rhs,
) = &expr.kind;
if let ExprKind::Binary(
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
div_lhs,
div_rhs,
) = &expr.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
mul_lhs,
mul_rhs,
) = &div_lhs.kind;
if let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs);
if let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs);
then {
// TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
(F32(180_f32) == lvalue || F64(180_f64) == lvalue)
) = &div_lhs.kind
&& let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs)
&& let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs)
{
// TODO: also check for constant values near PI/180 or 180/PI
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());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par());
if_chain! {
if let ExprKind::Lit(literal) = mul_lhs.kind;
if let ast::LitKind::Float(ref value, float_type) = literal.node;
if float_type == ast::LitFloatType::Unsuffixed;
then {
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
}
}
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to degrees can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
} 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());
if_chain! {
if let ExprKind::Lit(literal) = mul_lhs.kind;
if let ast::LitKind::Float(ref value, float_type) = literal.node;
if float_type == ast::LitFloatType::Unsuffixed;
then {
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
}
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to radians can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to degrees can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
} 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());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to radians can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
}
}
}

View file

@ -8,7 +8,6 @@ use clippy_utils::macros::{
};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
@ -404,49 +403,43 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
}
fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Expr<'_>) {
if_chain! {
if !value.span.from_expansion();
if let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
if is_diag_trait_item(cx, method_def_id, sym::ToString);
let receiver_ty = cx.typeck_results().expr_ty(receiver);
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
let (n_needed_derefs, target) =
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter());
if implements_trait(cx, target, display_trait_id, &[]);
if let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait();
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
if n_needed_derefs == 0 && !needs_ref {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
to_string_span.with_lo(receiver.span.hi()),
&format!(
"`to_string` applied to a type that implements `Display` in `{name}!` args"
),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
&format!(
"`to_string` applied to a type that implements `Display` in `{name}!` args"
),
"use this",
format!(
"{}{:*>n_needed_derefs$}{receiver_snippet}",
if needs_ref { "&" } else { "" },
""
),
Applicability::MachineApplicable,
);
}
if !value.span.from_expansion()
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
&& let (n_needed_derefs, target) =
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
&& implements_trait(cx, target, display_trait_id, &[])
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
{
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
if n_needed_derefs == 0 && !needs_ref {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
to_string_span.with_lo(receiver.span.hi()),
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
&format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"use this",
format!(
"{}{:*>n_needed_derefs$}{receiver_snippet}",
if needs_ref { "&" } else { "" },
""
),
Applicability::MachineApplicable,
);
}
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{find_format_arg_expr, find_format_args, 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 if_chain::if_chain;
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
@ -141,27 +140,25 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
}
fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind
// Get the hir_id of the object we are calling the method on
if let ExprKind::MethodCall(path, self_arg, ..) = expr.kind;
// Is the method to_string() ?
if path.ident.name == sym::to_string;
&& path.ident.name == sym::to_string
// Is the method a part of the ToString trait? (i.e. not to_string() implemented
// separately)
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& is_diag_trait_item(cx, expr_def_id, sym::ToString)
// Is the method is called on self
if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind;
if let [segment] = path.segments;
if segment.ident.name == kw::SelfLower;
then {
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
expr.span,
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
);
}
&& let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind
&& let [segment] = path.segments
&& segment.ident.name == kw::SelfLower
{
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
expr.span,
"using `self.to_string` in `fmt::Display` implementation will cause infinite recursion",
);
}
}
@ -215,55 +212,53 @@ fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_
}
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
then {
let replacement = match name {
sym::print_macro | sym::eprint_macro => "write",
sym::println_macro | sym::eprintln_macro => "writeln",
_ => return,
};
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
{
let replacement = match name {
sym::print_macro | sym::eprint_macro => "write",
sym::println_macro | sym::eprintln_macro => "writeln",
_ => return,
};
let name = name.as_str().strip_suffix("_macro").unwrap();
let name = name.as_str().strip_suffix("_macro").unwrap();
span_lint_and_sugg(
cx,
PRINT_IN_FORMAT_IMPL,
macro_call.span,
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
"replace with",
if let Some(formatter_name) = impl_trait.formatter_name {
format!("{replacement}!({formatter_name}, ..)")
} else {
format!("{replacement}!(..)")
},
Applicability::HasPlaceholders,
);
}
span_lint_and_sugg(
cx,
PRINT_IN_FORMAT_IMPL,
macro_call.span,
&format!("use of `{name}!` in `{}` impl", impl_trait.name),
"replace with",
if let Some(formatter_name) = impl_trait.formatter_name {
format!("{replacement}!({formatter_name}, ..)")
} else {
format!("{replacement}!(..)")
},
Applicability::HasPlaceholders,
);
}
}
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
if_chain! {
if impl_item.ident.name == sym::fmt;
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
if let Some(did) = trait_ref.trait_def_id();
if let Some(name) = cx.tcx.get_diagnostic_name(did);
if matches!(name, sym::Debug | sym::Display);
then {
let body = cx.tcx.hir().body(body_id);
let formatter_name = body.params.get(1)
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
if impl_item.ident.name == sym::fmt
&& let ImplItemKind::Fn(_, body_id) = impl_item.kind
&& let Some(Impl {
of_trait: Some(trait_ref),
..
}) = get_parent_as_impl(cx.tcx, impl_item.hir_id())
&& let Some(did) = trait_ref.trait_def_id()
&& let Some(name) = cx.tcx.get_diagnostic_name(did)
&& matches!(name, sym::Debug | sym::Display)
{
let body = cx.tcx.hir().body(body_id);
let formatter_name = body
.params
.get(1)
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
Some(FormatTraitNames {
name,
formatter_name,
})
} else {
None
}
Some(FormatTraitNames { name, formatter_name })
} else {
None
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
use clippy_utils::is_span_if;
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -168,93 +167,84 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
/// Implementation of the `SUSPICIOUS_UNARY_OP_FORMATTING` lint.
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
if_chain! {
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
if !lhs.span.from_expansion() && !rhs.span.from_expansion();
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind
&& !lhs.span.from_expansion() && !rhs.span.from_expansion()
// span between BinOp LHS and RHS
let binop_span = lhs.span.between(rhs.span);
&& let binop_span = lhs.span.between(rhs.span)
// if RHS is an UnOp
if let ExprKind::Unary(op, ref un_rhs) = rhs.kind;
&& let ExprKind::Unary(op, ref un_rhs) = rhs.kind
// from UnOp operator to UnOp operand
let unop_operand_span = rhs.span.until(un_rhs.span);
if let Some(binop_snippet) = snippet_opt(cx, binop_span);
if let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span);
let binop_str = BinOpKind::to_string(&binop.node);
&& let unop_operand_span = rhs.span.until(un_rhs.span)
&& let Some(binop_snippet) = snippet_opt(cx, binop_span)
&& let Some(unop_operand_snippet) = snippet_opt(cx, unop_operand_span)
&& let binop_str = BinOpKind::to_string(&binop.node)
// no space after BinOp operator and space after UnOp operator
if binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ');
then {
let unop_str = UnOp::to_string(op);
let eqop_span = lhs.span.between(un_rhs.span);
span_lint_and_help(
cx,
SUSPICIOUS_UNARY_OP_FORMATTING,
eqop_span,
&format!(
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \
`{binop_str}{unop_str}` is a single operator"
),
None,
&format!(
"put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"
),
);
}
&& binop_snippet.ends_with(binop_str) && unop_operand_snippet.ends_with(' ')
{
let unop_str = UnOp::to_string(op);
let eqop_span = lhs.span.between(un_rhs.span);
span_lint_and_help(
cx,
SUSPICIOUS_UNARY_OP_FORMATTING,
eqop_span,
&format!(
"by not having a space between `{binop_str}` and `{unop_str}` it looks like \
`{binop_str}{unop_str}` is a single operator"
),
None,
&format!("put a space between `{binop_str}` and `{unop_str}` and remove the space after `{unop_str}`"),
);
}
}
/// Implementation of the `SUSPICIOUS_ELSE_FORMATTING` lint for weird `else`.
fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
if_chain! {
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
if is_block(else_) || is_if(else_);
if !then.span.from_expansion() && !else_.span.from_expansion();
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::If(_, then, Some(else_)) = &expr.kind
&& (is_block(else_) || is_if(else_))
&& !then.span.from_expansion() && !else_.span.from_expansion()
&& !in_external_macro(cx.sess(), expr.span)
// workaround for rust-lang/rust#43081
if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
&& expr.span.lo().0 != 0 && expr.span.hi().0 != 0
// this will be a span from the closing } of the “then” block (excluding) to
// the “if” of the “else if” block (excluding)
let else_span = then.span.between(else_.span);
&& let else_span = then.span.between(else_.span)
// the snippet should look like " else \n " with maybe comments anywhere
// its bad when there is a \n after the “else”
if let Some(else_snippet) = snippet_opt(cx, else_span);
if let Some((pre_else, post_else)) = else_snippet.split_once("else");
if let Some((_, post_else_post_eol)) = post_else.split_once('\n');
then {
// Allow allman style braces `} \n else \n {`
if_chain! {
if is_block(else_);
if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n');
// Exactly one eol before and after the else
if !pre_else_post_eol.contains('\n');
if !post_else_post_eol.contains('\n');
then {
return;
}
}
// Don't warn if the only thing inside post_else_post_eol is a comment block.
let trimmed_post_else_post_eol = post_else_post_eol.trim();
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
return
}
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this is an `else {else_desc}` but the formatting might hide it"),
None,
&format!(
"to remove this lint, remove the `else` or remove the new line between \
`else` and `{else_desc}`",
),
);
&& let Some(else_snippet) = snippet_opt(cx, else_span)
&& let Some((pre_else, post_else)) = else_snippet.split_once("else")
&& let Some((_, post_else_post_eol)) = post_else.split_once('\n')
{
// Allow allman style braces `} \n else \n {`
if is_block(else_)
&& let Some((_, pre_else_post_eol)) = pre_else.split_once('\n')
// Exactly one eol before and after the else
&& !pre_else_post_eol.contains('\n')
&& !post_else_post_eol.contains('\n')
{
return;
}
// Don't warn if the only thing inside post_else_post_eol is a comment block.
let trimmed_post_else_post_eol = post_else_post_eol.trim();
if trimmed_post_else_post_eol.starts_with("/*") && trimmed_post_else_post_eol.ends_with("*/") {
return;
}
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this is an `else {else_desc}` but the formatting might hide it"),
None,
&format!(
"to remove this lint, remove the `else` or remove the new line between \
`else` and `{else_desc}`",
),
);
}
}
@ -272,61 +262,56 @@ fn indentation(cx: &EarlyContext<'_>, span: Span) -> usize {
fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Array(ref array) = expr.kind {
for element in array {
if_chain! {
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
if has_unary_equivalent(op.node) && lhs.span.eq_ctxt(op.span);
let space_span = lhs.span.between(op.span);
if let Some(space_snippet) = snippet_opt(cx, space_span);
let lint_span = lhs.span.with_lo(lhs.span.hi());
if space_snippet.contains('\n');
if indentation(cx, op.span) <= indentation(cx, lhs.span);
then {
span_lint_and_note(
cx,
POSSIBLE_MISSING_COMMA,
lint_span,
"possibly missing a comma here",
None,
"to remove this lint, add a comma or write the expr in a single line",
);
}
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind
&& has_unary_equivalent(op.node)
&& lhs.span.eq_ctxt(op.span)
&& let space_span = lhs.span.between(op.span)
&& let Some(space_snippet) = snippet_opt(cx, space_span)
&& let lint_span = lhs.span.with_lo(lhs.span.hi())
&& space_snippet.contains('\n')
&& indentation(cx, op.span) <= indentation(cx, lhs.span)
{
span_lint_and_note(
cx,
POSSIBLE_MISSING_COMMA,
lint_span,
"possibly missing a comma here",
None,
"to remove this lint, add a comma or write the expr in a single line",
);
}
}
}
}
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
if_chain! {
if !first.span.from_expansion() && !second.span.from_expansion();
if matches!(first.kind, ExprKind::If(..));
if is_block(second) || is_if(second);
if !first.span.from_expansion() && !second.span.from_expansion()
&& matches!(first.kind, ExprKind::If(..))
&& (is_block(second) || is_if(second))
// Proc-macros can give weird spans. Make sure this is actually an `if`.
if is_span_if(cx, first.span);
&& is_span_if(cx, first.span)
// If there is a line break between the two expressions, don't lint.
// If there is a non-whitespace character, this span came from a proc-macro.
let else_span = first.span.between(second.span);
if let Some(else_snippet) = snippet_opt(cx, else_span);
if !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace());
then {
let (looks_like, next_thing) = if is_if(second) {
("an `else if`", "the second `if`")
} else {
("an `else {..}`", "the next block")
};
&& let else_span = first.span.between(second.span)
&& let Some(else_snippet) = snippet_opt(cx, else_span)
&& !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace())
{
let (looks_like, next_thing) = if is_if(second) {
("an `else if`", "the second `if`")
} else {
("an `else {..}`", "the next block")
};
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this looks like {looks_like} but the `else` is missing"),
None,
&format!(
"to remove this lint, add the missing `else` or add a new line before {next_thing}",
),
);
}
span_lint_and_note(
cx,
SUSPICIOUS_ELSE_FORMATTING,
else_span,
&format!("this looks like {looks_like} but the `else` is missing"),
None,
&format!("to remove this lint, add the missing `else` or add a new line before {next_thing}",),
);
}
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_integer_literal;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
@ -46,52 +45,41 @@ declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
if_chain! {
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind
// check if the first part of the path is some integer primitive
if let TyKind::Path(ty_qpath) = &ty.kind;
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
if let def::Res::PrimTy(prim_ty) = ty_res;
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
&& let TyKind::Path(ty_qpath) = &ty.kind
&& let ty_res = cx.qpath_res(ty_qpath, ty.hir_id)
&& let def::Res::PrimTy(prim_ty) = ty_res
&& matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_))
// check if the second part of the path indeed calls the associated
// function `from_str_radix`
if pathseg.ident.name.as_str() == "from_str_radix";
&& pathseg.ident.name.as_str() == "from_str_radix"
// check if the second argument is a primitive `10`
if is_integer_literal(radix, 10);
&& is_integer_literal(radix, 10)
{
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) { expr } else { &src }
} else {
&src
};
then {
let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) {
expr
} else {
&src
}
} else {
&src
};
let sugg =
Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par();
let sugg = Sugg::hir_with_applicability(
cx,
expr,
"<string>",
&mut Applicability::MachineApplicable
).maybe_par();
span_lint_and_sugg(
cx,
FROM_STR_RADIX_10,
exp.span,
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
"try",
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
Applicability::MaybeIncorrect
);
}
span_lint_and_sugg(
cx,
FROM_STR_RADIX_10,
exp.span,
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
"try",
format!("{sugg}.parse::<{}>()", prim_ty.name_str()),
Applicability::MaybeIncorrect,
);
}
}
}

View file

@ -52,54 +52,48 @@ fn report(
}
pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
if_chain! {
if let FnKind::ItemFn(ident, generics, _) = kind;
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
if !is_in_test_function(cx.tcx, hir_id);
then {
for param in generics.params {
if param.is_impl_trait() {
report(cx, param, ident, generics, body.params[0].span);
};
}
if let FnKind::ItemFn(ident, generics, _) = kind
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
&& !is_in_test_function(cx.tcx, hir_id)
{
for param in generics.params {
if param.is_impl_trait() {
report(cx, param, ident, generics, body.params[0].span);
};
}
}
}
pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
if_chain! {
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id());
if let hir::ItemKind::Impl(impl_) = item.kind;
if let hir::Impl { of_trait, .. } = *impl_;
if of_trait.is_none();
let body = cx.tcx.hir().body(body_id);
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
if !is_in_test_function(cx.tcx, impl_item.hir_id());
then {
for param in impl_item.generics.params {
if param.is_impl_trait() {
report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
}
if let ImplItemKind::Fn(_, body_id) = impl_item.kind
&& let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id())
&& let hir::ItemKind::Impl(impl_) = item.kind
&& let hir::Impl { of_trait, .. } = *impl_
&& of_trait.is_none()
&& let body = cx.tcx.hir().body(body_id)
&& cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public()
&& !is_in_test_function(cx.tcx, impl_item.hir_id())
{
for param in impl_item.generics.params {
if param.is_impl_trait() {
report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
}
}
}
}
pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
if_chain! {
if !avoid_breaking_exported_api;
if let TraitItemKind::Fn(_, _) = trait_item.kind;
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id());
if !avoid_breaking_exported_api
&& let TraitItemKind::Fn(_, _) = trait_item.kind
&& let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id())
// ^^ (Will always be a trait)
if !item.vis_span.is_empty(); // Is public
if !is_in_test_function(cx.tcx, trait_item.hir_id());
then {
for param in trait_item.generics.params {
if param.is_impl_trait() {
let sp = trait_item.ident.span.with_hi(trait_item.ident.span.hi() + BytePos(1));
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
}
&& !item.vis_span.is_empty() // Is public
&& !is_in_test_function(cx.tcx, trait_item.hir_id())
{
for param in trait_item.generics.params {
if param.is_impl_trait() {
let sp = trait_item.ident.span.with_hi(trait_item.ident.span.hi() + BytePos(1));
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
}
}
}

View file

@ -43,15 +43,13 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
// Body must be &(mut) <self_data>.name
// self_data is not necessarily self, to also lint sub-getters, etc…
let block_expr = if_chain! {
if let ExprKind::Block(block,_) = body.value.kind;
if block.stmts.is_empty();
if let Some(block_expr) = block.expr;
then {
block_expr
} else {
return;
}
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(block_expr) = block.expr
{
block_expr
} else {
return;
};
let expr_span = block_expr.span;
@ -61,14 +59,12 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
} else {
block_expr
};
let (self_data, used_ident) = if_chain! {
if let ExprKind::Field(self_data, ident) = expr.kind;
if ident.name.as_str() != name;
then {
(self_data, ident)
} else {
return;
}
let (self_data, used_ident) = if let ExprKind::Field(self_data, ident) = expr.kind
&& ident.name.as_str() != name
{
(self_data, ident)
} else {
return;
};
let mut used_field = None;

View file

@ -86,59 +86,60 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S
}
fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
if_chain! {
if let Adt(adt, subst) = err_ty.kind();
if let Some(local_def_id) = err_ty.ty_adt_def().expect("already checked this is adt").did().as_local();
if let Some(hir::Node::Item(item)) = cx
.tcx
.hir()
.find_by_def_id(local_def_id);
if let hir::ItemKind::Enum(ref def, _) = item.kind;
then {
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
if let Some((first_variant, variants)) = variants_size.split_first()
&& first_variant.size >= large_err_threshold
{
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag| {
diag.span_label(
def.variants[first_variant.ind].span,
format!("the largest variant contains at least {} bytes", variants_size[0].size),
);
if let Adt(adt, subst) = err_ty.kind()
&& let Some(local_def_id) = err_ty
.ty_adt_def()
.expect("already checked this is adt")
.did()
.as_local()
&& let Some(hir::Node::Item(item)) = cx.tcx.hir().find_by_def_id(local_def_id)
&& let hir::ItemKind::Enum(ref def, _) = item.kind
{
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
if let Some((first_variant, variants)) = variants_size.split_first()
&& first_variant.size >= large_err_threshold
{
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag| {
diag.span_label(
def.variants[first_variant.ind].span,
format!("the largest variant contains at least {} bytes", variants_size[0].size),
);
for variant in variants {
if variant.size >= large_err_threshold {
let variant_def = &def.variants[variant.ind];
diag.span_label(
variant_def.span,
format!("the variant `{}` contains at least {} bytes", variant_def.ident, variant.size),
);
}
for variant in variants {
if variant.size >= large_err_threshold {
let variant_def = &def.variants[variant.ind];
diag.span_label(
variant_def.span,
format!(
"the variant `{}` contains at least {} bytes",
variant_def.ident, variant.size
),
);
}
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
}
);
}
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
},
);
}
else {
let ty_size = approx_ty_size(cx, err_ty);
if ty_size >= large_err_threshold {
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag: &mut Diagnostic| {
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
},
);
}
} else {
let ty_size = approx_ty_size(cx, err_ty);
if ty_size >= large_err_threshold {
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
hir_ty_span,
"the `Err`-variant returned from this function is very large",
|diag: &mut Diagnostic| {
diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
},
);
}
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{higher, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Diagnostic;
use rustc_hir::intravisit::{self as visit, Visitor};
use rustc_hir::{Expr, ExprKind};
@ -127,15 +126,13 @@ impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
}
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind;
if path.ident.as_str() == "lock";
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if is_type_diagnostic_item(cx, ty, sym::Mutex);
then {
Some(self_arg)
} else {
None
}
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
&& path.ident.as_str() == "lock"
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& is_type_diagnostic_item(cx, ty, sym::Mutex)
{
Some(self_arg)
} else {
None
}
}

View file

@ -13,8 +13,6 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::sym;
use if_chain::if_chain;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
@ -337,42 +335,38 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(fun, args) = e.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
if let Some(ty_did) = ty_path.res.opt_def_id();
then {
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
return;
}
if let ExprKind::Call(fun, args) = e.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind
&& let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind
&& let Some(ty_did) = ty_path.res.opt_def_id()
{
if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) {
return;
}
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashMap::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashMap::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashSet::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashSet::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashMap::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashMap::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
} else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
if method.ident.name == sym::new {
self.suggestions.insert(e.span, "HashSet::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashSet::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
}
}

View file

@ -2,7 +2,6 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
@ -40,42 +39,58 @@ declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let ExprKind::If(cond, then, None) = expr.kind;
if let ExprKind::DropTemps(expr1) = cond.kind;
if let Some((c, op_node, l)) = get_const(cx, expr1);
if let BinOpKind::Ne | BinOpKind::Lt = op_node;
if let ExprKind::Block(block, None) = then.kind;
if let Block {
if let ExprKind::If(cond, then, None) = expr.kind
&& let ExprKind::DropTemps(expr1) = cond.kind
&& let Some((c, op_node, l)) = get_const(cx, expr1)
&& let BinOpKind::Ne | BinOpKind::Lt = op_node
&& let ExprKind::Block(block, None) = then.kind
&& let Block {
stmts:
[Stmt
{ kind: StmtKind::Expr(ex) | StmtKind::Semi(ex), .. }],
expr: None, ..} |
Block { stmts: [], expr: Some(ex), ..} = block;
if let ExprKind::AssignOp(op1, target, value) = ex.kind;
let ty = cx.typeck_results().expr_ty(target);
if Some(c) == get_int_max(ty);
let ctxt = expr.span.ctxt();
if ex.span.ctxt() == ctxt;
if expr1.span.ctxt() == ctxt;
if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target);
if BinOpKind::Add == op1.node;
if let ExprKind::Lit(lit) = value.kind;
if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node;
if block.expr.is_none();
then {
let mut app = Applicability::MachineApplicable;
let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
let sugg = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
&& else_.hir_id == expr.hir_id
{
format!("{{{code} = {code}.saturating_add(1); }}")
} else {
format!("{code} = {code}.saturating_add(1);")
};
span_lint_and_sugg(cx, IMPLICIT_SATURATING_ADD, expr.span, "manual saturating add detected", "use instead", sugg, app);
[
Stmt {
kind: StmtKind::Expr(ex) | StmtKind::Semi(ex),
..
},
],
expr: None,
..
}
| Block {
stmts: [],
expr: Some(ex),
..
} = block
&& let ExprKind::AssignOp(op1, target, value) = ex.kind
&& let ty = cx.typeck_results().expr_ty(target)
&& Some(c) == get_int_max(ty)
&& let ctxt = expr.span.ctxt()
&& ex.span.ctxt() == ctxt
&& expr1.span.ctxt() == ctxt
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
&& BinOpKind::Add == op1.node
&& let ExprKind::Lit(lit) = value.kind
&& let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node
&& block.expr.is_none()
{
let mut app = Applicability::MachineApplicable;
let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
let sugg = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
&& else_.hir_id == expr.hir_id
{
format!("{{{code} = {code}.saturating_add(1); }}")
} else {
format!("{code} = {code}.saturating_add(1);")
};
span_lint_and_sugg(
cx,
IMPLICIT_SATURATING_ADD,
expr.span,
"manual saturating add detected",
"use instead",
sugg,
app,
);
}
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{higher, is_integer_literal, peel_blocks_with_stmt, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
@ -46,83 +45,76 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
if expr.span.from_expansion() {
return;
}
if_chain! {
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr)
// Check if the conditional expression is a binary operation
if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
&& let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind
// Ensure that the binary operator is >, !=, or <
if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
&& (BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node)
// Check if assign operation is done
if let Some(target) = subtracts_one(cx, then);
&& let Some(target) = subtracts_one(cx, then)
// Extracting out the variable name
if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
then {
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_left, cond_right)
} else {
return;
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_right, cond_left)
} else {
return;
}
&& let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind
{
// Handle symmetric conditions in the if statement
let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_left, cond_right)
} else {
return;
};
// Check if the variable in the condition statement is an integer
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
}
} else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
(cond_right, cond_left)
} else {
return;
}
} else {
return;
};
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
match cond_num_val.kind {
ExprKind::Lit(cond_lit) => {
// Check if the constant is zero
if let LitKind::Int(0, _) = cond_lit.node {
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
};
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if_chain! {
if name.ident.as_str() == "MIN";
if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(const_id);
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral();
then {
print_lint_and_sugg(cx, var_name, expr)
}
}
},
ExprKind::Call(func, []) => {
if_chain! {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind;
if name.ident.as_str() == "min_value";
if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(func_id);
if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl
if cx.tcx.type_of(impl_id).instantiate_identity().is_integral();
then {
print_lint_and_sugg(cx, var_name, expr)
}
}
},
_ => (),
}
// Check if the variable in the condition statement is an integer
if !cx.typeck_results().expr_ty(cond_var).is_integral() {
return;
}
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
match cond_num_val.kind {
ExprKind::Lit(cond_lit) => {
// Check if the constant is zero
if let LitKind::Int(0, _) = cond_lit.node {
if cx.typeck_results().expr_ty(cond_left).is_signed() {
} else {
print_lint_and_sugg(cx, var_name, expr);
};
}
},
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if name.ident.as_str() == "MIN"
&& let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(const_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
ExprKind::Call(func, []) => {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind
&& name.ident.as_str() == "min_value"
&& let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(func_id)
&& let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl
&& cx.tcx.type_of(impl_id).instantiate_identity().is_integral()
{
print_lint_and_sugg(cx, var_name, expr);
}
},
_ => (),
}
}
}
@ -135,18 +127,14 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp
(BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target)
},
ExprKind::Assign(target, value, _) => {
if_chain! {
if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
if BinOpKind::Sub == op1.node;
if SpanlessEq::new(cx).eq_expr(left1, target);
if is_integer_literal(right1, 1);
then {
Some(target)
} else {
None
}
if let ExprKind::Binary(ref op1, left1, right1) = value.kind
&& BinOpKind::Sub == op1.node
&& SpanlessEq::new(cx).eq_expr(left1, target)
&& is_integer_literal(right1, 1)
{
Some(target)
} else {
None
}
},
_ => None,

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind};
@ -66,54 +65,53 @@ declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRU
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if !expr.span.from_expansion();
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
let ty = cx.typeck_results().expr_ty(expr);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants().iter().next();
if fields.iter().all(|f| f.is_shorthand);
then {
let mut def_order_map = FxHashMap::default();
for (idx, field) in variant.fields.iter().enumerate() {
def_order_map.insert(field.name, idx);
}
if is_consistent_order(fields, &def_order_map) {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!("{} {{ {fields_snippet}{base_snippet} }}",
snippet(cx, qpath.span(), ".."),
);
span_lint_and_sugg(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
"struct constructor field order is inconsistent with struct definition field order",
"try",
sugg,
Applicability::MachineApplicable,
)
if !expr.span.from_expansion()
&& let ExprKind::Struct(qpath, fields, base) = expr.kind
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_struct()
&& let Some(variant) = adt_def.variants().iter().next()
&& fields.iter().all(|f| f.is_shorthand)
{
let mut def_order_map = FxHashMap::default();
for (idx, field) in variant.fields.iter().enumerate() {
def_order_map.insert(field.name, idx);
}
if is_consistent_order(fields, &def_order_map) {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!(
"{} {{ {fields_snippet}{base_snippet} }}",
snippet(cx, qpath.span(), ".."),
);
span_lint_and_sugg(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
"struct constructor field order is inconsistent with struct definition field order",
"try",
sugg,
Applicability::MachineApplicable,
);
}
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLet;
use clippy_utils::ty::is_copy;
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local};
use if_chain::if_chain;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -70,20 +69,17 @@ impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
if self.msrv.meets(msrvs::SLICE_PATTERNS);
let found_slices = find_slice_values(cx, let_pat);
if !found_slices.is_empty();
let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then);
if !filtered_slices.is_empty();
then {
for slice in filtered_slices.values() {
lint_slice(cx, slice);
}
if (!expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some())
&& let Some(IfLet { let_pat, if_then, .. }) = IfLet::hir(cx, expr)
&& !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id)
&& self.msrv.meets(msrvs::SLICE_PATTERNS)
&& let found_slices = find_slice_values(cx, let_pat)
&& !found_slices.is_empty()
&& let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then)
&& !filtered_slices.is_empty()
{
for slice in filtered_slices.values() {
lint_slice(cx, slice);
}
}
}
@ -245,28 +241,26 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
max_suggested_slice,
} = *self;
if_chain! {
if let Some(use_info) = slice_lint_info.get_mut(&local_id)
// Check if this is even a local we're interested in
if let Some(use_info) = slice_lint_info.get_mut(&local_id);
let map = cx.tcx.hir();
&& let map = cx.tcx.hir()
// Checking for slice indexing
let parent_id = map.parent_id(expr.hir_id);
if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
if let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind;
if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr);
if let Ok(index_value) = index_value.try_into();
if index_value < max_suggested_slice;
&& let parent_id = map.parent_id(expr.hir_id)
&& let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id)
&& let hir::ExprKind::Index(_, index_expr, _) = parent_expr.kind
&& let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr)
&& let Ok(index_value) = index_value.try_into()
&& index_value < max_suggested_slice
// Make sure that this slice index is read only
let maybe_addrof_id = map.parent_id(parent_id);
if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id);
if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind;
then {
use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
return;
}
&& let maybe_addrof_id = map.parent_id(parent_id)
&& let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id)
&& let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind
{
use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
return;
}
// The slice was used for something other than indexing

View file

@ -89,27 +89,17 @@ impl LateLintPass<'_> for InstantSubtraction {
rhs,
) = expr.kind
{
if_chain! {
if is_instant_now_call(cx, lhs);
if is_an_instant(cx, rhs);
if let Some(sugg) = Sugg::hir_opt(cx, rhs);
then {
print_manual_instant_elapsed_sugg(cx, expr, sugg)
} else {
if_chain! {
if !expr.span.from_expansion();
if self.msrv.meets(msrvs::TRY_FROM);
if is_an_instant(cx, lhs);
if is_a_duration(cx, rhs);
then {
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr)
}
}
}
if is_instant_now_call(cx, lhs)
&& is_an_instant(cx, rhs)
&& let Some(sugg) = Sugg::hir_opt(cx, rhs)
{
print_manual_instant_elapsed_sugg(cx, expr, sugg);
} else if !expr.span.from_expansion()
&& self.msrv.meets(msrvs::TRY_FROM)
&& is_an_instant(cx, lhs)
&& is_a_duration(cx, rhs)
{
print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr);
}
}
}

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_then;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -47,43 +46,40 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if !item.span.from_expansion();
if let ItemKind::Const(_, generics, _) = &item.kind;
if !item.span.from_expansion()
&& let ItemKind::Const(_, generics, _) = &item.kind
// Since static items may not have generics, skip generic const items.
// FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it
// doesn't account for empty where-clauses that only consist of keyword `where` IINM.
if generics.params.is_empty() && !generics.has_where_clause_predicates;
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
if let ty::Array(element_type, cst) = ty.kind();
if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind();
if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx);
if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes());
if self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size);
then {
let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
let sugg_span = Span::new(
hi_pos - BytePos::from_usize("const".len()),
hi_pos,
item.span.ctxt(),
item.span.parent(),
);
span_lint_and_then(
cx,
LARGE_CONST_ARRAYS,
item.span,
"large array defined as const",
|diag| {
diag.span_suggestion(
sugg_span,
"make this a static item",
"static",
Applicability::MachineApplicable,
);
}
);
}
&& generics.params.is_empty() && !generics.has_where_clause_predicates
&& let ty = cx.tcx.type_of(item.owner_id).instantiate_identity()
&& let ty::Array(element_type, cst) = ty.kind()
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
&& let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes())
&& self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size)
{
let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
let sugg_span = Span::new(
hi_pos - BytePos::from_usize("const".len()),
hi_pos,
item.span.ctxt(),
item.span.parent(),
);
span_lint_and_then(
cx,
LARGE_CONST_ARRAYS,
item.span,
"large array defined as const",
|diag| {
diag.span_suggestion(
sugg_span,
"make this a static item",
"static",
Applicability::MachineApplicable,
);
},
);
}
}
}

View file

@ -50,37 +50,35 @@ impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]);
impl LateLintPass<'_> for LargeIncludeFile {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id);
if cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id);
if let ExprKind::Lit(lit) = &expr.kind;
then {
let len = match &lit.node {
// include_bytes
LitKind::ByteStr(bstr, _) => bstr.len(),
// include_str
LitKind::Str(sym, _) => sym.as_str().len(),
_ => return,
};
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id)
&& (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id)
|| cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id))
&& let ExprKind::Lit(lit) = &expr.kind
{
let len = match &lit.node {
// include_bytes
LitKind::ByteStr(bstr, _) => bstr.len(),
// include_str
LitKind::Str(sym, _) => sym.as_str().len(),
_ => return,
};
if len as u64 <= self.max_file_size {
return;
}
span_lint_and_note(
cx,
LARGE_INCLUDE_FILE,
expr.span,
"attempted to include a large file",
None,
&format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
),
);
if len as u64 <= self.max_file_size {
return;
}
span_lint_and_note(
cx,
LARGE_INCLUDE_FILE,
expr.span,
"attempted to include a large file",
None,
&format!(
"the configuration allows a maximum size of {} bytes",
self.max_file_size
),
);
}
}
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -131,37 +130,33 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
if_chain! {
if item.ident.name == sym::len;
if let ImplItemKind::Fn(sig, _) = &item.kind;
if sig.decl.implicit_self.has_implicit_self();
if sig.decl.inputs.len() == 1;
if cx.effective_visibilities.is_exported(item.owner_id.def_id);
if matches!(sig.decl.output, FnRetTy::Return(_));
if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
if imp.of_trait.is_none();
if let TyKind::Path(ty_path) = &imp.self_ty.kind;
if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
if let Some(local_id) = ty_id.as_local();
let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
if let Some(output) = parse_len_output(
cx,
cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder()
);
then {
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
Some(Node::Item(x)) => match x.kind {
ItemKind::Struct(..) => (x.ident.name, "struct"),
ItemKind::Enum(..) => (x.ident.name, "enum"),
ItemKind::Union(..) => (x.ident.name, "union"),
_ => (x.ident.name, "type"),
}
_ => return,
};
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind)
}
if item.ident.name == sym::len
&& let ImplItemKind::Fn(sig, _) = &item.kind
&& sig.decl.implicit_self.has_implicit_self()
&& sig.decl.inputs.len() == 1
&& cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& matches!(sig.decl.output, FnRetTy::Return(_))
&& let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id())
&& imp.of_trait.is_none()
&& let TyKind::Path(ty_path) = &imp.self_ty.kind
&& 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.hir().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())
{
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
Some(Node::Item(x)) => match x.kind {
ItemKind::Struct(..) => (x.ident.name, "struct"),
ItemKind::Enum(..) => (x.ident.name, "enum"),
ItemKind::Union(..) => (x.ident.name, "union"),
_ => (x.ident.name, "type"),
},
_ => return,
};
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
}
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local_id;
use clippy_utils::source::snippet;
use clippy_utils::visitors::is_local_used;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{BindingAnnotation, Mutability};
@ -61,76 +60,85 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
let mut it = block.stmts.iter().peekable();
while let Some(stmt) = it.next() {
if_chain! {
if let Some(expr) = it.peek();
if let hir::StmtKind::Local(local) = stmt.kind;
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
if let hir::StmtKind::Expr(if_) = expr.kind;
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
if !is_local_used(cx, *cond, canonical_id);
if let hir::ExprKind::Block(then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, then);
if !is_local_used(cx, value, canonical_id);
then {
let span = stmt.span.to(if_.span);
if let Some(expr) = it.peek()
&& let hir::StmtKind::Local(local) = stmt.kind
&& let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
&& let hir::StmtKind::Expr(if_) = expr.kind
&& let hir::ExprKind::If(
hir::Expr {
kind: hir::ExprKind::DropTemps(cond),
..
},
then,
else_,
) = if_.kind
&& !is_local_used(cx, *cond, canonical_id)
&& let hir::ExprKind::Block(then, _) = then.kind
&& let Some(value) = check_assign(cx, canonical_id, then)
&& !is_local_used(cx, value, canonical_id)
{
let span = stmt.span.to(if_.span);
let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze(
cx.tcx,
cx.param_env,
);
if has_interior_mutability { return; }
let has_interior_mutability = !cx
.typeck_results()
.node_type(canonical_id)
.is_freeze(cx.tcx, cx.param_env);
if has_interior_mutability {
return;
}
let (default_multi_stmts, default) = if let Some(else_) = else_ {
if let hir::ExprKind::Block(else_, _) = else_.kind {
if let Some(default) = check_assign(cx, canonical_id, else_) {
(else_.stmts.len() > 1, default)
} else if let Some(default) = local.init {
(true, default)
} else {
continue;
}
let (default_multi_stmts, default) = if let Some(else_) = else_ {
if let hir::ExprKind::Block(else_, _) = else_.kind {
if let Some(default) = check_assign(cx, canonical_id, else_) {
(else_.stmts.len() > 1, default)
} else if let Some(default) = local.init {
(true, default)
} else {
continue;
}
} else if let Some(default) = local.init {
(false, default)
} else {
continue;
};
}
} else if let Some(default) = local.init {
(false, default)
} else {
continue;
};
let mutability = match mode {
BindingAnnotation(_, Mutability::Mut) => "<mut> ",
_ => "",
};
let mutability = match mode {
BindingAnnotation(_, Mutability::Mut) => "<mut> ",
_ => "",
};
// FIXME: this should not suggest `mut` if we can detect that the variable is not
// use mutably after the `if`
// FIXME: this should not suggest `mut` if we can detect that the variable is not
// use mutably after the `if`
let sug = format!(
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
name=ident.name,
cond=snippet(cx, cond.span, "_"),
then=if then.stmts.len() > 1 { " ..;" } else { "" },
else=if default_multi_stmts { " ..;" } else { "" },
value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"),
);
span_lint_and_then(cx,
USELESS_LET_IF_SEQ,
span,
"`if _ { .. } else { .. }` is an expression",
|diag| {
diag.span_suggestion(
span,
"it is more idiomatic to write",
sug,
Applicability::HasPlaceholders,
);
if !mutability.is_empty() {
diag.note("you might not need `mut` at all");
}
});
}
let sug = format!(
"let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
name=ident.name,
cond=snippet(cx, cond.span, "_"),
then=if then.stmts.len() > 1 { " ..;" } else { "" },
else=if default_multi_stmts { " ..;" } else { "" },
value=snippet(cx, value.span, "<value>"),
default=snippet(cx, default.span, "<default>"),
);
span_lint_and_then(
cx,
USELESS_LET_IF_SEQ,
span,
"`if _ { .. } else { .. }` is an expression",
|diag| {
diag.span_suggestion(
span,
"it is more idiomatic to write",
sug,
Applicability::HasPlaceholders,
);
if !mutability.is_empty() {
diag.note("you might not need `mut` at all");
}
},
);
}
}
}
@ -141,20 +149,23 @@ fn check_assign<'tcx>(
decl: hir::HirId,
block: &'tcx hir::Block<'_>,
) -> Option<&'tcx hir::Expr<'tcx>> {
if_chain! {
if block.expr.is_none();
if let Some(expr) = block.stmts.iter().last();
if let hir::StmtKind::Semi(expr) = expr.kind;
if let hir::ExprKind::Assign(var, value, _) = expr.kind;
if path_to_local_id(var, decl);
then {
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
None
} else {
Some(value)
}
} else {
if block.expr.is_none()
&& let Some(expr) = block.stmts.iter().last()
&& let hir::StmtKind::Semi(expr) = expr.kind
&& let hir::ExprKind::Assign(var, value, _) = expr.kind
&& path_to_local_id(var, decl)
{
if block
.stmts
.iter()
.take(block.stmts.len() - 1)
.any(|stmt| is_local_used(cx, stmt, decl))
{
None
} else {
Some(value)
}
} else {
None
}
}

View file

@ -27,27 +27,25 @@ declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
impl LateLintPass<'_> for UnderscoreTyped {
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
if_chain! {
if !in_external_macro(cx.tcx.sess, local.span);
if let Some(ty) = local.ty; // Ensure that it has a type defined
if let TyKind::Infer = &ty.kind; // that type is '_'
if local.span.eq_ctxt(ty.span);
then {
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
if snippet(cx, ty.span, "_").trim() != "_" {
return;
}
span_lint_and_help(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration"
)
if !in_external_macro(cx.tcx.sess, local.span)
&& let Some(ty) = local.ty // Ensure that it has a type defined
&& let TyKind::Infer = &ty.kind // that type is '_'
&& local.span.eq_ctxt(ty.span)
{
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`
if snippet(cx, ty.span, "_").trim() != "_" {
return;
}
span_lint_and_help(
cx,
LET_WITH_TYPE_UNDERSCORE,
local.span,
"variable declared with type underscore",
Some(ty.span.with_lo(local.pat.span.hi())),
"remove the explicit type `_` declaration",
);
};
}
}

View file

@ -521,7 +521,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| {
Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new())
});
store.register_late_pass(|_| Box::new(utils::internal_lints::if_chain_style::IfChainStyle));
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()

View file

@ -310,20 +310,17 @@ fn elision_suggestions(
// elision doesn't work for explicit self types, see rust-lang/rust#69064
fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool {
if_chain! {
if let Some(ident) = ident;
if ident.name == kw::SelfLower;
if !func.implicit_self.has_implicit_self();
if let Some(ident) = ident
&& ident.name == kw::SelfLower
&& !func.implicit_self.has_implicit_self()
&& let Some(self_ty) = func.inputs.first()
{
let mut visitor = RefVisitor::new(cx);
visitor.visit_ty(self_ty);
if let Some(self_ty) = func.inputs.first();
then {
let mut visitor = RefVisitor::new(cx);
visitor.visit_ty(self_ty);
!visitor.all_lts().is_empty()
} else {
false
}
!visitor.all_lts().is_empty()
} else {
false
}
}

View file

@ -4,7 +4,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::{NumericLiteral, Radix};
use clippy_utils::source::snippet_opt;
use if_chain::if_chain;
use rustc_ast::ast::{Expr, ExprKind, LitKind};
use rustc_ast::token;
use rustc_errors::Applicability;
@ -255,56 +254,48 @@ impl LiteralDigitGrouping {
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
if_chain! {
if let Some(src) = snippet_opt(cx, span);
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
if let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
then {
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
return;
}
if let Some(src) = snippet_opt(cx, span)
&& let Ok(lit_kind) = LitKind::from_token_lit(lit)
&& let Some(mut num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
{
if !Self::check_for_mistyped_suffix(cx, span, &mut num_lit) {
return;
}
if Self::is_literal_uuid_formatted(&num_lit) {
return;
}
if Self::is_literal_uuid_formatted(&num_lit) {
return;
}
let result = (|| {
let result = (|| {
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
if let Some(fraction) = num_lit.fraction {
let fractional_group_size =
Self::get_group_size(fraction.rsplit('_'), num_lit.radix, self.lint_fraction_readability)?;
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
if let Some(fraction) = num_lit.fraction {
let fractional_group_size = Self::get_group_size(
fraction.rsplit('_'),
num_lit.radix,
self.lint_fraction_readability)?;
let consistent = Self::parts_consistent(integral_group_size,
fractional_group_size,
num_lit.integer.len(),
fraction.len());
if !consistent {
return Err(WarningType::InconsistentDigitGrouping);
};
}
Ok(())
})();
if let Err(warning_type) = result {
let should_warn = match warning_type {
| WarningType::UnreadableLiteral
| WarningType::InconsistentDigitGrouping
| WarningType::UnusualByteGroupings
| WarningType::LargeDigitGroups => {
!span.from_expansion()
}
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
true
}
let consistent = Self::parts_consistent(
integral_group_size,
fractional_group_size,
num_lit.integer.len(),
fraction.len(),
);
if !consistent {
return Err(WarningType::InconsistentDigitGrouping);
};
if should_warn {
warning_type.display(num_lit.format(), cx, span);
}
}
Ok(())
})();
if let Err(warning_type) = result {
let should_warn = match warning_type {
WarningType::UnreadableLiteral
| WarningType::InconsistentDigitGrouping
| WarningType::UnusualByteGroupings
| WarningType::LargeDigitGroups => !span.from_expansion(),
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true,
};
if should_warn {
warning_type.display(num_lit.format(), cx, span);
}
}
}
@ -478,20 +469,18 @@ impl DecimalLiteralRepresentation {
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
// Lint integral literals.
if_chain! {
if let Ok(lit_kind) = LitKind::from_token_lit(lit);
if let LitKind::Int(val, _) = lit_kind;
if let Some(src) = snippet_opt(cx, span);
if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind);
if num_lit.radix == Radix::Decimal;
if val >= u128::from(self.threshold);
then {
let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, span);
});
}
if let Ok(lit_kind) = LitKind::from_token_lit(lit)
&& let LitKind::Int(val, _) = lit_kind
&& let Some(src) = snippet_opt(cx, span)
&& let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit_kind)
&& num_lit.radix == Radix::Decimal
&& val >= u128::from(self.threshold)
{
let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, span);
});
}
}

View file

@ -2,7 +2,6 @@ use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_enclosing_block, is_integer_const};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr};
use rustc_hir::{Expr, Pat};
@ -30,59 +29,57 @@ pub(super) fn check<'tcx>(
let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
walk_block(&mut initialize_visitor, block);
if_chain! {
if let Some((name, ty, initializer)) = initialize_visitor.get_result();
if is_integer_const(cx, initializer, 0);
then {
let mut applicability = Applicability::MaybeIncorrect;
let span = expr.span.with_hi(arg.span.hi());
if let Some((name, ty, initializer)) = initialize_visitor.get_result()
&& is_integer_const(cx, initializer, 0)
{
let mut applicability = Applicability::MaybeIncorrect;
let span = expr.span.with_hi(arg.span.hi());
let int_name = match ty.map(Ty::kind) {
// usize or inferred
Some(ty::Uint(UintTy::Usize)) | None => {
span_lint_and_sugg(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
"consider using",
format!(
"for ({name}, {}) in {}.enumerate()",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
return;
}
Some(ty::Int(int_ty)) => int_ty.name_str(),
Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
_ => return,
};
let int_name = match ty.map(Ty::kind) {
// usize or inferred
Some(ty::Uint(UintTy::Usize)) | None => {
span_lint_and_sugg(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
"consider using",
format!(
"for ({name}, {}) in {}.enumerate()",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
return;
},
Some(ty::Int(int_ty)) => int_ty.name_str(),
Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
_ => return,
};
span_lint_and_then(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
|diag| {
diag.span_suggestion(
span,
"consider using",
format!(
"for ({name}, {}) in (0_{int_name}..).zip({})",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
span_lint_and_then(
cx,
EXPLICIT_COUNTER_LOOP,
span,
&format!("the variable `{name}` is used as a loop counter"),
|diag| {
diag.span_suggestion(
span,
"consider using",
format!(
"for ({name}, {}) in (0_{int_name}..).zip({})",
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
make_iterator_snippet(cx, arg, &mut applicability),
),
applicability,
);
diag.note(format!(
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
));
},
);
}
diag.note(format!(
"`{name}` is of type `{int_name}`, making it ineligible for `Iterator::enumerate`"
));
},
);
}
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::implements_trait;
use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::lang_items::LangItem;
@ -23,77 +22,79 @@ pub(super) fn check<'tcx>(
let inner_expr = peel_blocks_with_stmt(body);
// Check for the specific case that the result is returned and optimize suggestion for that (more
// cases can be added later)
if_chain! {
if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr);
if let Some(binding_id) = get_binding(pat);
if let ExprKind::Block(block, _) = then.kind;
if let [stmt] = block.stmts;
if let StmtKind::Semi(semi) = stmt.kind;
if let ExprKind::Ret(Some(ret_value)) = semi.kind;
if let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind;
if is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome);
if path_res(cx, inner_ret) == Res::Local(binding_id);
if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr);
then {
let mut applicability = Applicability::MachineApplicable;
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
// Checks if `pat` is a single reference to a binding (`&x`)
let is_ref_to_binding =
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
snippet.push_str(
&format!(
".map(|{}| {})",
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
)[..],
);
}
let ty = cx.typeck_results().expr_ty(inner_ret);
if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) {
snippet.push_str(
&format!(
".find(|{}{}| {})",
"&".repeat(1 + usize::from(is_ref_to_binding)),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
if is_ref_to_binding {
snippet.push_str(".copied()");
}
} else {
applicability = Applicability::MaybeIncorrect;
snippet.push_str(
&format!(
".find(|{}| {})",
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
}
// Extends to `last_stmt` to include semicolon in case of `return None;`
let lint_span = span.to(last_stmt.span).to(last_ret.span);
span_lint_and_then(
cx,
MANUAL_FIND,
lint_span,
"manual implementation of `Iterator::find`",
|diag| {
if applicability == Applicability::MaybeIncorrect {
diag.note("you may need to dereference some variables");
}
diag.span_suggestion(
lint_span,
"replace with an iterator",
snippet,
applicability,
);
},
if let Some(higher::If {
cond,
then,
r#else: None,
}) = higher::If::hir(inner_expr)
&& let Some(binding_id) = get_binding(pat)
&& let ExprKind::Block(block, _) = then.kind
&& let [stmt] = block.stmts
&& let StmtKind::Semi(semi) = stmt.kind
&& let ExprKind::Ret(Some(ret_value)) = semi.kind
&& 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)
&& let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr)
{
let mut applicability = Applicability::MachineApplicable;
let mut snippet = make_iterator_snippet(cx, arg, &mut applicability);
// Checks if `pat` is a single reference to a binding (`&x`)
let is_ref_to_binding =
matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..)));
// If `pat` is not a binding or a reference to a binding (`x` or `&x`)
// we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`)
if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) {
snippet.push_str(
&format!(
".map(|{}| {})",
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
)[..],
);
}
let ty = cx.typeck_results().expr_ty(inner_ret);
if cx
.tcx
.lang_items()
.copy_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
snippet.push_str(
&format!(
".find(|{}{}| {})",
"&".repeat(1 + usize::from(is_ref_to_binding)),
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
if is_ref_to_binding {
snippet.push_str(".copied()");
}
} else {
applicability = Applicability::MaybeIncorrect;
snippet.push_str(
&format!(
".find(|{}| {})",
snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability),
snippet_with_applicability(cx, cond.span, "..", &mut applicability),
)[..],
);
}
// Extends to `last_stmt` to include semicolon in case of `return None;`
let lint_span = span.to(last_stmt.span).to(last_ret.span);
span_lint_and_then(
cx,
MANUAL_FIND,
lint_span,
"manual implementation of `Iterator::find`",
|diag| {
if applicability == Applicability::MaybeIncorrect {
diag.note("you may need to dereference some variables");
}
diag.span_suggestion(lint_span, "replace with an iterator", snippet, applicability);
},
);
}
}
@ -124,34 +125,30 @@ fn last_stmt_and_ret<'tcx>(
if let Some(ret) = block.expr {
return Some((last_stmt, ret));
}
if_chain! {
if let [.., snd_last, _] = block.stmts;
if let StmtKind::Semi(last_expr) = last_stmt.kind;
if let ExprKind::Ret(Some(ret)) = last_expr.kind;
then {
return Some((snd_last, ret));
}
if let [.., snd_last, _] = block.stmts
&& let StmtKind::Semi(last_expr) = last_stmt.kind
&& let ExprKind::Ret(Some(ret)) = last_expr.kind
{
return Some((snd_last, ret));
}
}
None
}
let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id);
if_chain! {
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next()
// This should be the loop
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
// This should be the function body
if let Some((_, Node::Block(block))) = parent_iter.next();
if let Some((last_stmt, last_ret)) = extract(block);
if last_stmt.hir_id == node_hir;
if is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone);
if let Some((_, Node::Expr(_block))) = parent_iter.next();
&& let Some((_, Node::Block(block))) = parent_iter.next()
&& let Some((last_stmt, last_ret)) = extract(block)
&& last_stmt.hir_id == node_hir
&& is_res_lang_ctor(cx, path_res(cx, last_ret), LangItem::OptionNone)
&& let Some((_, Node::Expr(_block))) = parent_iter.next()
// This includes the function header
if let Some((_, func)) = parent_iter.next();
if func.fn_kind().is_some();
then {
Some((block.stmts.last().unwrap(), last_ret))
} else {
None
}
&& let Some((_, func)) = parent_iter.next()
&& func.fn_kind().is_some()
{
Some((block.stmts.last().unwrap(), last_ret))
} else {
None
}
}

View file

@ -3,7 +3,6 @@ use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{higher, path_to_local_id, peel_blocks_with_stmt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, Pat, PatKind};
@ -21,66 +20,51 @@ pub(super) fn check<'tcx>(
span: Span,
) {
let inner_expr = peel_blocks_with_stmt(body);
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
= higher::IfLet::hir(cx, inner_expr);
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
= higher::IfLet::hir(cx, inner_expr)
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
if path_to_local_id(let_expr, pat_hir_id);
&& let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind
&& path_to_local_id(let_expr, pat_hir_id)
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind;
if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id);
if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id);
let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id);
if some_ctor || ok_ctor;
&& let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind
&& let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id)
&& let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
&& let some_ctor = cx.tcx.lang_items().option_some_variant() == Some(variant_id)
&& let ok_ctor = cx.tcx.lang_items().result_ok_variant() == Some(variant_id)
&& (some_ctor || ok_ctor)
// Ensure expr in `if let` is not used afterwards
if !is_local_used(cx, if_then, pat_hir_id);
then {
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message
let msg = format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
&& !is_local_used(cx, if_then, pat_hir_id)
{
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message
let msg =
format!("unnecessary `if let` since only the `{if_let_type}` variant of the iterator element is used");
// Prepare the help message
let mut applicability = Applicability::MaybeIncorrect;
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
ty::Ref(_, inner, _) => match inner.kind() {
ty::Ref(..) => ".copied()",
_ => ""
}
_ => ""
};
// Prepare the help message
let mut applicability = Applicability::MaybeIncorrect;
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
let copied = match cx.typeck_results().expr_ty(let_expr).kind() {
ty::Ref(_, inner, _) => match inner.kind() {
ty::Ref(..) => ".copied()",
_ => "",
},
_ => "",
};
let sugg = format!("{arg_snippet}{copied}.flatten()");
let sugg = format!("{arg_snippet}{copied}.flatten()");
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
// it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
// to refer to the correct relative position of the suggestion.
let help_msg = if sugg.contains('\n') {
"remove the `if let` statement in the for loop and then..."
} else {
"...and remove the `if let` statement in the for loop"
};
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that
// case, it will be shown in the extra `help` message at the end, which is why the first
// `help_msg` needs to refer to the correct relative position of the suggestion.
let help_msg = if sugg.contains('\n') {
"remove the `if let` statement in the for loop and then..."
} else {
"...and remove the `if let` statement in the for loop"
};
span_lint_and_then(
cx,
MANUAL_FLATTEN,
span,
&msg,
|diag| {
diag.span_suggestion(
arg.span,
"try",
sugg,
applicability,
);
diag.span_help(
inner_expr.span,
help_msg,
);
}
);
}
span_lint_and_then(cx, MANUAL_FLATTEN, span, &msg, |diag| {
diag.span_suggestion(arg.span, "try", sugg, applicability);
diag.span_help(inner_expr.span, help_msg);
});
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::source::snippet;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_copy;
use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir::intravisit::walk_block;
@ -59,22 +58,31 @@ pub(super) fn check<'tcx>(
.map(|o| {
o.and_then(|(lhs, rhs)| {
let rhs = fetch_cloned_expr(rhs);
if_chain! {
if let ExprKind::Index(base_left, idx_left, _) = lhs.kind;
if let ExprKind::Index(base_right, idx_right, _) = rhs.kind;
if let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left));
if get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some();
if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts);
if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
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()
&& let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts)
&& let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts)
// Source and destination must be different
if path_to_local(base_left) != path_to_local(base_right);
then {
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
}
&& 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
}
})
})
@ -118,23 +126,19 @@ fn build_manual_memcpy_suggestion<'tcx>(
}
let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
if_chain! {
if let ExprKind::MethodCall(method, recv, [], _) = end.kind;
if method.ident.name == sym::len;
if path_to_local(recv) == path_to_local(base);
then {
if sugg.to_string() == end_str {
sugg::EMPTY.into()
} else {
sugg
}
if let ExprKind::MethodCall(method, recv, [], _) = end.kind
&& method.ident.name == sym::len
&& path_to_local(recv) == path_to_local(base)
{
if sugg.to_string() == end_str {
sugg::EMPTY.into()
} else {
match limits {
ast::RangeLimits::Closed => {
sugg + &sugg::ONE.into()
},
ast::RangeLimits::HalfOpen => sugg,
}
sugg
}
} else {
match limits {
ast::RangeLimits::Closed => sugg + &sugg::ONE.into(),
ast::RangeLimits::HalfOpen => sugg,
}
}
};
@ -331,10 +335,12 @@ fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Opti
}
fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
if_chain! {
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind;
if method.ident.name == sym::clone;
then { arg } else { expr }
if let ExprKind::MethodCall(method, arg, [], _) = expr.kind
&& method.ident.name == sym::clone
{
arg
} else {
expr
}
}

View file

@ -31,26 +31,30 @@ fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind;
if let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind;
if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name);
if let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind();
if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did());
then {
span_lint_and_sugg(
cx,
MISSING_SPIN_LOOP,
body.span,
"busy-waiting loop should at least have a spin loop hint",
"try",
(if is_no_std_crate(cx) {
"{ core::hint::spin_loop() }"
} else {
"{ std::hint::spin_loop() }"
}).into(),
Applicability::MachineApplicable
);
}
if let ExprKind::Block(
Block {
stmts: [], expr: None, ..
},
_,
) = body.kind
&& let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind
&& [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name)
&& let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind()
&& cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did())
{
span_lint_and_sugg(
cx,
MISSING_SPIN_LOOP,
body.span,
"busy-waiting loop should at least have a spin loop hint",
"try",
(if is_no_std_crate(cx) {
"{ core::hint::spin_loop() }"
} else {
"{ std::hint::spin_loop() }"
})
.into(),
Applicability::MachineApplicable,
);
}
}

View file

@ -1,7 +1,6 @@
use super::MUT_RANGE_BOUND;
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::{get_enclosing_block, higher, path_to_local};
use if_chain::if_chain;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
@ -12,19 +11,17 @@ use rustc_middle::ty;
use rustc_span::source_map::Span;
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
if_chain! {
if let Some(higher::Range {
start: Some(start),
end: Some(end),
..
}) = higher::Range::hir(arg);
let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
if mut_id_start.is_some() || mut_id_end.is_some();
then {
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
mut_warn_with_span(cx, span_low);
mut_warn_with_span(cx, span_high);
}
if let Some(higher::Range {
start: Some(start),
end: Some(end),
..
}) = higher::Range::hir(arg)
&& let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end))
&& (mut_id_start.is_some() || mut_id_end.is_some())
{
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
mut_warn_with_span(cx, span_low);
mut_warn_with_span(cx, span_high);
}
}
@ -42,13 +39,11 @@ fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
}
fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> {
if_chain! {
if let Some(hir_id) = path_to_local(bound);
if let Node::Pat(pat) = cx.tcx.hir().get(hir_id);
if let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind;
then {
return Some(hir_id);
}
if let Some(hir_id) = path_to_local(bound)
&& let Node::Pat(pat) = cx.tcx.hir().get(hir_id)
&& let PatKind::Binding(BindingAnnotation::MUT, ..) = pat.kind
{
return Some(hir_id);
}
None
}

View file

@ -4,7 +4,6 @@ use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{contains_name, higher, is_integer_const, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::{DefKind, Res};
@ -187,15 +186,13 @@ pub(super) fn check<'tcx>(
}
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
if_chain! {
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind;
if method.ident.name == sym::len;
if let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind;
if path.segments.len() == 1;
if path.segments[0].ident.name == var;
then {
return true;
}
if let ExprKind::MethodCall(method, recv, [], _) = expr.kind
&& method.ident.name == sym::len
&& let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind
&& path.segments.len() == 1
&& path.segments[0].ident.name == var
{
return true;
}
false
@ -207,17 +204,15 @@ fn is_end_eq_array_len<'tcx>(
limits: ast::RangeLimits,
indexed_ty: Ty<'tcx>,
) -> bool {
if_chain! {
if let ExprKind::Lit(lit) = end.kind;
if let ast::LitKind::Int(end_int, _) = lit.node;
if let ty::Array(_, arr_len_const) = indexed_ty.kind();
if let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env);
then {
return match limits {
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
};
}
if let ExprKind::Lit(lit) = end.kind
&& let ast::LitKind::Int(end_int, _) = lit.node
&& let ty::Array(_, arr_len_const) = indexed_ty.kind()
&& let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env)
{
return match limits {
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
};
}
false
@ -248,51 +243,49 @@ struct VarVisitor<'a, 'tcx> {
impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
if_chain! {
if let ExprKind::Path(ref seqpath) = seqexpr.kind
// the indexed container is referenced by a name
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
if let QPath::Resolved(None, seqvar) = *seqpath;
if seqvar.segments.len() == 1;
if is_local_used(self.cx, idx, self.var);
then {
if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name);
}
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res {
Res::Local(hir_id) => {
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let extent = self
.cx
.tcx
.region_scope_tree(parent_def_id)
.var_scope(hir_id.local_id)
.unwrap();
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly
.insert(seqvar.segments[0].ident.name, Some(extent));
}
return false; // no need to walk further *on the variable*
},
Res::Def(DefKind::Static(_) | DefKind::Const, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
return false; // no need to walk further *on the variable*
},
_ => (),
}
&& let QPath::Resolved(None, seqvar) = *seqpath
&& seqvar.segments.len() == 1
&& is_local_used(self.cx, idx, self.var)
{
if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name);
}
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res {
Res::Local(hir_id) => {
let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let extent = self
.cx
.tcx
.region_scope_tree(parent_def_id)
.var_scope(hir_id.local_id)
.unwrap();
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly
.insert(seqvar.segments[0].ident.name, Some(extent));
}
return false; // no need to walk further *on the variable*
},
Res::Def(DefKind::Static(_) | DefKind::Const, ..) => {
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
return false; // no need to walk further *on the variable*
},
_ => (),
}
}
true
@ -301,42 +294,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind
// a range index op
if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind;
if let Some(trait_id) = self
&& let Some(trait_id) = self
.cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.and_then(|def_id| self.cx.tcx.trait_of_item(def_id));
if (meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id))
|| (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id));
if !self.check(args_1, args_0, expr);
then {
return;
}
.and_then(|def_id| self.cx.tcx.trait_of_item(def_id))
&& ((meth.ident.name == sym::index && self.cx.tcx.lang_items().index_trait() == Some(trait_id))
|| (meth.ident.name == sym::index_mut && self.cx.tcx.lang_items().index_mut_trait() == Some(trait_id)))
&& !self.check(args_1, args_0, expr)
{
return;
}
if_chain! {
if let ExprKind::Index(seqexpr, idx, _) = expr.kind
// an index op
if let ExprKind::Index(seqexpr, idx, _) = expr.kind;
if !self.check(idx, seqexpr, expr);
then {
return;
}
&& !self.check(idx, seqexpr, expr)
{
return;
}
if_chain! {
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
// directly using a variable
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
if let Res::Local(local_id) = path.res;
then {
if local_id == self.var {
self.nonindex = true;
} else {
// not the correct variable, but still a variable
self.referenced.insert(path.segments[0].ident.name);
}
&& let Res::Local(local_id) = path.res
{
if local_id == self.var {
self.nonindex = true;
} else {
// not the correct variable, but still a variable
self.referenced.insert(path.segments[0].ident.name);
}
}

View file

@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -44,54 +43,50 @@ pub(super) fn check<'tcx>(
// Determine whether it is safe to lint the body
let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
walk_expr(&mut same_item_push_visitor, body);
if_chain! {
if same_item_push_visitor.should_lint();
if let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push;
let vec_ty = cx.typeck_results().expr_ty(vec);
let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
if cx
if same_item_push_visitor.should_lint()
&& let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push
&& let vec_ty = cx.typeck_results().expr_ty(vec)
&& let ty = vec_ty.walk().nth(1).unwrap().expect_ty()
&& cx
.tcx
.lang_items()
.clone_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[]));
then {
// Make sure that the push does not involve possibly mutating values
match pushed_item.kind {
ExprKind::Path(ref qpath) => {
match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => {
let node = cx.tcx.hir().get(hir_id);
if_chain! {
if let Node::Pat(pat) = node;
if let PatKind::Binding(bind_ann, ..) = pat.kind;
if !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut));
let parent_node = cx.tcx.hir().parent_id(hir_id);
if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
if let Some(init) = parent_let_expr.init;
then {
match init.kind {
// immutable bindings that are initialized with literal
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
// immutable bindings that are initialized with constant
ExprKind::Path(ref path) => {
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
emit_lint(cx, vec, pushed_item, ctxt);
}
}
_ => {},
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
// Make sure that the push does not involve possibly mutating values
match pushed_item.kind {
ExprKind::Path(ref qpath) => {
match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => {
let node = cx.tcx.hir().get(hir_id);
if let Node::Pat(pat) = node
&& let PatKind::Binding(bind_ann, ..) = pat.kind
&& !matches!(bind_ann, BindingAnnotation(_, Mutability::Mut))
&& let parent_node = cx.tcx.hir().parent_id(hir_id)
&& let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node)
&& let Some(init) = parent_let_expr.init
{
match init.kind {
// immutable bindings that are initialized with literal
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
// immutable bindings that are initialized with constant
ExprKind::Path(ref path) => {
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
emit_lint(cx, vec, pushed_item, ctxt);
}
}
},
_ => {},
}
},
// constant
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
},
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
}
},
// constant
Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
},
ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
}
}
@ -118,16 +113,14 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
}
fn should_lint(&self) -> bool {
if_chain! {
if !self.non_deterministic_expr;
if !self.multiple_pushes;
if let Some((vec, _, _)) = self.vec_push;
if let Some(hir_id) = path_to_local(vec);
then {
!self.used_locals.contains(&hir_id)
} else {
false
}
if !self.non_deterministic_expr
&& !self.multiple_pushes
&& let Some((vec, _, _)) = self.vec_push
&& let Some(hir_id) = path_to_local(vec)
{
!self.used_locals.contains(&hir_id)
} else {
false
}
}
}
@ -180,18 +173,16 @@ fn get_vec_push<'tcx>(
cx: &LateContext<'tcx>,
stmt: &'tcx Stmt<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> {
if_chain! {
if let StmtKind::Semi(semi_stmt) = &stmt.kind
// Extract method being called
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
&& let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind
// Figure out the parameters for the method call
if let Some(pushed_item) = args.first();
&& let Some(pushed_item) = args.first()
// Check that the method being called is push() on a Vec
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
if path.ident.name.as_str() == "push";
then {
return Some((self_expr, pushed_item, semi_stmt.span.ctxt()))
}
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec)
&& path.ident.name.as_str() == "push"
{
return Some((self_expr, pushed_item, semi_stmt.span.ctxt()));
}
None
}

View file

@ -2,7 +2,6 @@ use super::SINGLE_ELEMENT_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, snippet_with_applicability};
use clippy_utils::visitors::contains_break_or_continue;
use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX;
use rustc_ast::Mutability;
use rustc_errors::Applicability;
@ -66,36 +65,36 @@ pub(super) fn check<'tcx>(
ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""),
_ => return,
};
if_chain! {
if let ExprKind::Block(block, _) = body.kind;
if !block.stmts.is_empty();
if !contains_break_or_continue(body);
then {
let mut applicability = Applicability::MachineApplicable;
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
block_str.remove(0);
block_str.pop();
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
if let ExprKind::Block(block, _) = body.kind
&& !block.stmts.is_empty()
&& !contains_break_or_continue(body)
{
let mut applicability = Applicability::MachineApplicable;
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
block_str.remove(0);
block_str.pop();
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
if !prefix.is_empty() && (
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
if !prefix.is_empty()
&& (
// Precedence of internal expression is less than or equal to precedence of `&expr`.
arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression)
) {
arg_snip = format!("({arg_snip})").into();
}
span_lint_and_sugg(
cx,
SINGLE_ELEMENT_LOOP,
expr.span,
"for loop over a single element",
"try",
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
applicability,
)
{
arg_snip = format!("({arg_snip})").into();
}
span_lint_and_sugg(
cx,
SINGLE_ELEMENT_LOOP,
expr.span,
"for loop over a single element",
"try",
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
applicability,
);
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::ty::{has_iter_method, implements_trait};
use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
use if_chain::if_chain;
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor};
@ -145,20 +144,18 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
fn visit_local(&mut self, l: &'tcx Local<'_>) {
// Look for declarations of the variable
if_chain! {
if l.pat.hir_id == self.var_id;
if let PatKind::Binding(.., ident, _) = l.pat.kind;
then {
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
if l.pat.hir_id == self.var_id
&& let PatKind::Binding(.., ident, _) = l.pat.kind
{
let ty = l.ty.map(|_| self.cx.typeck_results().pat_ty(l.pat));
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
InitializeVisitorState::Initialized {
initializer: init,
ty,
name: ident.name,
}
})
}
self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
InitializeVisitorState::Initialized {
initializer: init,
ty,
name: ident.name,
}
});
}
walk_local(self, l);

View file

@ -2,7 +2,6 @@ use super::WHILE_IMMUTABLE_CONDITION;
use clippy_utils::consts::constant;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::usage::mutated_variables;
use if_chain::if_chain;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -95,20 +94,18 @@ struct VarCollectorVisitor<'a, 'tcx> {
impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Path(ref qpath) = ex.kind;
if let QPath::Resolved(None, _) = *qpath;
then {
match self.cx.qpath_res(qpath, ex.hir_id) {
Res::Local(hir_id) => {
self.ids.insert(hir_id);
},
Res::Def(DefKind::Static(_), def_id) => {
let mutable = self.cx.tcx.is_mutable_static(def_id);
self.def_ids.insert(def_id, mutable);
},
_ => {},
}
if let ExprKind::Path(ref qpath) = ex.kind
&& let QPath::Resolved(None, _) = *qpath
{
match self.cx.qpath_res(qpath, ex.hir_id) {
Res::Local(hir_id) => {
self.ids.insert(hir_id);
},
Res::Def(DefKind::Static(_), def_id) => {
let mutable = self.cx.tcx.is_mutable_static(def_id);
self.def_ids.insert(def_id, mutable);
},
_ => {},
}
}
}

View file

@ -3,7 +3,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::visitors::is_res_used;
use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable, is_res_lang_ctor, is_trait_method};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -15,59 +14,53 @@ use rustc_span::symbol::sym;
use rustc_span::Symbol;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (scrutinee_expr, iter_expr_struct, iter_expr, some_pat, loop_expr) = if_chain! {
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr)
// check for `Some(..)` pattern
if let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind;
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
&& let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
// check for call to `Iterator::next`
if let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind;
if method_name.ident.name == sym::next;
if is_trait_method(cx, let_expr, sym::Iterator);
if let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr);
&& let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind
&& method_name.ident.name == sym::next
&& is_trait_method(cx, let_expr, sym::Iterator)
&& let Some(iter_expr_struct) = try_parse_iter_expr(cx, iter_expr)
// get the loop containing the match expression
if !uses_iter(cx, &iter_expr_struct, if_then);
then {
(let_expr, iter_expr_struct, iter_expr, some_pat, expr)
} else {
return;
}
};
let mut applicability = Applicability::MachineApplicable;
let loop_var = if let Some(some_pat) = some_pat.first() {
if is_refutable(cx, some_pat) {
// Refutable patterns don't work with for loops.
return;
}
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
} else {
"_".into()
};
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// afterwards a mutable borrow of a field isn't necessary.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, loop_expr)
&& !uses_iter(cx, &iter_expr_struct, if_then)
{
".by_ref()"
} else {
""
};
let mut applicability = Applicability::MachineApplicable;
let loop_var = if let Some(some_pat) = some_pat.first() {
if is_refutable(cx, some_pat) {
// Refutable patterns don't work with for loops.
return;
}
snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
} else {
"_".into()
};
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(scrutinee_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("for {loop_var} in {iterator}{by_ref}"),
applicability,
);
// If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
// borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
// afterwards a mutable borrow of a field isn't necessary.
let by_ref = if cx.typeck_results().expr_ty(iter_expr).ref_mutability() == Some(Mutability::Mut)
|| !iter_expr_struct.can_move
|| !iter_expr_struct.fields.is_empty()
|| needs_mutable_borrow(cx, &iter_expr_struct, expr)
{
".by_ref()"
} else {
""
};
let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
WHILE_LET_ON_ITERATOR,
expr.span.with_hi(let_expr.span.hi()),
"this loop could be written as a `for` loop",
"try",
format!("for {loop_var} in {iterator}{by_ref}"),
applicability,
);
}
}
#[derive(Debug)]

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use hir::def::{DefKind, Res};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
@ -90,30 +89,26 @@ impl MacroUseImports {
impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! {
if cx.sess().opts.edition >= Edition::Edition2018;
if let hir::ItemKind::Use(path, _kind) = &item.kind;
let hir_id = item.hir_id();
let attrs = cx.tcx.hir().attrs(hir_id);
if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
if let Some(id) = path.res.iter().find_map(|res| match res {
if cx.sess().opts.edition >= Edition::Edition2018
&& let hir::ItemKind::Use(path, _kind) = &item.kind
&& let hir_id = item.hir_id()
&& let attrs = cx.tcx.hir().attrs(hir_id)
&& let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use))
&& let Some(id) = path.res.iter().find_map(|res| match res {
Res::Def(DefKind::Mod, id) => Some(id),
_ => None,
});
if !id.is_local();
then {
for kid in cx.tcx.module_children(id) {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id);
self.imports.push((def_path, span, hir_id));
}
}
} else {
if item.span.from_expansion() {
self.push_unique_macro_pat_ty(cx, item.span);
})
&& !id.is_local()
{
for kid in cx.tcx.module_children(id) {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
let span = mac_attr.span;
let def_path = cx.tcx.def_path_str(mac_id);
self.imports.push((def_path, span, hir_id));
}
}
} else if item.span.from_expansion() {
self.push_unique_macro_pat_ty(cx, item.span);
}
}
fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet;
use clippy_utils::{is_entrypoint_fn, is_no_std_crate};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -43,21 +42,19 @@ impl LateLintPass<'_> for MainRecursion {
return;
}
if_chain! {
if let ExprKind::Call(func, _) = &expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind;
if let Some(def_id) = path.res.opt_def_id();
if is_entrypoint_fn(cx, def_id);
then {
span_lint_and_help(
cx,
MAIN_RECURSION,
func.span,
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
None,
"consider using another function for this recursion"
)
}
if let ExprKind::Call(func, _) = &expr.kind
&& let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind
&& let Some(def_id) = path.res.opt_def_id()
&& is_entrypoint_fn(cx, def_id)
{
span_lint_and_help(
cx,
MAIN_RECURSION,
func.span,
&format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
None,
"consider using another function for this recursion",
);
}
}
}

View file

@ -1,6 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
@ -47,61 +46,57 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
span: Span,
def_id: LocalDefId,
) {
if_chain! {
if let Some(header) = kind.header();
if !header.asyncness.is_async();
if let Some(header) = kind.header()
&& !header.asyncness.is_async()
// Check that this function returns `impl Future`
if let FnRetTy::Return(ret_ty) = decl.output;
if let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty);
if let Some(output) = future_output_ty(trait_ref);
if captures_all_lifetimes(decl.inputs, &output_lifetimes);
&& let FnRetTy::Return(ret_ty) = decl.output
&& let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty)
&& let Some(output) = future_output_ty(trait_ref)
&& captures_all_lifetimes(decl.inputs, &output_lifetimes)
// Check that the body of the function consists of one async block
if let ExprKind::Block(block, _) = body.value.kind;
if block.stmts.is_empty();
if let Some(closure_body) = desugared_async_block(cx, block);
if let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
cx.tcx.hir().get_by_def_id(def_id);
then {
let header_span = span.with_hi(ret_ty.span.hi());
&& let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(closure_body) = desugared_async_block(cx, block)
&& let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
cx.tcx.hir().get_by_def_id(def_id)
{
let header_span = span.with_hi(ret_ty.span.hi());
span_lint_and_then(
cx,
MANUAL_ASYNC_FN,
header_span,
"this function can be simplified using the `async fn` syntax",
|diag| {
if_chain! {
if let Some(vis_snip) = snippet_opt(cx, *vis_span);
if let Some(header_snip) = snippet_opt(cx, header_span);
if let Some(ret_pos) = position_before_rarrow(&header_snip);
if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
then {
let header_snip = if vis_snip.is_empty() {
format!("async {}", &header_snip[..ret_pos])
} else {
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
};
span_lint_and_then(
cx,
MANUAL_ASYNC_FN,
header_span,
"this function can be simplified using the `async fn` syntax",
|diag| {
if let Some(vis_snip) = snippet_opt(cx, *vis_span)
&& let Some(header_snip) = snippet_opt(cx, header_span)
&& let Some(ret_pos) = position_before_rarrow(&header_snip)
&& let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output)
{
let header_snip = if vis_snip.is_empty() {
format!("async {}", &header_snip[..ret_pos])
} else {
format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
};
let help = format!("make the function `async` and {ret_sugg}");
diag.span_suggestion(
header_span,
help,
format!("{header_snip}{ret_snip}"),
Applicability::MachineApplicable
);
let help = format!("make the function `async` and {ret_sugg}");
diag.span_suggestion(
header_span,
help,
format!("{header_snip}{ret_snip}"),
Applicability::MachineApplicable,
);
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.span_suggestion(
block.span,
"move the body of the async block to the enclosing function",
body_snip,
Applicability::MachineApplicable
);
}
}
},
);
}
let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.span_suggestion(
block.span,
"move the body of the async block to the enclosing function",
body_snip,
Applicability::MachineApplicable,
);
}
},
);
}
}
}
@ -110,48 +105,44 @@ fn future_trait_ref<'tcx>(
cx: &LateContext<'tcx>,
ty: &'tcx Ty<'tcx>,
) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> {
if_chain! {
if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind;
let item = cx.tcx.hir().item(item_id);
if let ItemKind::OpaqueTy(opaque) = &item.kind;
if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind
&& let item = cx.tcx.hir().item(item_id)
&& let ItemKind::OpaqueTy(opaque) = &item.kind
&& let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
if let GenericBound::Trait(poly, _) = bound {
Some(&poly.trait_ref)
} else {
None
}
});
if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait();
then {
let output_lifetimes = bounds
.iter()
.filter_map(|bound| {
if let GenericArg::Lifetime(lt) = bound {
Some(lt.res)
} else {
None
}
})
.collect();
})
&& trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait()
{
let output_lifetimes = bounds
.iter()
.filter_map(|bound| {
if let GenericArg::Lifetime(lt) = bound {
Some(lt.res)
} else {
None
}
})
.collect();
return Some((trait_ref, output_lifetimes));
}
return Some((trait_ref, output_lifetimes));
}
None
}
fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
if_chain! {
if let Some(segment) = trait_ref.path.segments.last();
if let Some(args) = segment.args;
if args.bindings.len() == 1;
let binding = &args.bindings[0];
if binding.ident.name == sym::Output;
if let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind;
then {
return Some(output);
}
if let Some(segment) = trait_ref.path.segments.last()
&& let Some(args) = segment.args
&& args.bindings.len() == 1
&& let binding = &args.bindings[0]
&& binding.ident.name == sym::Output
&& let TypeBindingKind::Equality { term: Term::Ty(output) } = binding.kind
{
return Some(output);
}
None
@ -181,17 +172,15 @@ fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName])
}
fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
if_chain! {
if let Some(block_expr) = block.expr;
if let Expr {
if let Some(block_expr) = block.expr
&& let Expr {
kind: ExprKind::Closure(&Closure { body, .. }),
..
} = block_expr;
let closure_body = cx.tcx.hir().body(body);
if closure_body.coroutine_kind == Some(CoroutineKind::Async(CoroutineSource::Block));
then {
return Some(closure_body);
}
} = block_expr
&& let closure_body = cx.tcx.hir().body(body)
&& closure_body.coroutine_kind == Some(CoroutineKind::Async(CoroutineSource::Block))
{
return Some(closure_body);
}
None

View file

@ -53,32 +53,30 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
return;
}
if_chain! {
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind;
if let BinOpKind::Mul = &bin_op.node;
if !in_external_macro(cx.sess(), expr.span);
let ctxt = expr.span.ctxt();
if left_expr.span.ctxt() == ctxt;
if right_expr.span.ctxt() == ctxt;
if let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr);
if matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_));
if let ExprKind::Lit(lit) = &other_expr.kind;
if let LitKind::Int(8, _) = lit.node;
then {
let mut app = Applicability::MachineApplicable;
let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind
&& let BinOpKind::Mul = &bin_op.node
&& !in_external_macro(cx.sess(), expr.span)
&& let ctxt = expr.span.ctxt()
&& left_expr.span.ctxt() == ctxt
&& right_expr.span.ctxt() == ctxt
&& let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr)
&& matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_))
&& let ExprKind::Lit(lit) = &other_expr.kind
&& let LitKind::Int(8, _) = lit.node
{
let mut app = Applicability::MachineApplicable;
let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
span_lint_and_sugg(
cx,
MANUAL_BITS,
expr.span,
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
"consider using",
sugg,
app,
);
}
span_lint_and_sugg(
cx,
MANUAL_BITS,
expr.span,
"usage of `mem::size_of::<T>()` to obtain the size of `T` in bits",
"consider using",
sugg,
app,
);
}
}
@ -98,22 +96,22 @@ fn get_one_size_of_ty<'tcx>(
}
fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(&'tcx rustc_hir::Ty<'tcx>, Ty<'tcx>)> {
if_chain! {
if let ExprKind::Call(count_func, _func_args) = expr.kind;
if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
if let QPath::Resolved(_, count_func_path) = count_func_qpath;
if let Some(segment_zero) = count_func_path.segments.first();
if let Some(args) = segment_zero.args;
if let Some(GenericArg::Type(real_ty)) = args.args.first();
if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id);
then {
cx.typeck_results().node_args(count_func.hir_id).types().next().map(|resolved_ty| (*real_ty, resolved_ty))
} else {
None
}
if let ExprKind::Call(count_func, _func_args) = expr.kind
&& let ExprKind::Path(ref count_func_qpath) = count_func.kind
&& let QPath::Resolved(_, count_func_path) = count_func_qpath
&& let Some(segment_zero) = count_func_path.segments.first()
&& let Some(args) = segment_zero.args
&& let Some(GenericArg::Type(real_ty)) = args.args.first()
&& let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
{
cx.typeck_results()
.node_args(count_func.hir_id)
.types()
.next()
.map(|resolved_ty| (*real_ty, resolved_ty))
} else {
None
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::usage::mutated_variables;
use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, Visitor};
@ -71,55 +70,61 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
return;
}
if_chain! {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
if let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id);
if let ExprKind::Path(target_path) = &target_arg.kind;
then {
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
StripKind::Prefix
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
StripKind::Suffix
} else {
return;
};
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err {
return;
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
&& let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id)
&& let ExprKind::Path(target_path) = &target_arg.kind
{
let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
StripKind::Prefix
} else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
StripKind::Suffix
} else {
return;
};
let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err {
return;
};
if let Res::Local(hir_id) = target_res
&& let Some(used_mutably) = mutated_variables(then, cx)
&& used_mutably.contains(&hir_id)
{
return;
}
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
if !strippings.is_empty() {
let kind_word = match strip_kind {
StripKind::Prefix => "prefix",
StripKind::Suffix => "suffix",
};
if_chain! {
if let Res::Local(hir_id) = target_res;
if let Some(used_mutably) = mutated_variables(then, cx);
if used_mutably.contains(&hir_id);
then {
return;
}
}
let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
if !strippings.is_empty() {
let kind_word = match strip_kind {
StripKind::Prefix => "prefix",
StripKind::Suffix => "suffix",
};
let test_span = expr.span.until(then.span);
span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {kind_word} manually"), |diag| {
let test_span = expr.span.until(then.span);
span_lint_and_then(
cx,
MANUAL_STRIP,
strippings[0],
&format!("stripping a {kind_word} manually"),
|diag| {
diag.span_note(test_span, format!("the {kind_word} was tested here"));
multispan_sugg(
diag,
&format!("try using the `strip_{kind_word}` method"),
vec![(test_span,
format!("if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
snippet(cx, target_arg.span, ".."),
snippet(cx, pattern.span, "..")))]
.into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
vec![(
test_span,
format!(
"if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
snippet(cx, target_arg.span, ".."),
snippet(cx, pattern.span, "..")
),
)]
.into_iter()
.chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
);
});
}
},
);
}
}
}
@ -129,15 +134,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::MethodCall(_, arg, [], _) = expr.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if match_def_path(cx, method_def_id, &paths::STR_LEN);
then {
Some(arg)
} else {
None
}
if let ExprKind::MethodCall(_, arg, [], _) = expr.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& match_def_path(cx, method_def_id, &paths::STR_LEN)
{
Some(arg)
} else {
None
}
}
@ -201,36 +204,38 @@ fn find_stripping<'tcx>(
impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
if_chain! {
if is_ref_str(self.cx, ex);
let unref = peel_ref(ex);
if let ExprKind::Index(indexed, index, _) = &unref.kind;
if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index);
if let ExprKind::Path(path) = &indexed.kind;
if self.cx.qpath_res(path, ex.hir_id) == self.target;
then {
match (self.strip_kind, start, end) {
(StripKind::Prefix, Some(start), None) => {
if eq_pattern_length(self.cx, self.pattern, start) {
self.results.push(ex.span);
return;
}
},
(StripKind::Suffix, None, Some(end)) => {
if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind;
if let Some(left_arg) = len_arg(self.cx, left);
if let ExprKind::Path(left_path) = &left_arg.kind;
if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target;
if eq_pattern_length(self.cx, self.pattern, right);
then {
self.results.push(ex.span);
return;
}
}
},
_ => {}
}
if is_ref_str(self.cx, ex)
&& let unref = peel_ref(ex)
&& let ExprKind::Index(indexed, index, _) = &unref.kind
&& let Some(higher::Range { start, end, .. }) = higher::Range::hir(index)
&& let ExprKind::Path(path) = &indexed.kind
&& self.cx.qpath_res(path, ex.hir_id) == self.target
{
match (self.strip_kind, start, end) {
(StripKind::Prefix, Some(start), None) => {
if eq_pattern_length(self.cx, self.pattern, start) {
self.results.push(ex.span);
return;
}
},
(StripKind::Suffix, None, Some(end)) => {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
left,
right,
) = end.kind
&& let Some(left_arg) = len_arg(self.cx, left)
&& let ExprKind::Path(left_path) = &left_arg.kind
&& self.cx.qpath_res(left_path, left_arg.hir_id) == self.target
&& eq_pattern_length(self.cx, self.pattern, right)
{
self.results.push(ex.span);
return;
}
},
_ => {},
}
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{iter_input_pats, method_chain_args};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
@ -164,16 +163,14 @@ fn unit_closure<'tcx>(
cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>,
) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
if_chain! {
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind;
let body = cx.tcx.hir().body(body);
let body_expr = &body.value;
if fn_decl.inputs.len() == 1;
if is_unit_expression(cx, body_expr);
if let Some(binding) = iter_input_pats(fn_decl, body).next();
then {
return Some((binding, body_expr));
}
if let hir::ExprKind::Closure(&hir::Closure { fn_decl, body, .. }) = expr.kind
&& let body = cx.tcx.hir().body(body)
&& let body_expr = &body.value
&& fn_decl.inputs.len() == 1
&& is_unit_expression(cx, body_expr)
&& let Some(binding) = iter_input_pats(fn_decl, body).next()
{
return Some((binding, body_expr));
}
None
}

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{higher, is_res_lang_ctor};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, PatKind};
use rustc_lint::{LateContext, LateLintPass};
@ -56,33 +55,31 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
return;
};
if_chain! {
if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind; //get operation
if ok_path.ident.as_str() == "ok";
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
let ctxt = expr.span.ctxt();
if let_expr.span.ctxt() == ctxt;
if let_pat.span.ctxt() == ctxt;
then {
let mut applicability = Applicability::MachineApplicable;
let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
let sugg = format!(
"{ifwhile} let Ok({some_expr_string}) = {}",
trimmed_ok.trim().trim_end_matches('.'),
);
span_lint_and_sugg(
cx,
MATCH_RESULT_OK,
expr.span.with_hi(let_expr.span.hi()),
"matching on `Some` with `ok()` is redundant",
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
sugg,
applicability,
);
}
if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result<T,E>.ok(, _)
&& let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation
&& ok_path.ident.as_str() == "ok"
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
&& is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome)
&& let ctxt = expr.span.ctxt()
&& let_expr.span.ctxt() == ctxt
&& let_pat.span.ctxt() == ctxt
{
let mut applicability = Applicability::MachineApplicable;
let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
let sugg = format!(
"{ifwhile} let Ok({some_expr_string}) = {}",
trimmed_ok.trim().trim_end_matches('.'),
);
span_lint_and_sugg(
cx,
MATCH_RESULT_OK,
expr.span.with_hi(let_expr.span.hi()),
"matching on `Some` with `ok()` is redundant",
&format!("consider matching on `Ok({some_expr_string})` and removing the call to `ok` instead"),
sugg,
applicability,
);
}
}
}

View file

@ -5,7 +5,6 @@ use clippy_utils::visitors::is_local_used;
use clippy_utils::{
is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq,
};
use if_chain::if_chain;
use rustc_errors::MultiSpan;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
@ -40,76 +39,73 @@ fn check_arm<'tcx>(
outer_else_body: Option<&'tcx Expr<'tcx>>,
) {
let inner_expr = peel_blocks_with_stmt(outer_then_body);
if_chain! {
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr);
if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr)
&& let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)),
IfLetOrMatch::Match(scrutinee, arms, ..) => if_chain! {
IfLetOrMatch::Match(scrutinee, arms, ..) => if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none())
// if there are more than two arms, collapsing would be non-trivial
if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none());
// one of the arms must be "wild-like"
if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a));
then {
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
Some((scrutinee, then.pat, Some(els.body)))
} else {
None
}
&& let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a))
{
let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
Some((scrutinee, then.pat, Some(els.body)))
} else {
None
},
};
if outer_pat.span.eq_ctxt(inner_scrutinee.span);
}
&& outer_pat.span.eq_ctxt(inner_scrutinee.span)
// match expression must be a local binding
// match <local> { .. }
if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));
if !pat_contains_or(inner_then_pat);
&& let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee))
&& !pat_contains_or(inner_then_pat)
// the binding must come from the pattern of the containing match arm
// ..<local>.. => match <local> { .. }
if let (Some(binding_span), is_innermost_parent_pat_struct)
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id);
&& let (Some(binding_span), is_innermost_parent_pat_struct)
= find_pat_binding_and_is_innermost_parent_pat_struct(outer_pat, binding_id)
// the "else" branches must be equal
if match (outer_else_body, inner_else_body) {
&& match (outer_else_body, inner_else_body) {
(None, None) => true,
(None, Some(e)) | (Some(e), None) => is_unit_expr(e),
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
};
}
// the binding must not be used in the if guard
if outer_guard.map_or(
&& outer_guard.map_or(
true,
|(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id)
);
)
// ...or anywhere in the inner expression
if match inner {
&& match inner {
IfLetOrMatch::IfLet(_, _, body, els) => {
!is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
},
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
};
then {
let msg = format!(
"this `{}` can be collapsed into the outer `{}`",
if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
if outer_is_match { "match" } else { "if let" },
);
// collapsing patterns need an explicit field name in struct pattern matching
// ex: Struct {x: Some(1)}
let replace_msg = if is_innermost_parent_pat_struct {
format!(", prefixed by {}:", snippet(cx, binding_span, "their field name"))
} else {
String::new()
};
span_lint_and_then(
cx,
COLLAPSIBLE_MATCH,
inner_expr.span,
&msg,
|diag| {
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
help_span.push_span_label(binding_span, "replace this binding");
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
},
);
}
{
let msg = format!(
"this `{}` can be collapsed into the outer `{}`",
if matches!(inner, IfLetOrMatch::Match(..)) {
"match"
} else {
"if let"
},
if outer_is_match { "match" } else { "if let" },
);
// collapsing patterns need an explicit field name in struct pattern matching
// ex: Struct {x: Some(1)}
let replace_msg = if is_innermost_parent_pat_struct {
format!(", prefixed by {}:", snippet(cx, binding_span, "their field name"))
} else {
String::new()
};
span_lint_and_then(cx, COLLAPSIBLE_MATCH, inner_expr.span, &msg, |diag| {
let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
help_span.push_span_label(binding_span, "replace this binding");
help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}"));
diag.span_help(
help_span,
"the outer pattern can be modified to include the inner pattern",
);
});
}
}

View file

@ -8,38 +8,35 @@ use rustc_lint::LateContext;
use super::INFALLIBLE_DESTRUCTURING_MATCH;
pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool {
if_chain! {
if !local.span.from_expansion();
if let Some(expr) = local.init;
if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
if arms.len() == 1 && arms[0].guard.is_none();
if let PatKind::TupleStruct(
QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
if args.len() == 1;
if let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind;
let body = peel_blocks(arms[0].body);
if path_to_local_id(body, arg);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
INFALLIBLE_DESTRUCTURING_MATCH,
local.span,
"you seem to be trying to use `match` to destructure a single infallible pattern. \
Consider using `let`",
"try",
format!(
"let {}({}{}) = {};",
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
if binding.0 == ByRef::Yes { "ref " } else { "" },
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
snippet_with_applicability(cx, target.span, "..", &mut applicability),
),
applicability,
);
return true;
}
if !local.span.from_expansion()
&& let Some(expr) = local.init
&& let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind
&& arms.len() == 1
&& arms[0].guard.is_none()
&& let PatKind::TupleStruct(QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind
&& args.len() == 1
&& let PatKind::Binding(binding, arg, ..) = strip_pat_refs(&args[0]).kind
&& let body = peel_blocks(arms[0].body)
&& path_to_local_id(body, arg)
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
INFALLIBLE_DESTRUCTURING_MATCH,
local.span,
"you seem to be trying to use `match` to destructure a single infallible pattern. \
Consider using `let`",
"try",
format!(
"let {}({}{}) = {};",
snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
if binding.0 == ByRef::Yes { "ref " } else { "" },
snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
snippet_with_applicability(cx, target.span, "..", &mut applicability),
),
applicability,
);
return true;
}
false
}

View file

@ -21,19 +21,19 @@ fn get_cond_expr<'tcx>(
expr: &'tcx Expr<'_>,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
if_chain! {
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr);
if let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind;
if let PatKind::Binding(_,target, ..) = pat.kind;
if is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr); // check that one expr resolves to `Some(x)`, the other to `None`
then {
return Some(SomeExpr {
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
needs_unsafe_block: contains_unsafe_block(cx, expr),
needs_negated: is_none_expr(cx, then_expr) // if the `then_expr` resolves to `None`, need to negate the cond
})
}
if let Some(block_expr) = peels_blocks_incl_unsafe_opt(expr)
&& let ExprKind::If(cond, then_expr, Some(else_expr)) = block_expr.kind
&& let PatKind::Binding(_, target, ..) = pat.kind
&& (is_some_expr(cx, target, ctxt, then_expr) && is_none_expr(cx, else_expr)
|| is_none_expr(cx, then_expr) && is_some_expr(cx, target, ctxt, else_expr))
// check that one expr resolves to `Some(x)`, the other to `None`
{
return Some(SomeExpr {
expr: peels_blocks_incl_unsafe(cond.peel_drop_temps()),
needs_unsafe_block: contains_unsafe_block(cx, expr),
needs_negated: is_none_expr(cx, then_expr), /* if the `then_expr` resolves to `None`, need to negate the
* cond */
});
};
None
}

View file

@ -4,7 +4,6 @@ use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{is_res_lang_ctor, path_to_local_id, sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::LangItem::{OptionNone, ResultErr};
@ -16,65 +15,57 @@ use super::MANUAL_UNWRAP_OR;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
let ty = cx.typeck_results().expr_ty(scrutinee);
if_chain! {
if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
Some("Option")
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
Some("Result")
} else {
None
};
if let Some(or_arm) = applicable_or_arm(cx, arms);
if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
if let Some(indent) = indent_of(cx, expr.span);
if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
then {
let reindented_or_body =
reindent_multiline(or_body_snippet.into(), true, Some(indent));
if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
Some("Option")
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
Some("Result")
} else {
None
} && let Some(or_arm) = applicable_or_arm(cx, arms)
&& let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span)
&& let Some(indent) = indent_of(cx, expr.span)
&& constant_simple(cx, cx.typeck_results(), or_arm.body).is_some()
{
let reindented_or_body = reindent_multiline(or_body_snippet.into(), true, Some(indent));
let mut app = Applicability::MachineApplicable;
let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par();
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR, expr.span,
&format!("this pattern reimplements `{ty_name}::unwrap_or`"),
"replace with",
format!(
"{suggestion}.unwrap_or({reindented_or_body})",
),
app,
);
}
let mut app = Applicability::MachineApplicable;
let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par();
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR,
expr.span,
&format!("this pattern reimplements `{ty_name}::unwrap_or`"),
"replace with",
format!("{suggestion}.unwrap_or({reindented_or_body})",),
app,
);
}
}
fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
if_chain! {
if arms.len() == 2;
if arms.iter().all(|arm| arm.guard.is_none());
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
match arm.pat.kind {
PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone),
PatKind::TupleStruct(ref qpath, [pat], _) =>
matches!(pat.kind, PatKind::Wild)
&& is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr),
_ => false,
}
});
let unwrap_arm = &arms[1 - idx];
if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind;
if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, unwrap_arm.pat.hir_id);
if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
if cx.tcx.lang_items().option_some_variant() == Some(variant_id)
|| cx.tcx.lang_items().result_ok_variant() == Some(variant_id);
if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
if path_to_local_id(unwrap_arm.body, binding_hir_id);
if cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty();
if !contains_return_break_continue_macro(or_arm.body);
then {
Some(or_arm)
} else {
None
}
if arms.len() == 2
&& arms.iter().all(|arm| arm.guard.is_none())
&& let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| match arm.pat.kind {
PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone),
PatKind::TupleStruct(ref qpath, [pat], _) => {
matches!(pat.kind, PatKind::Wild)
&& is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr)
},
_ => false,
})
&& let unwrap_arm = &arms[1 - idx]
&& let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind
&& let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, unwrap_arm.pat.hir_id)
&& let Some(variant_id) = cx.tcx.opt_parent(ctor_id)
&& (cx.tcx.lang_items().option_some_variant() == Some(variant_id)
|| cx.tcx.lang_items().result_ok_variant() == Some(variant_id))
&& let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind
&& path_to_local_id(unwrap_arm.body, binding_hir_id)
&& cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty()
&& !contains_return_break_continue_macro(or_arm.body)
{
Some(or_arm)
} else {
None
}
}

View file

@ -127,32 +127,30 @@ where
let closure_expr_snip = some_expr.to_snippet_with_context(cx, expr_ctxt, &mut app);
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
if_chain! {
if !some_expr.needs_unsafe_block;
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
if func.span.eq_ctxt(some_expr.expr.span);
then {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
if !some_expr.needs_unsafe_block
&& let Some(func) = can_pass_as_func(cx, id, some_expr.expr)
&& func.span.eq_ctxt(some_expr.expr.span)
{
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return None;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::MUT) {
"mut "
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return None;
}
""
};
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::MUT) {
"mut "
} else {
""
};
if some_expr.needs_unsafe_block {
format!("|{annotation}{some_binding}| unsafe {{ {closure_expr_snip} }}")
} else {
format!("|{annotation}{some_binding}| {closure_expr_snip}")
}
if some_expr.needs_unsafe_block {
format!("|{annotation}{some_binding}| unsafe {{ {closure_expr_snip} }}")
} else {
format!("|{annotation}{some_binding}| {closure_expr_snip}")
}
}
} else if !is_wild_none && explicit_ref.is_none() {

View file

@ -26,18 +26,16 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
let output_ty = cx.typeck_results().expr_ty(expr);
let input_ty = cx.typeck_results().expr_ty(ex);
let cast = if_chain! {
if let ty::Adt(_, args) = input_ty.kind();
let input_ty = args.type_at(0);
if let ty::Adt(_, args) = output_ty.kind();
let output_ty = args.type_at(0);
if let ty::Ref(_, output_ty, _) = *output_ty.kind();
if input_ty != output_ty;
then {
".map(|x| x as _)"
} else {
""
}
let cast = if let ty::Adt(_, args) = input_ty.kind()
&& let input_ty = args.type_at(0)
&& let ty::Adt(_, args) = output_ty.kind()
&& let output_ty = args.type_at(0)
&& let ty::Ref(_, output_ty, _) = *output_ty.kind()
&& input_ty != output_ty
{
".map(|x| x as _)"
} else {
""
};
let mut applicability = Applicability::MachineApplicable;
@ -67,17 +65,16 @@ fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<Mutability> {
if_chain! {
if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionSome);
if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., ident, _) = first_pat.kind;
if let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind;
if is_res_lang_ctor(cx, path_res(cx, e), LangItem::OptionSome);
if let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind;
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
then {
return Some(mutabl)
}
if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind
&& is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionSome)
&& let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., ident, _) = first_pat.kind
&& let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind
&& is_res_lang_ctor(cx, path_res(cx, e), LangItem::OptionSome)
&& let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind
&& path2.segments.len() == 1
&& ident.name == path2.segments[0].ident.name
{
return Some(mutabl);
}
None
}

View file

@ -76,79 +76,80 @@ where
),
>,
{
if_chain! {
if !span_contains_comment(cx.sess().source_map(), expr.span);
if iter.len() >= 2;
if cx.typeck_results().expr_ty(expr).is_bool();
if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
let iter_without_last = iter.clone();
if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
if let Some(b0) = find_bool_lit(&first_expr.kind);
if let Some(b1) = find_bool_lit(&last_expr.kind);
if b0 != b1;
if first_guard.is_none() || iter.len() == 0;
if first_attrs.is_empty();
if iter
.all(|arm| {
find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
});
then {
if let Some(last_pat) = last_pat_opt {
if !is_wild(last_pat) {
if !span_contains_comment(cx.sess().source_map(), expr.span)
&& iter.len() >= 2
&& cx.typeck_results().expr_ty(expr).is_bool()
&& let Some((_, last_pat_opt, last_expr, _)) = iter.next_back()
&& let iter_without_last = iter.clone()
&& let Some((first_attrs, _, first_expr, first_guard)) = iter.next()
&& let Some(b0) = find_bool_lit(&first_expr.kind)
&& let Some(b1) = find_bool_lit(&last_expr.kind)
&& b0 != b1
&& (first_guard.is_none() || iter.len() == 0)
&& first_attrs.is_empty()
&& iter.all(|arm| find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty())
{
if let Some(last_pat) = last_pat_opt {
if !is_wild(last_pat) {
return false;
}
}
for arm in iter_without_last.clone() {
if let Some(pat) = arm.1 {
if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some(pat.kind) {
return false;
}
}
for arm in iter_without_last.clone() {
if let Some(pat) = arm.1 {
if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some(pat.kind) {
return false;
}
}
}
// The suggestion may be incorrect, because some arms can have `cfg` attributes
// evaluated into `false` and so such arms will be stripped before.
let mut applicability = Applicability::MaybeIncorrect;
let pat = {
use itertools::Itertools as _;
iter_without_last
.filter_map(|arm| {
let pat_span = arm.1?.span;
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
})
.join(" | ")
};
let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
format!("{pat} if {}", snippet_with_applicability(cx, g.span, "..", &mut applicability))
} else {
pat
};
// strip potential borrows (#6503), but only if the type is a reference
let mut ex_new = ex;
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
ex_new = ex_inner;
}
};
span_lint_and_sugg(
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
&format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
"try",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
true
} else {
false
}
// The suggestion may be incorrect, because some arms can have `cfg` attributes
// evaluated into `false` and so such arms will be stripped before.
let mut applicability = Applicability::MaybeIncorrect;
let pat = {
use itertools::Itertools as _;
iter_without_last
.filter_map(|arm| {
let pat_span = arm.1?.span;
Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
})
.join(" | ")
};
let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
format!(
"{pat} if {}",
snippet_with_applicability(cx, g.span, "..", &mut applicability)
)
} else {
pat
};
// strip potential borrows (#6503), but only if the type is a reference
let mut ex_new = ex;
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
ex_new = ex_inner;
}
};
span_lint_and_sugg(
cx,
MATCH_LIKE_MATCHES_MACRO,
expr.span,
&format!(
"{} expression looks like `matches!` macro",
if is_if_let { "if let .. else" } else { "match" }
),
"try",
format!(
"{}matches!({}, {pat_and_guard})",
if b0 { "" } else { "!" },
snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
),
applicability,
);
true
} else {
false
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
@ -10,39 +9,29 @@ use rustc_span::sym;
use super::MATCH_ON_VEC_ITEMS;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>) {
if_chain! {
if let Some(idx_expr) = is_vec_indexing(cx, scrutinee);
if let ExprKind::Index(vec, idx, _) = idx_expr.kind;
then {
// FIXME: could be improved to suggest surrounding every pattern with Some(_),
// but only when `or_patterns` are stabilized.
span_lint_and_sugg(
cx,
MATCH_ON_VEC_ITEMS,
scrutinee.span,
"indexing into a vector may panic",
"try",
format!(
"{}.get({})",
snippet(cx, vec.span, ".."),
snippet(cx, idx.span, "..")
),
Applicability::MaybeIncorrect
);
}
if let Some(idx_expr) = is_vec_indexing(cx, scrutinee)
&& let ExprKind::Index(vec, idx, _) = idx_expr.kind
{
// FIXME: could be improved to suggest surrounding every pattern with Some(_),
// but only when `or_patterns` are stabilized.
span_lint_and_sugg(
cx,
MATCH_ON_VEC_ITEMS,
scrutinee.span,
"indexing into a vector may panic",
"try",
format!("{}.get({})", snippet(cx, vec.span, ".."), snippet(cx, idx.span, "..")),
Applicability::MaybeIncorrect,
);
}
}
fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::Index(array, index, _) = expr.kind;
if is_vector(cx, array);
if !is_full_range(cx, index);
then {
return Some(expr);
}
if let ExprKind::Index(array, index, _) = expr.kind
&& is_vector(cx, array)
&& !is_full_range(cx, index)
{
return Some(expr);
}
None

View file

@ -66,25 +66,23 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
let mut local_map: HirIdMap<HirId> = HirIdMap::default();
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
if_chain! {
if let Some(a_id) = path_to_local(a);
if let Some(b_id) = path_to_local(b);
let entry = match local_map.entry(a_id) {
if let Some(a_id) = path_to_local(a)
&& let Some(b_id) = path_to_local(b)
&& let entry = match local_map.entry(a_id) {
HirIdMapEntry::Vacant(entry) => entry,
// check if using the same bindings as before
HirIdMapEntry::Occupied(entry) => return *entry.get() == b_id,
};
// the names technically don't have to match; this makes the lint more conservative
if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
if cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b);
if pat_contains_local(lhs.pat, a_id);
if pat_contains_local(rhs.pat, b_id);
then {
entry.insert(b_id);
true
} else {
false
}
// the names technically don't have to match; this makes the lint more conservative
&& cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id)
&& cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b)
&& pat_contains_local(lhs.pat, a_id)
&& pat_contains_local(rhs.pat, b_id)
{
entry.insert(b_id);
true
} else {
false
}
};
// Arms with a guard are ignored, those cant always be merged together

View file

@ -20,21 +20,16 @@ enum CaseMethod {
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
if_chain! {
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind();
if let ty::Str = ty.kind();
then {
let mut visitor = MatchExprVisitor {
cx,
case_method: None,
};
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind()
&& let ty::Str = ty.kind()
{
let mut visitor = MatchExprVisitor { cx, case_method: None };
visitor.visit_expr(scrutinee);
visitor.visit_expr(scrutinee);
if let Some(case_method) = visitor.case_method {
if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) {
lint(cx, &case_method, bad_case_span, bad_case_sym.as_str());
}
if let Some(case_method) = visitor.case_method {
if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) {
lint(cx, &case_method, bad_case_span, bad_case_sym.as_str());
}
}
}
@ -88,17 +83,15 @@ fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(
};
for arm in arms {
if_chain! {
if let PatKind::Lit(Expr {
kind: ExprKind::Lit(lit),
..
}) = arm.pat.kind;
if let LitKind::Str(symbol, _) = lit.node;
let input = symbol.as_str();
if !case_check(input);
then {
return Some((lit.span, symbol));
}
if let PatKind::Lit(Expr {
kind: ExprKind::Lit(lit),
..
}) = arm.pat.kind
&& let LitKind::Str(symbol, _) = lit.node
&& let input = symbol.as_str()
&& !case_check(input)
{
return Some((lit.span, symbol));
}
}

View file

@ -34,20 +34,19 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'
}
}
}
if_chain! {
if matching_wild;
if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span);
if is_panic(cx, macro_call.def_id);
then {
// `Err(_)` or `Err(_e)` arm with `panic!` found
span_lint_and_note(cx,
MATCH_WILD_ERR_ARM,
arm.pat.span,
&format!("`Err({ident_bind_name})` matches all errors"),
None,
"match each error separately or use the error output, or use `.expect(msg)` if the error case is unreachable",
);
}
if matching_wild
&& let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span)
&& is_panic(cx, macro_call.def_id)
{
// `Err(_)` or `Err(_e)` arm with `panic!` found
span_lint_and_note(
cx,
MATCH_WILD_ERR_ARM,
arm.pat.span,
&format!("`Err({ident_bind_name})` matches all errors"),
None,
"match each error separately or use the error output, or use `.expect(msg)` if the error case is unreachable",
);
}
}
}

View file

@ -5,7 +5,6 @@ use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop};
use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr};
use clippy_utils::{higher, is_expn_of, is_trait_method};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -35,15 +34,13 @@ pub(super) fn check_if_let<'tcx>(
// Extract the generic arguments out of a type
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
if_chain! {
if let ty::Adt(_, subs) = ty.kind();
if let Some(sub) = subs.get(index);
if let GenericArgKind::Type(sub_ty) = sub.unpack();
then {
Some(sub_ty)
} else {
None
}
if let ty::Adt(_, subs) = ty.kind()
&& let Some(sub) = subs.get(index)
&& let GenericArgKind::Type(sub_ty) = sub.unpack()
{
Some(sub_ty)
} else {
None
}
}
@ -142,14 +139,12 @@ fn find_sugg_for_if_let<'tcx>(
let needs_drop = needs_ordered_drop(cx, check_ty) || any_temporaries_need_ordered_drop(cx, let_expr);
// check that `while_let_on_iterator` lint does not trigger
if_chain! {
if keyword == "while";
if let ExprKind::MethodCall(method_path, ..) = let_expr.kind;
if method_path.ident.name == sym::next;
if is_trait_method(cx, let_expr, sym::Iterator);
then {
return;
}
if keyword == "while"
&& let ExprKind::MethodCall(method_path, ..) = let_expr.kind
&& method_path.ident.name == sym::next
&& is_trait_method(cx, let_expr, sym::Iterator)
{
return;
}
let result_expr = match &let_expr.kind {

View file

@ -6,25 +6,22 @@ use rustc_middle::ty;
use super::REST_PAT_IN_FULLY_BOUND_STRUCTS;
pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) {
if_chain! {
if !pat.span.from_expansion();
if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
if let Some(def_id) = path.res.opt_def_id();
let ty = cx.tcx.type_of(def_id).instantiate_identity();
if let ty::Adt(def, _) = ty.kind();
if def.is_struct() || def.is_union();
if fields.len() == def.non_enum_variant().fields.len();
if !def.non_enum_variant().is_field_list_non_exhaustive();
then {
span_lint_and_help(
cx,
REST_PAT_IN_FULLY_BOUND_STRUCTS,
pat.span,
"unnecessary use of `..` pattern in struct binding. All fields were already bound",
None,
"consider removing `..` from this binding",
);
}
if !pat.span.from_expansion()
&& let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind
&& let Some(def_id) = path.res.opt_def_id()
&& let ty = cx.tcx.type_of(def_id).instantiate_identity()
&& let ty::Adt(def, _) = ty.kind()
&& (def.is_struct() || def.is_union())
&& fields.len() == def.non_enum_variant().fields.len()
&& !def.non_enum_variant().is_field_list_non_exhaustive()
{
span_lint_and_help(
cx,
REST_PAT_IN_FULLY_BOUND_STRUCTS,
pat.span,
"unnecessary use of `..` pattern in struct binding. All fields were already bound",
None,
"consider removing `..` from this binding",
);
}
}

View file

@ -89,50 +89,52 @@ fn report_single_pattern(
});
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
let (msg, sugg) = if_chain! {
if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait();
if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait();
if ty.is_integral() || ty.is_char() || ty.is_str()
|| (implements_trait(cx, ty, spe_trait_id, &[])
&& implements_trait(cx, ty, pe_trait_id, &[ty.into()]));
then {
// scrutinee derives PartialEq and the pattern is a constant.
let pat_ref_count = match pat.kind {
// string literals are already a reference.
PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
_ => pat_ref_count,
};
// References are only implicitly added to the pattern, so no overflow here.
// e.g. will work: match &Some(_) { Some(_) => () }
// will not: match Some(_) { &Some(_) => () }
let ref_count_diff = ty_ref_count - pat_ref_count;
let (msg, sugg) = if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind
&& let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex))
&& let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait()
&& let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait()
&& (ty.is_integral()
|| ty.is_char()
|| ty.is_str()
|| (implements_trait(cx, ty, spe_trait_id, &[]) && implements_trait(cx, ty, pe_trait_id, &[ty.into()])))
{
// scrutinee derives PartialEq and the pattern is a constant.
let pat_ref_count = match pat.kind {
// string literals are already a reference.
PatKind::Lit(Expr {
kind: ExprKind::Lit(lit),
..
}) if lit.node.is_str() => pat_ref_count + 1,
_ => pat_ref_count,
};
// References are only implicitly added to the pattern, so no overflow here.
// e.g. will work: match &Some(_) { Some(_) => () }
// will not: match Some(_) { &Some(_) => () }
let ref_count_diff = ty_ref_count - pat_ref_count;
// Try to remove address of expressions first.
let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
let ref_count_diff = ref_count_diff - removed;
// Try to remove address of expressions first.
let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
let ref_count_diff = ref_count_diff - removed;
let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
let sugg = format!(
"if {} == {}{} {}{els_str}",
snippet(cx, ex.span, ".."),
// PartialEq for different reference counts may not exist.
"&".repeat(ref_count_diff),
snippet(cx, arms[0].pat.span, ".."),
expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app),
);
(msg, sugg)
} else {
let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
let sugg = format!(
"if let {} = {} {}{els_str}",
snippet(cx, arms[0].pat.span, ".."),
snippet(cx, ex.span, ".."),
expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app),
);
(msg, sugg)
}
let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
let sugg = format!(
"if {} == {}{} {}{els_str}",
snippet(cx, ex.span, ".."),
// PartialEq for different reference counts may not exist.
"&".repeat(ref_count_diff),
snippet(cx, arms[0].pat.span, ".."),
expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app),
);
(msg, sugg)
} else {
let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
let sugg = format!(
"if let {} = {} {}{els_str}",
snippet(cx, arms[0].pat.span, ".."),
snippet(cx, ex.span, ".."),
expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app),
);
(msg, sugg)
};
span_lint_and_sugg(cx, lint, expr.span, msg, "try", sugg, app);

View file

@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
@ -22,59 +21,57 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
// #[allow(unreachable_code)]
// val,
// };
if_chain! {
if let ExprKind::Call(match_fun, [try_arg, ..]) = scrutinee.kind;
if let ExprKind::Path(ref match_fun_path) = match_fun.kind;
if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..));
if let ExprKind::Call(err_fun, [err_arg, ..]) = try_arg.kind;
if is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr);
if let Some(return_ty) = find_return_type(cx, &expr.kind);
then {
let prefix;
let suffix;
let err_ty;
if let ExprKind::Call(match_fun, [try_arg, ..]) = scrutinee.kind
&& let ExprKind::Path(ref match_fun_path) = match_fun.kind
&& matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..))
&& let ExprKind::Call(err_fun, [err_arg, ..]) = try_arg.kind
&& is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr)
&& let Some(return_ty) = find_return_type(cx, &expr.kind)
{
let prefix;
let suffix;
let err_ty;
if let Some(ty) = result_error_type(cx, return_ty) {
prefix = "Err(";
suffix = ")";
err_ty = ty;
} else if let Some(ty) = poll_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Err(";
suffix = "))";
err_ty = ty;
} else if let Some(ty) = poll_option_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Some(Err(";
suffix = ")))";
err_ty = ty;
} else {
return;
};
if let Some(ty) = result_error_type(cx, return_ty) {
prefix = "Err(";
suffix = ")";
err_ty = ty;
} else if let Some(ty) = poll_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Err(";
suffix = "))";
err_ty = ty;
} else if let Some(ty) = poll_option_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Some(Err(";
suffix = ")))";
err_ty = ty;
} else {
return;
};
let expr_err_ty = cx.typeck_results().expr_ty(err_arg);
let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt());
let mut applicability = Applicability::MachineApplicable;
let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability);
let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) {
"" // already returns
} else {
"return "
};
let suggestion = if err_ty == expr_err_ty {
format!("{ret_prefix}{prefix}{origin_snippet}{suffix}")
} else {
format!("{ret_prefix}{prefix}{origin_snippet}.into(){suffix}")
};
let expr_err_ty = cx.typeck_results().expr_ty(err_arg);
let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt());
let mut applicability = Applicability::MachineApplicable;
let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability);
let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) {
"" // already returns
} else {
"return "
};
let suggestion = if err_ty == expr_err_ty {
format!("{ret_prefix}{prefix}{origin_snippet}{suffix}")
} else {
format!("{ret_prefix}{prefix}{origin_snippet}.into(){suffix}")
};
span_lint_and_sugg(
cx,
TRY_ERR,
expr.span,
"returning an `Err(_)` with the `?` operator",
"try",
suggestion,
applicability,
);
}
span_lint_and_sugg(
cx,
TRY_ERR,
expr.span,
"returning an `Err(_)` with the `?` operator",
"try",
suggestion,
applicability,
);
}
}
@ -92,51 +89,42 @@ fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> O
/// Extracts the error type from Result<T, E>.
fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(_, subst) = ty.kind();
if is_type_diagnostic_item(cx, ty, sym::Result);
then {
Some(subst.type_at(1))
} else {
None
}
if let ty::Adt(_, subst) = ty.kind()
&& is_type_diagnostic_item(cx, ty, sym::Result)
{
Some(subst.type_at(1))
} else {
None
}
}
/// Extracts the error type from Poll<Result<T, E>>.
fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
if cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did());
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did());
then {
Some(ready_subst.type_at(1))
} else {
None
}
if let ty::Adt(def, subst) = ty.kind()
&& cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did())
&& let ready_ty = subst.type_at(0)
&& let ty::Adt(ready_def, ready_subst) = ready_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Result, ready_def.did())
{
Some(ready_subst.type_at(1))
} else {
None
}
}
/// Extracts the error type from Poll<Option<Result<T, E>>>.
fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
if cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did());
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did());
let some_ty = ready_subst.type_at(0);
if let ty::Adt(some_def, some_subst) = some_ty.kind();
if cx.tcx.is_diagnostic_item(sym::Result, some_def.did());
then {
Some(some_subst.type_at(1))
} else {
None
}
if let ty::Adt(def, subst) = ty.kind()
&& cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did())
&& let ready_ty = subst.type_at(0)
&& let ty::Adt(ready_def, ready_subst) = ready_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Option, ready_def.did())
&& let some_ty = ready_subst.type_at(0)
&& let ty::Adt(some_def, some_subst) = some_ty.kind()
&& cx.tcx.is_diagnostic_item(sym::Result, some_def.did())
{
Some(some_subst.type_at(1))
} else {
None
}
}

View file

@ -4,7 +4,6 @@ use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_non_aggregate_primitive_type;
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Expr, ExprKind};
@ -125,17 +124,37 @@ fn check_replace_option_with_none(cx: &LateContext<'_>, dest: &Expr<'_>, expr_sp
}
fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
if_chain! {
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(src.hir_id)
// check if replacement is mem::MaybeUninit::uninit().assume_init()
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(src.hir_id);
if cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id);
then {
&& cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id)
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
MEM_REPLACE_WITH_UNINIT,
expr_span,
"replacing with `mem::MaybeUninit::uninit().assume_init()`",
"consider using",
format!(
"std::ptr::read({})",
snippet_with_applicability(cx, dest.span, "", &mut applicability)
),
applicability,
);
return;
}
if let ExprKind::Call(repl_func, []) = src.kind
&& let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
&& let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
{
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
MEM_REPLACE_WITH_UNINIT,
expr_span,
"replacing with `mem::MaybeUninit::uninit().assume_init()`",
"replacing with `mem::uninitialized()`",
"consider using",
format!(
"std::ptr::read({})",
@ -143,40 +162,17 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
),
applicability,
);
return;
}
}
if_chain! {
if let ExprKind::Call(repl_func, []) = src.kind;
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
then {
if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
MEM_REPLACE_WITH_UNINIT,
expr_span,
"replacing with `mem::uninitialized()`",
"consider using",
format!(
"std::ptr::read({})",
snippet_with_applicability(cx, dest.span, "", &mut applicability)
),
applicability,
);
} else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) &&
!cx.typeck_results().expr_ty(src).is_primitive() {
span_lint_and_help(
cx,
MEM_REPLACE_WITH_UNINIT,
expr_span,
"replacing with `mem::zeroed()`",
None,
"consider using a default value or the `take_mut` crate instead",
);
}
} else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id)
&& !cx.typeck_results().expr_ty(src).is_primitive()
{
span_lint_and_help(
cx,
MEM_REPLACE_WITH_UNINIT,
expr_span,
"replacing with `mem::zeroed()`",
None,
"consider using a default value or the `take_mut` crate instead",
);
}
}
}
@ -222,21 +218,19 @@ impl MemReplace {
impl<'tcx> LateLintPass<'tcx> for MemReplace {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(func, [dest, src]) = expr.kind
// Check that `expr` is a call to `mem::replace()`
if let ExprKind::Call(func, [dest, src]) = expr.kind;
if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
then {
// Check that second argument is `Option::None`
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
check_replace_option_with_none(cx, dest, expr.span);
} else if self.msrv.meets(msrvs::MEM_TAKE) {
check_replace_with_default(cx, src, dest, expr.span);
}
check_replace_with_uninit(cx, src, dest, expr.span);
&& let ExprKind::Path(ref func_qpath) = func.kind
&& let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::mem_replace, def_id)
{
// Check that second argument is `Option::None`
if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
check_replace_option_with_none(cx, dest, expr.span);
} else if self.msrv.meets(msrvs::MEM_TAKE) {
check_replace_with_default(cx, src, dest, expr.span);
}
check_replace_with_uninit(cx, src, dest, expr.span);
}
}
extract_msrv_attr!(LateContext);

View file

@ -3,7 +3,6 @@ use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and
use clippy_utils::peel_blocks;
use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::visitors::find_all_ret_expressions;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
@ -70,57 +69,50 @@ pub(crate) trait BindInsteadOfMap {
closure_expr: &hir::Expr<'_>,
closure_args_span: Span,
) -> bool {
if_chain! {
if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind;
if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind;
if Self::is_variant(cx, path.res);
if !contains_return(inner_expr);
if let Some(msg) = Self::lint_msg(cx);
then {
let mut app = Applicability::MachineApplicable;
let some_inner_snip = snippet_with_context(cx, inner_expr.span, closure_expr.span.ctxt(), "_", &mut app).0;
if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind
&& let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind
&& Self::is_variant(cx, path.res)
&& !contains_return(inner_expr)
&& let Some(msg) = Self::lint_msg(cx)
{
let mut app = Applicability::MachineApplicable;
let some_inner_snip = snippet_with_context(cx, inner_expr.span, closure_expr.span.ctxt(), "_", &mut app).0;
let closure_args_snip = snippet(cx, closure_args_span, "..");
let option_snip = snippet(cx, recv.span, "..");
let note = format!("{option_snip}.{}({closure_args_snip} {some_inner_snip})", Self::GOOD_METHOD_NAME);
span_lint_and_sugg(
cx,
BIND_INSTEAD_OF_MAP,
expr.span,
&msg,
"try",
note,
app,
);
true
} else {
false
}
let closure_args_snip = snippet(cx, closure_args_span, "..");
let option_snip = snippet(cx, recv.span, "..");
let note = format!(
"{option_snip}.{}({closure_args_snip} {some_inner_snip})",
Self::GOOD_METHOD_NAME
);
span_lint_and_sugg(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, "try", note, app);
true
} else {
false
}
}
fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
let mut suggs = Vec::new();
let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
if_chain! {
if !ret_expr.span.from_expansion();
if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
if Self::is_variant(cx, path.res);
if !contains_return(arg);
then {
suggs.push((ret_expr.span, arg.span.source_callsite()));
true
} else {
false
}
if !ret_expr.span.from_expansion()
&& let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind
&& let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind
&& Self::is_variant(cx, path.res)
&& !contains_return(arg)
{
suggs.push((ret_expr.span, arg.span.source_callsite()));
true
} else {
false
}
});
let (span, msg) = if_chain! {
if can_sugg;
if let hir::ExprKind::MethodCall(segment, ..) = expr.kind;
if let Some(msg) = Self::lint_msg(cx);
then { (segment.ident.span, msg) } else { return false; }
let (span, msg) = if can_sugg
&& let hir::ExprKind::MethodCall(segment, ..) = expr.kind
&& let Some(msg) = Self::lint_msg(cx)
{
(segment.ident.span, msg)
} else {
return false;
};
span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| {
multispan_sugg_with_applicability(
@ -139,11 +131,12 @@ pub(crate) trait BindInsteadOfMap {
/// Lint use of `_.and_then(|x| Some(y))` for `Option`s
fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool {
if_chain! {
if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
if let Some(vid) = cx.tcx.lang_items().get(Self::VARIANT_LANG_ITEM);
if adt.did() == cx.tcx.parent(vid);
then {} else { return false; }
if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
&& let Some(vid) = cx.tcx.lang_items().get(Self::VARIANT_LANG_ITEM)
&& adt.did() == cx.tcx.parent(vid)
{
} else {
return false;
}
match arg.kind {

View file

@ -3,7 +3,6 @@ use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{path_to_local_id, peel_blocks, peel_ref_operators, strip_pat_refs};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, PatKind};
use rustc_lint::LateContext;
@ -18,53 +17,50 @@ pub(super) fn check<'tcx>(
filter_recv: &'tcx Expr<'_>,
filter_arg: &'tcx Expr<'_>,
) {
if_chain! {
if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind;
let body = cx.tcx.hir().body(body);
if let [param] = body.params;
if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind;
if let ExprKind::Binary(ref op, l, r) = body.value.kind;
if op.node == BinOpKind::Eq;
if is_type_diagnostic_item(cx,
cx.typeck_results().expr_ty(filter_recv).peel_refs(),
sym::SliceIter);
let operand_is_arg = |expr| {
if let ExprKind::Closure(&Closure { body, .. }) = filter_arg.kind
&& let body = cx.tcx.hir().body(body)
&& let [param] = body.params
&& let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind
&& let ExprKind::Binary(ref op, l, r) = body.value.kind
&& op.node == BinOpKind::Eq
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv).peel_refs(), sym::SliceIter)
&& let operand_is_arg = (|expr| {
let expr = peel_ref_operators(cx, peel_blocks(expr));
path_to_local_id(expr, arg_id)
};
let needle = if operand_is_arg(l) {
})
&& let needle = if operand_is_arg(l) {
r
} else if operand_is_arg(r) {
l
} else {
return;
};
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
if !is_local_used(cx, needle, arg_id);
then {
let haystack = if let ExprKind::MethodCall(path, receiver, [], _) =
filter_recv.kind {
let p = path.ident.name;
if p == sym::iter || p == sym::iter_mut {
receiver
} else {
filter_recv
}
}
&& ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind()
&& !is_local_used(cx, needle, arg_id)
{
let haystack = if let ExprKind::MethodCall(path, receiver, [], _) = filter_recv.kind {
let p = path.ident.name;
if p == sym::iter || p == sym::iter_mut {
receiver
} else {
filter_recv
};
let mut applicability = Applicability::MaybeIncorrect;
span_lint_and_sugg(
cx,
NAIVE_BYTECOUNT,
expr.span,
"you appear to be counting bytes the naive way",
"consider using the bytecount crate",
format!("bytecount::count({}, {})",
snippet_with_applicability(cx, haystack.span, "..", &mut applicability),
snippet_with_applicability(cx, needle.span, "..", &mut applicability)),
applicability,
);
}
}
} else {
filter_recv
};
let mut applicability = Applicability::MaybeIncorrect;
span_lint_and_sugg(
cx,
NAIVE_BYTECOUNT,
expr.span,
"you appear to be counting bytes the naive way",
"consider using the bytecount crate",
format!(
"bytecount::count({}, {})",
snippet_with_applicability(cx, haystack.span, "..", &mut applicability),
snippet_with_applicability(cx, needle.span, "..", &mut applicability)
),
applicability,
);
};
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_lang_item;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
@ -14,23 +13,24 @@ pub(super) fn check<'tcx>(
count_recv: &'tcx hir::Expr<'_>,
bytes_recv: &'tcx hir::Expr<'_>,
) {
if_chain! {
if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(bytes_id);
if cx.tcx.type_of(impl_id).instantiate_identity().is_str();
let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs();
if ty.is_str() || is_type_lang_item(cx, ty, hir::LangItem::String);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
BYTES_COUNT_TO_LEN,
expr.span,
"using long and hard to read `.bytes().count()`",
"consider calling `.len()` instead",
format!("{}.len()", snippet_with_applicability(cx, bytes_recv.span, "..", &mut applicability)),
applicability
);
}
if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(bytes_id)
&& cx.tcx.type_of(impl_id).instantiate_identity().is_str()
&& let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs()
&& (ty.is_str() || is_type_lang_item(cx, ty, hir::LangItem::String))
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
BYTES_COUNT_TO_LEN,
expr.span,
"using long and hard to read `.bytes().count()`",
"consider calling `.len()` instead",
format!(
"{}.len()",
snippet_with_applicability(cx, bytes_recv.span, "..", &mut applicability)
),
applicability,
);
};
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_lang_item;
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem};
@ -27,51 +26,54 @@ pub(super) fn check<'tcx>(
}
}
if_chain! {
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(method_id);
if cx.tcx.type_of(impl_id).instantiate_identity().is_str();
if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = arg.kind;
if (2..=6).contains(&ext_literal.as_str().len());
let ext_str = ext_literal.as_str();
if ext_str.starts_with('.');
if ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
|| ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit());
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
if recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String);
then {
span_lint_and_then(
cx,
CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
recv.span.to(call_span),
"case-sensitive file extension comparison",
|diag| {
diag.help("consider using a case-insensitive comparison instead");
if let Some(mut recv_source) = snippet_opt(cx, recv.span) {
if !cx.typeck_results().expr_ty(recv).is_ref() {
recv_source = format!("&{recv_source}");
}
let suggestion_source = reindent_multiline(
format!(
"std::path::Path::new({})
.extension()
.map_or(false, |ext| ext.eq_ignore_ascii_case(\"{}\"))",
recv_source, ext_str.strip_prefix('.').unwrap()).into(),
true,
Some(indent_of(cx, call_span).unwrap_or(0) + 4)
);
diag.span_suggestion(
recv.span.to(call_span),
"use std::path::Path",
suggestion_source,
Applicability::MaybeIncorrect,
);
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_method(method_id)
&& cx.tcx.type_of(impl_id).instantiate_identity().is_str()
&& let ExprKind::Lit(Spanned {
node: LitKind::Str(ext_literal, ..),
..
}) = arg.kind
&& (2..=6).contains(&ext_literal.as_str().len())
&& let ext_str = ext_literal.as_str()
&& ext_str.starts_with('.')
&& (ext_str.chars().skip(1).all(|c| c.is_uppercase() || c.is_ascii_digit())
|| ext_str.chars().skip(1).all(|c| c.is_lowercase() || c.is_ascii_digit()))
&& let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs()
&& (recv_ty.is_str() || is_type_lang_item(cx, recv_ty, LangItem::String))
{
span_lint_and_then(
cx,
CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
recv.span.to(call_span),
"case-sensitive file extension comparison",
|diag| {
diag.help("consider using a case-insensitive comparison instead");
if let Some(mut recv_source) = snippet_opt(cx, recv.span) {
if !cx.typeck_results().expr_ty(recv).is_ref() {
recv_source = format!("&{recv_source}");
}
let suggestion_source = reindent_multiline(
format!(
"std::path::Path::new({})
.extension()
.map_or(false, |ext| ext.eq_ignore_ascii_case(\"{}\"))",
recv_source,
ext_str.strip_prefix('.').unwrap()
)
.into(),
true,
Some(indent_of(cx, call_span).unwrap_or(0) + 4),
);
diag.span_suggestion(
recv.span.to(call_span),
"use std::path::Path",
suggestion_source,
Applicability::MaybeIncorrect,
);
}
);
}
},
);
}
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{method_chain_args, path_def_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, Lint};
@ -15,34 +14,34 @@ pub(super) fn check(
lint: &'static Lint,
suggest: &str,
) -> bool {
if_chain! {
if let Some(args) = method_chain_args(info.chain, chain_methods);
if let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind;
if let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id));
if Some(id) == cx.tcx.lang_items().option_some_variant();
then {
let mut applicability = Applicability::MachineApplicable;
let self_ty = cx.typeck_results().expr_ty_adjusted(args[0].0).peel_refs();
if let Some(args) = method_chain_args(info.chain, chain_methods)
&& let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind
&& let Some(id) = path_def_id(cx, fun).map(|ctor_id| cx.tcx.parent(ctor_id))
&& Some(id) == cx.tcx.lang_items().option_some_variant()
{
let mut applicability = Applicability::MachineApplicable;
let self_ty = cx.typeck_results().expr_ty_adjusted(args[0].0).peel_refs();
if *self_ty.kind() != ty::Str {
return false;
}
span_lint_and_sugg(
cx,
lint,
info.expr.span,
&format!("you should use the `{suggest}` method"),
"like this",
format!("{}{}.{suggest}({})",
if info.eq { "" } else { "!" },
snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability),
snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)),
applicability,
);
return true;
if *self_ty.kind() != ty::Str {
return false;
}
span_lint_and_sugg(
cx,
lint,
info.expr.span,
&format!("you should use the `{suggest}` method"),
"like this",
format!(
"{}{}.{suggest}({})",
if info.eq { "" } else { "!" },
snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability),
snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)
),
applicability,
);
return true;
}
false

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::method_chain_args;
use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -15,28 +14,28 @@ pub(super) fn check(
lint: &'static Lint,
suggest: &str,
) -> bool {
if_chain! {
if let Some(args) = method_chain_args(info.chain, chain_methods);
if let hir::ExprKind::Lit(lit) = info.other.kind;
if let ast::LitKind::Char(c) = lit.node;
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
lint,
info.expr.span,
&format!("you should use the `{suggest}` method"),
"like this",
format!("{}{}.{suggest}('{}')",
if info.eq { "" } else { "!" },
snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability),
c.escape_default()),
applicability,
);
if let Some(args) = method_chain_args(info.chain, chain_methods)
&& let hir::ExprKind::Lit(lit) = info.other.kind
&& let ast::LitKind::Char(c) = lit.node
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
lint,
info.expr.span,
&format!("you should use the `{suggest}` method"),
"like this",
format!(
"{}{}.{suggest}('{}')",
if info.eq { "" } else { "!" },
snippet_with_applicability(cx, args[0].0.span, "..", &mut applicability),
c.escape_default()
),
applicability,
);
true
} else {
false
}
true
} else {
false
}
}

View file

@ -16,30 +16,27 @@ pub(super) fn check(
err_span: Span,
msrv: &Msrv,
) {
if_chain! {
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
// Test the version to make sure the lint can be showed (expect_err has been
// introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
if msrv.meets(msrvs::EXPECT_ERR);
&& msrv.meets(msrvs::EXPECT_ERR)
// Grabs the `Result<T, E>` type
let result_type = cx.typeck_results().expr_ty(recv);
&& let result_type = cx.typeck_results().expr_ty(recv)
// Tests if the T type in a `Result<T, E>` is not None
if let Some(data_type) = get_data_type(cx, result_type);
&& let Some(data_type) = get_data_type(cx, result_type)
// Tests if the T type in a `Result<T, E>` implements debug
if has_debug_impl(cx, data_type);
then {
span_lint_and_sugg(
cx,
ERR_EXPECT,
err_span.to(expect_span),
"called `.err().expect()` on a `Result` value",
"try",
"expect_err".to_string(),
Applicability::MachineApplicable
&& has_debug_impl(cx, data_type)
{
span_lint_and_sugg(
cx,
ERR_EXPECT,
err_span.to(expect_span),
"called `.err().expect()` on a `Result` value",
"try",
"expect_err".to_string(),
Applicability::MachineApplicable,
);
}
};
}

View file

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
@ -11,35 +10,33 @@ use super::EXTEND_WITH_DRAIN;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if_chain! {
if is_type_diagnostic_item(cx, ty, sym::Vec);
if is_type_diagnostic_item(cx, ty, sym::Vec)
//check source object
if let ExprKind::MethodCall(src_method, drain_vec, [drain_arg], _) = &arg.kind;
if src_method.ident.as_str() == "drain";
let src_ty = cx.typeck_results().expr_ty(drain_vec);
&& let ExprKind::MethodCall(src_method, drain_vec, [drain_arg], _) = &arg.kind
&& src_method.ident.as_str() == "drain"
&& let src_ty = cx.typeck_results().expr_ty(drain_vec)
//check if actual src type is mutable for code suggestion
let immutable = src_ty.is_mutable_ptr();
let src_ty = src_ty.peel_refs();
if is_type_diagnostic_item(cx, src_ty, sym::Vec);
&& let immutable = src_ty.is_mutable_ptr()
&& let src_ty = src_ty.peel_refs()
&& is_type_diagnostic_item(cx, src_ty, sym::Vec)
//check drain range
if let src_ty_range = cx.typeck_results().expr_ty(drain_arg).peel_refs();
if is_type_lang_item(cx, src_ty_range, LangItem::RangeFull);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
EXTEND_WITH_DRAIN,
expr.span,
"use of `extend` instead of `append` for adding the full range of a second vector",
"try",
format!(
"{}.append({}{})",
snippet_with_applicability(cx, recv.span, "..", &mut applicability),
if immutable { "" } else { "&mut " },
snippet_with_applicability(cx, drain_vec.span, "..", &mut applicability)
),
applicability,
);
}
&& let src_ty_range = cx.typeck_results().expr_ty(drain_arg).peel_refs()
&& is_type_lang_item(cx, src_ty_range, LangItem::RangeFull)
{
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
EXTEND_WITH_DRAIN,
expr.span,
"use of `extend` instead of `append` for adding the full range of a second vector",
"try",
format!(
"{}.append({}{})",
snippet_with_applicability(cx, recv.span, "..", &mut applicability),
if immutable { "" } else { "&mut " },
snippet_with_applicability(cx, drain_vec.span, "..", &mut applicability)
),
applicability,
);
}
}

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