Auto merge of #7010 - camsteffen:if-chain-lint, r=llogiq

Internal `if_chain!` lints

changelog: none

We use `if_chain!` a lot. So this enforces some style rules around it, internal only.

Lints when...
* Nested `if`/`if_chain!` can be collapsed
* An `if_chain!` starts with `let` or ends with `let ..; then {..}`
* An `if_chain!` has only one `if`
* An `if_chain!` contains `if .. && ..;` that spans multiple lines
This commit is contained in:
bors 2021-03-31 21:57:48 +00:00
commit 8cf7d9b037
46 changed files with 824 additions and 539 deletions

View File

@ -371,8 +371,8 @@ fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<SymbolStr> {
if meta_item.path.segments.len() > 1; if meta_item.path.segments.len() > 1;
if let tool_name = meta_item.path.segments[0].ident; if let tool_name = meta_item.path.segments[0].ident;
if tool_name.name == sym::clippy; if tool_name.name == sym::clippy;
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
then { then {
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name.as_str()); return Some(lint_name.as_str());
} }
} }

View File

@ -45,52 +45,48 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
if filter.ident.name == sym!(filter); if filter.ident.name == sym!(filter);
if filter_args.len() == 2; if filter_args.len() == 2;
if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind; if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind;
let body = cx.tcx.hir().body(body_id);
if body.params.len() == 1;
if let Some(argname) = get_pat_name(&body.params[0].pat);
if let ExprKind::Binary(ref op, ref l, ref r) = body.value.kind;
if op.node == BinOpKind::Eq;
if match_type(cx,
cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(),
&paths::SLICE_ITER);
then { then {
let body = cx.tcx.hir().body(body_id); let needle = match get_path_name(l) {
if_chain! { Some(name) if check_arg(name, argname, r) => r,
if body.params.len() == 1; _ => match get_path_name(r) {
if let Some(argname) = get_pat_name(&body.params[0].pat); Some(name) if check_arg(name, argname, l) => l,
if let ExprKind::Binary(ref op, ref l, ref r) = body.value.kind; _ => { return; }
if op.node == BinOpKind::Eq;
if match_type(cx,
cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(),
&paths::SLICE_ITER);
then {
let needle = match get_path_name(l) {
Some(name) if check_arg(name, argname, r) => r,
_ => match get_path_name(r) {
Some(name) if check_arg(name, argname, l) => l,
_ => { return; }
}
};
if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() {
return;
}
let haystack = if let ExprKind::MethodCall(ref path, _, ref args, _) =
filter_args[0].kind {
let p = path.ident.name;
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
&args[0]
} else {
&filter_args[0]
}
} else {
&filter_args[0]
};
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,
);
} }
}; };
if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() {
return;
}
let haystack = if let ExprKind::MethodCall(ref path, _, ref args, _) =
filter_args[0].kind {
let p = path.ident.name;
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
&args[0]
} else {
&filter_args[0]
}
} else {
&filter_args[0]
};
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

@ -321,9 +321,8 @@ fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function:
if path.ident.name.as_str() == function; if path.ident.name.as_str() == function;
if let TyKind::Path(QPath::Resolved(None, ref tp)) = &ty.kind; if let TyKind::Path(QPath::Resolved(None, ref tp)) = &ty.kind;
if let [int] = &*tp.segments; if let [int] = &*tp.segments;
let name = &int.ident.name.as_str();
then { then {
let name = &int.ident.name.as_str();
candidates.iter().find(|c| name == *c).cloned() candidates.iter().find(|c| name == *c).cloned()
} else { } else {
None None
@ -336,9 +335,8 @@ fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! { if_chain! {
if let QPath::Resolved(_, ref path) = *path; if let QPath::Resolved(_, ref path) = *path;
if let [ty] = &*path.segments; if let [ty] = &*path.segments;
let name = &ty.ident.name.as_str();
then { then {
let name = &ty.ident.name.as_str();
INTS.iter().find(|c| name == *c).cloned() INTS.iter().find(|c| name == *c).cloned()
} else { } else {
None None

View File

@ -62,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
} }
fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) { fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) {
let expr = strip_singleton_blocks(arm.body);
if_chain! { if_chain! {
let expr = strip_singleton_blocks(arm.body);
if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind; if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind;
// the outer arm pattern and the inner match // the outer arm pattern and the inner match
if expr_in.span.ctxt() == arm.pat.span.ctxt(); if expr_in.span.ctxt() == arm.pat.span.ctxt();

View File

@ -84,22 +84,21 @@ impl LateLintPass<'_> for Default {
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD); if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type. // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
if let QPath::Resolved(None, _path) = qpath; if let QPath::Resolved(None, _path) = qpath;
let expr_ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, ..) = expr_ty.kind();
then { then {
let expr_ty = cx.typeck_results().expr_ty(expr); // TODO: Work out a way to put "whatever the imported way of referencing
if let ty::Adt(def, ..) = expr_ty.kind() { // this type in this file" rather than a fully-qualified type.
// TODO: Work out a way to put "whatever the imported way of referencing let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
// this type in this file" rather than a fully-qualified type. span_lint_and_sugg(
let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did)); cx,
span_lint_and_sugg( DEFAULT_TRAIT_ACCESS,
cx, expr.span,
DEFAULT_TRAIT_ACCESS, &format!("calling `{}` is more clear than this expression", replacement),
expr.span, "try",
&format!("calling `{}` is more clear than this expression", replacement), replacement,
"try", Applicability::Unspecified, // First resolve the TODO above
replacement, );
Applicability::Unspecified, // First resolve the TODO above
);
}
} }
} }
} }
@ -202,14 +201,14 @@ impl LateLintPass<'_> for Default {
let binding_type = if_chain! { let binding_type = if_chain! {
if let ty::Adt(adt_def, substs) = binding_type.kind(); if let ty::Adt(adt_def, substs) = binding_type.kind();
if !substs.is_empty(); if !substs.is_empty();
let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
let generic_args = substs.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
then { then {
let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
let generic_args = substs.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ");
format!("{}::<{}>", adt_def_ty_name, &tys_str) format!("{}::<{}>", adt_def_ty_name, &tys_str)
} else { } else {
binding_type.to_string() binding_type.to_string()

View File

@ -130,8 +130,8 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
}, },
ExprKind::Struct(_, fields, base) => { ExprKind::Struct(_, fields, base) => {
let ty = self.cx.typeck_results().expr_ty(expr);
if_chain! { if_chain! {
let ty = self.cx.typeck_results().expr_ty(expr);
if let Some(adt_def) = ty.ty_adt_def(); if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct(); if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next(); if let Some(variant) = adt_def.variants.iter().next();

View File

@ -32,16 +32,14 @@ impl<'tcx> LateLintPass<'tcx> for Exit {
if let ExprKind::Path(ref path) = path_expr.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 let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::EXIT); if match_def_path(cx, def_id, &paths::EXIT);
let parent = cx.tcx.hir().get_parent_item(e.hir_id);
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent);
// If the next item up is a function we check if it is an entry point
// and only then emit a linter warning
let def_id = cx.tcx.hir().local_def_id(parent);
if !is_entrypoint_fn(cx, def_id.to_def_id());
then { then {
let parent = cx.tcx.hir().get_parent_item(e.hir_id); span_lint(cx, EXIT, e.span, "usage of `process::exit`");
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent) {
// If the next item up is a function we check if it is an entry point
// and only then emit a linter warning
let def_id = cx.tcx.hir().local_def_id(parent);
if !is_entrypoint_fn(cx, def_id.to_def_id()) {
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
}
}
} }
} }
} }

View File

@ -61,8 +61,8 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for FloatLiteral { impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let ty = cx.typeck_results().expr_ty(expr);
if_chain! { if_chain! {
let ty = cx.typeck_results().expr_ty(expr);
if let ty::Float(fty) = *ty.kind(); if let ty::Float(fty) = *ty.kind();
if let hir::ExprKind::Lit(ref lit) = expr.kind; if let hir::ExprKind::Lit(ref lit) = expr.kind;
if let LitKind::Float(sym, lit_float_ty) = lit.node; if let LitKind::Float(sym, lit_float_ty) = lit.node;

View File

@ -217,9 +217,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
if let Some(else_snippet) = snippet_opt(cx, else_span); if let Some(else_snippet) = snippet_opt(cx, else_span);
if let Some(else_pos) = else_snippet.find("else"); if let Some(else_pos) = else_snippet.find("else");
if else_snippet[else_pos..].contains('\n'); if else_snippet[else_pos..].contains('\n');
let else_desc = if is_if(else_) { "if" } else { "{..}" };
then { then {
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note( span_lint_and_note(
cx, cx,
SUSPICIOUS_ELSE_FORMATTING, SUSPICIOUS_ELSE_FORMATTING,

View File

@ -94,13 +94,10 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
type Map = Map<'tcx>; type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
if let Some(mutex) = is_mutex_lock_call(self.cx, expr); self.found_mutex = Some(mutex);
then { self.mutex_lock_called = true;
self.found_mutex = Some(mutex); return;
self.mutex_lock_called = true;
return;
}
} }
visit::walk_expr(self, expr); visit::walk_expr(self, expr);
} }
@ -121,13 +118,10 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
type Map = Map<'tcx>; type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if_chain! { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
if let Some(mutex) = is_mutex_lock_call(self.cx, expr); self.found_mutex = Some(mutex);
then { self.mutex_lock_called = true;
self.found_mutex = Some(mutex); return;
self.mutex_lock_called = true;
return;
}
} }
visit::walk_expr(self, expr); visit::walk_expr(self, expr);
} }

View File

@ -550,6 +550,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
#[cfg(feature = "internal-lints")] #[cfg(feature = "internal-lints")]
&utils::internal_lints::DEFAULT_LINT, &utils::internal_lints::DEFAULT_LINT,
#[cfg(feature = "internal-lints")] #[cfg(feature = "internal-lints")]
&utils::internal_lints::IF_CHAIN_STYLE,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::INTERNING_DEFINED_SYMBOL, &utils::internal_lints::INTERNING_DEFINED_SYMBOL,
#[cfg(feature = "internal-lints")] #[cfg(feature = "internal-lints")]
&utils::internal_lints::INVALID_PATHS, &utils::internal_lints::INVALID_PATHS,
@ -1026,6 +1028,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box utils::inspector::DeepCodeInspector); store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls); store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new()); store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
store.register_late_pass(|| box utils::internal_lints::IfChainStyle);
store.register_late_pass(|| box utils::internal_lints::InvalidPaths); store.register_late_pass(|| box utils::internal_lints::InvalidPaths);
store.register_late_pass(|| box utils::internal_lints::InterningDefinedSymbol::default()); store.register_late_pass(|| box utils::internal_lints::InterningDefinedSymbol::default());
store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default()); store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
@ -1442,6 +1445,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS), LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS), LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS),
LintId::of(&utils::internal_lints::DEFAULT_LINT), LintId::of(&utils::internal_lints::DEFAULT_LINT),
LintId::of(&utils::internal_lints::IF_CHAIN_STYLE),
LintId::of(&utils::internal_lints::INTERNING_DEFINED_SYMBOL), LintId::of(&utils::internal_lints::INTERNING_DEFINED_SYMBOL),
LintId::of(&utils::internal_lints::INVALID_PATHS), LintId::of(&utils::internal_lints::INVALID_PATHS),
LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS), LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS),

View File

@ -46,9 +46,8 @@ pub(super) fn check<'tcx>(
let some_ctor = is_some_ctor(cx, path.res); let some_ctor = is_some_ctor(cx, path.res);
let ok_ctor = is_ok_ctor(cx, path.res); let ok_ctor = is_ok_ctor(cx, path.res);
if some_ctor || ok_ctor; if some_ctor || ok_ctor;
let if_let_type = if some_ctor { "Some" } else { "Ok" };
then { then {
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message // Prepare the error message
let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);

View File

@ -61,8 +61,8 @@ pub(super) fn check<'tcx>(
if_chain! { if_chain! {
if let ExprKind::Index(base_left, idx_left) = lhs.kind; if let ExprKind::Index(base_left, idx_left) = lhs.kind;
if let ExprKind::Index(base_right, idx_right) = rhs.kind; if let ExprKind::Index(base_right, idx_right) = rhs.kind;
if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) if is_slice_like(cx, cx.typeck_results().expr_ty(base_left));
&& is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); if is_slice_like(cx, cx.typeck_results().expr_ty(base_right));
if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); 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 Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts);

View File

@ -26,60 +26,59 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator); if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
if let Some(ref generic_args) = chain_method.args; if let Some(ref generic_args) = chain_method.args;
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
let ty = cx.typeck_results().node_type(ty.hir_id);
if is_type_diagnostic_item(cx, ty, sym::vec_type)
|| is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
|| match_type(cx, ty, &paths::BTREEMAP)
|| is_type_diagnostic_item(cx, ty, sym::hashmap_type);
then { then {
let ty = cx.typeck_results().node_type(ty.hir_id); if method.ident.name == sym!(len) {
if is_type_diagnostic_item(cx, ty, sym::vec_type) || let span = shorten_needless_collect_span(expr);
is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || span_lint_and_sugg(
match_type(cx, ty, &paths::BTREEMAP) || cx,
is_type_diagnostic_item(cx, ty, sym::hashmap_type) { NEEDLESS_COLLECT,
if method.ident.name == sym!(len) { span,
let span = shorten_needless_collect_span(expr); NEEDLESS_COLLECT_MSG,
span_lint_and_sugg( "replace with",
cx, "count()".to_string(),
NEEDLESS_COLLECT, Applicability::MachineApplicable,
span, );
NEEDLESS_COLLECT_MSG, }
"replace with", if method.ident.name == sym!(is_empty) {
"count()".to_string(), let span = shorten_needless_collect_span(expr);
Applicability::MachineApplicable, span_lint_and_sugg(
); cx,
} NEEDLESS_COLLECT,
if method.ident.name == sym!(is_empty) { span,
let span = shorten_needless_collect_span(expr); NEEDLESS_COLLECT_MSG,
span_lint_and_sugg( "replace with",
cx, "next().is_none()".to_string(),
NEEDLESS_COLLECT, Applicability::MachineApplicable,
span, );
NEEDLESS_COLLECT_MSG, }
"replace with", if method.ident.name == sym!(contains) {
"next().is_none()".to_string(), let contains_arg = snippet(cx, args[1].span, "??");
Applicability::MachineApplicable, let span = shorten_needless_collect_span(expr);
); span_lint_and_then(
} cx,
if method.ident.name == sym!(contains) { NEEDLESS_COLLECT,
let contains_arg = snippet(cx, args[1].span, "??"); span,
let span = shorten_needless_collect_span(expr); NEEDLESS_COLLECT_MSG,
span_lint_and_then( |diag| {
cx, let (arg, pred) = contains_arg
NEEDLESS_COLLECT, .strip_prefix('&')
span, .map_or(("&x", &*contains_arg), |s| ("x", s));
NEEDLESS_COLLECT_MSG, diag.span_suggestion(
|diag| { span,
let (arg, pred) = contains_arg "replace with",
.strip_prefix('&') format!(
.map_or(("&x", &*contains_arg), |s| ("x", s)); "any(|{}| x == {})",
diag.span_suggestion( arg, pred
span, ),
"replace with", Applicability::MachineApplicable,
format!( );
"any(|{}| x == {})", }
arg, pred );
),
Applicability::MachineApplicable,
);
}
);
}
} }
} }
} }

View File

@ -256,49 +256,47 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
if let ExprKind::Path(ref seqpath) = seqexpr.kind; if let ExprKind::Path(ref seqpath) = seqexpr.kind;
if let QPath::Resolved(None, ref seqvar) = *seqpath; if let QPath::Resolved(None, ref seqvar) = *seqpath;
if seqvar.segments.len() == 1; if seqvar.segments.len() == 1;
let index_used_directly = path_to_local_id(idx, self.var);
let indexed_indirectly = {
let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var);
walk_expr(&mut used_visitor, idx);
used_visitor.used
};
if indexed_indirectly || index_used_directly;
then { then {
let index_used_directly = path_to_local_id(idx, self.var); if self.prefer_mutable {
let indexed_indirectly = { self.indexed_mut.insert(seqvar.segments[0].ident.name);
let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); }
walk_expr(&mut used_visitor, idx); let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
used_visitor.used match res {
}; Res::Local(hir_id) => {
let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
if indexed_indirectly || index_used_directly { let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
if self.prefer_mutable { let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
self.indexed_mut.insert(seqvar.segments[0].ident.name); if indexed_indirectly {
} self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res {
Res::Local(hir_id) => {
let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
if indexed_indirectly {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
}
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
}
return false; // no need to walk further *on the variable*
} }
Res::Def(DefKind::Static | DefKind::Const, ..) => { if index_used_directly {
if indexed_indirectly { self.indexed_directly.insert(
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); seqvar.segments[0].ident.name,
} (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
if index_used_directly { );
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
}
return false; // no need to walk further *on the variable*
} }
_ => (), return false; // no need to walk further *on the variable*
} }
Res::Def(DefKind::Static | DefKind::Const, ..) => {
if indexed_indirectly {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
}
return false; // no need to walk further *on the variable*
}
_ => (),
} }
} }
} }

View File

@ -63,8 +63,8 @@ pub(super) fn check<'tcx>(
match cx.qpath_res(qpath, pushed_item.hir_id) { match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant // immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => { Res::Local(hir_id) => {
let node = cx.tcx.hir().get(hir_id);
if_chain! { if_chain! {
let node = cx.tcx.hir().get(hir_id);
if let Node::Binding(pat) = node; if let Node::Binding(pat) = node;
if let PatKind::Binding(bind_ann, ..) = pat.kind; if let PatKind::Binding(bind_ann, ..) = pat.kind;
if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);

View File

@ -103,9 +103,8 @@ impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
if_chain! { if_chain! {
if let ExprKind::Path(ref qpath) = ex.kind; if let ExprKind::Path(ref qpath) = ex.kind;
if let QPath::Resolved(None, _) = *qpath; if let QPath::Resolved(None, _) = *qpath;
let res = self.cx.qpath_res(qpath, ex.hir_id);
then { then {
match res { match self.cx.qpath_res(qpath, ex.hir_id) {
Res::Local(hir_id) => { Res::Local(hir_id) => {
self.ids.insert(hir_id); self.ids.insert(hir_id);
}, },

View File

@ -113,8 +113,8 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
} }
} }
let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
if_chain! { if_chain! {
let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
if let Some(marker) = markers.next(); if let Some(marker) = markers.next();
if markers.count() == 0 && variants.len() > 1; if markers.count() == 0 && variants.len() > 1;
then { then {

View File

@ -58,9 +58,9 @@ impl<'tcx> LateLintPass<'tcx> for MapClone {
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(&args[0]);
if is_type_diagnostic_item(cx, ty, sym::option_type) || is_trait_method(cx, e, sym::Iterator); if is_type_diagnostic_item(cx, ty, sym::option_type) || is_trait_method(cx, e, sym::Iterator);
if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind; if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind;
let closure_body = cx.tcx.hir().body(body_id);
let closure_expr = remove_blocks(&closure_body.value);
then { then {
let closure_body = cx.tcx.hir().body(body_id);
let closure_expr = remove_blocks(&closure_body.value);
match closure_body.params[0].pat.kind { match closure_body.params[0].pat.kind {
hir::PatKind::Ref(ref inner, hir::Mutability::Not) => if let hir::PatKind::Binding( hir::PatKind::Ref(ref inner, hir::Mutability::Not) => if let hir::PatKind::Binding(
hir::BindingAnnotation::Unannotated, .., name, None hir::BindingAnnotation::Unannotated, .., name, None

View File

@ -168,17 +168,15 @@ fn unit_closure<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,
) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> { ) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.kind { if_chain! {
if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.kind;
let body = cx.tcx.hir().body(inner_expr_id); let body = cx.tcx.hir().body(inner_expr_id);
let body_expr = &body.value; let body_expr = &body.value;
if decl.inputs.len() == 1;
if_chain! { if is_unit_expression(cx, body_expr);
if decl.inputs.len() == 1; if let Some(binding) = iter_input_pats(&decl, body).next();
if is_unit_expression(cx, body_expr); then {
if let Some(binding) = iter_input_pats(&decl, body).next(); return Some((binding, body_expr));
then {
return Some((binding, body_expr));
}
} }
} }
None None

View File

@ -732,8 +732,8 @@ fn report_single_match_single_pattern(
format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
}); });
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
let (msg, sugg) = if_chain! { let (msg, sugg) = if_chain! {
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait(); if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait();
@ -1480,8 +1480,8 @@ fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[A
/// Returns true if the `ex` match expression is in a local (`let`) statement /// Returns true if the `ex` match expression is in a local (`let`) statement
fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> { fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
let map = &cx.tcx.hir();
if_chain! { if_chain! {
let map = &cx.tcx.hir();
if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)); if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id));
if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id)); if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
then { then {

View File

@ -225,34 +225,33 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
} }
fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
if let ExprKind::Call(ref repl_func, _) = src.kind { if_chain! {
if_chain! { if let ExprKind::Call(ref repl_func, _) = src.kind;
if !in_external_macro(cx.tcx.sess, expr_span); if !in_external_macro(cx.tcx.sess, expr_span);
if let ExprKind::Path(ref repl_func_qpath) = repl_func.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(); if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
if is_diagnostic_assoc_item(cx, repl_def_id, sym::Default) if is_diagnostic_assoc_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath); || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
then { then {
span_lint_and_then( span_lint_and_then(
cx, cx,
MEM_REPLACE_WITH_DEFAULT, MEM_REPLACE_WITH_DEFAULT,
expr_span, expr_span,
"replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`", "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
|diag| { |diag| {
if !in_macro(expr_span) { if !in_macro(expr_span) {
let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, "")); let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
diag.span_suggestion( diag.span_suggestion(
expr_span, expr_span,
"consider using", "consider using",
suggestion, suggestion,
Applicability::MachineApplicable Applicability::MachineApplicable
); );
}
} }
); }
} );
} }
} }
} }

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::Expr; use rustc_hir::Expr;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -10,31 +9,26 @@ use rustc_span::sym;
use super::BYTES_NTH; use super::BYTES_NTH;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) {
if_chain! { let ty = cx.typeck_results().expr_ty(recv).peel_refs();
let ty = cx.typeck_results().expr_ty(recv).peel_refs(); let caller_type = if ty.is_str() {
let caller_type = if is_type_diagnostic_item(cx, ty, sym::string_type) { "str"
Some("String") } else if is_type_diagnostic_item(cx, ty, sym::string_type) {
} else if ty.is_str() { "String"
Some("str") } else {
} else { return;
None };
}; let mut applicability = Applicability::MachineApplicable;
if let Some(caller_type) = caller_type; span_lint_and_sugg(
then { cx,
let mut applicability = Applicability::MachineApplicable; BYTES_NTH,
span_lint_and_sugg( expr.span,
cx, &format!("called `.byte().nth()` on a `{}`", caller_type),
BYTES_NTH, "try",
expr.span, format!(
&format!("called `.byte().nth()` on a `{}`", caller_type), "{}.as_bytes().get({})",
"try", snippet_with_applicability(cx, recv.span, "..", &mut applicability),
format!( snippet_with_applicability(cx, n_arg.span, "..", &mut applicability)
"{}.as_bytes().get({})", ),
snippet_with_applicability(cx, recv.span, "..", &mut applicability), applicability,
snippet_with_applicability(cx, n_arg.span, "..", &mut applicability) );
),
applicability,
);
}
}
} }

View File

@ -40,8 +40,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp
} }
fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String { fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String {
let call_site = expr.span.source_callsite();
if_chain! { if_chain! {
let call_site = expr.span.source_callsite();
if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site); if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site);
let snippet_split = snippet.split("::").collect::<Vec<_>>(); let snippet_split = snippet.split("::").collect::<Vec<_>>();
if let Some((_, elements)) = snippet_split.split_last(); if let Some((_, elements)) = snippet_split.split_last();

View File

@ -1882,11 +1882,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
if_chain! { if_chain! {
if let TraitItemKind::Fn(ref sig, _) = item.kind; if let TraitItemKind::Fn(ref sig, _) = item.kind;
if let Some(first_arg_ty) = sig.decl.inputs.iter().next(); if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
let first_arg_span = first_arg_ty.span;
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty();
then { then {
let first_arg_span = first_arg_ty.span;
let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty();
wrong_self_convention::check( wrong_self_convention::check(
cx, cx,
&item.ident.name.as_str(), &item.ident.name.as_str(),

View File

@ -1,7 +1,6 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use clippy_utils::{is_trait_method, match_qpath, path_to_local_id, paths}; use clippy_utils::{is_trait_method, match_qpath, path_to_local_id, paths};
use if_chain::if_chain;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -54,18 +53,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) { fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> (bool, bool) {
match &expr.kind { match &expr.kind {
hir::ExprKind::Call(ref func, ref args) => { hir::ExprKind::Call(ref func, ref args) => {
if_chain! { if let hir::ExprKind::Path(ref path) = func.kind {
if let hir::ExprKind::Path(ref path) = func.kind; if match_qpath(path, &paths::OPTION_SOME) {
then { if path_to_local_id(&args[0], arg_id) {
if match_qpath(path, &paths::OPTION_SOME) { return (false, false);
if path_to_local_id(&args[0], arg_id) {
return (false, false)
}
return (true, false);
} }
// We don't know. It might do anything. return (true, false);
return (true, true);
} }
// We don't know. It might do anything.
return (true, true);
} }
(true, true) (true, true)
}, },

View File

@ -308,46 +308,45 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
if let PatKind::Binding(an, .., name, None) = local.pat.kind; if let PatKind::Binding(an, .., name, None) = local.pat.kind;
if let Some(ref init) = local.init; if let Some(ref init) = local.init;
if !higher::is_from_for_desugar(local); if !higher::is_from_for_desugar(local);
if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut;
then { then {
if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut { // use the macro callsite when the init span (but not the whole local span)
// use the macro callsite when the init span (but not the whole local span) // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
// comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];` let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() { Sugg::hir_with_macro_callsite(cx, init, "..")
Sugg::hir_with_macro_callsite(cx, init, "..") } else {
} else { Sugg::hir(cx, init, "..")
Sugg::hir(cx, init, "..") };
}; let (mutopt, initref) = if an == BindingAnnotation::RefMut {
let (mutopt, initref) = if an == BindingAnnotation::RefMut { ("mut ", sugg_init.mut_addr())
("mut ", sugg_init.mut_addr()) } else {
} else { ("", sugg_init.addr())
("", sugg_init.addr()) };
}; let tyopt = if let Some(ref ty) = local.ty {
let tyopt = if let Some(ref ty) = local.ty { format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, ".."))
format!(": &{mutopt}{ty}", mutopt=mutopt, ty=snippet(cx, ty.span, "..")) } else {
} else { String::new()
String::new() };
}; span_lint_hir_and_then(
span_lint_hir_and_then( cx,
cx, TOPLEVEL_REF_ARG,
TOPLEVEL_REF_ARG, init.hir_id,
init.hir_id, local.pat.span,
local.pat.span, "`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead",
"`ref` on an entire `let` pattern is discouraged, take a reference with `&` instead", |diag| {
|diag| { diag.span_suggestion(
diag.span_suggestion( stmt.span,
stmt.span, "try",
"try", format!(
format!( "let {name}{tyopt} = {initref};",
"let {name}{tyopt} = {initref};", name=snippet(cx, name.span, ".."),
name=snippet(cx, name.span, ".."), tyopt=tyopt,
tyopt=tyopt, initref=initref,
initref=initref, ),
), Applicability::MachineApplicable,
Applicability::MachineApplicable, );
); }
} );
);
}
} }
}; };
if_chain! { if_chain! {
@ -462,21 +461,18 @@ fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
if_chain! { if_chain! {
if !in_constant(cx, cmp_expr.hir_id); if !in_constant(cx, cmp_expr.hir_id);
if let Some((value, _)) = constant(cx, cx.typeck_results(), expr); if let Some((value, _)) = constant(cx, cx.typeck_results(), expr);
if match value {
Constant::F32(num) => num.is_nan(),
Constant::F64(num) => num.is_nan(),
_ => false,
};
then { then {
let needs_lint = match value { span_lint(
Constant::F32(num) => num.is_nan(), cx,
Constant::F64(num) => num.is_nan(), CMP_NAN,
_ => false, cmp_expr.span,
}; "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
);
if needs_lint {
span_lint(
cx,
CMP_NAN,
cmp_expr.span,
"doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
);
}
} }
} }
} }

View File

@ -97,60 +97,60 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
// impl of `Default` // impl of `Default`
return; return;
} }
if sig.decl.inputs.is_empty() && name == sym::new && cx.access_levels.is_reachable(id) { if_chain! {
if sig.decl.inputs.is_empty();
if name == sym::new;
if cx.access_levels.is_reachable(id);
let self_def_id = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id)); let self_def_id = cx.tcx.hir().local_def_id(cx.tcx.hir().get_parent_item(id));
let self_ty = cx.tcx.type_of(self_def_id); let self_ty = cx.tcx.type_of(self_def_id);
if_chain! { if TyS::same_type(self_ty, return_ty(cx, id));
if TyS::same_type(self_ty, return_ty(cx, id)); if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT);
if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); then {
then { if self.impling_types.is_none() {
if self.impling_types.is_none() { let mut impls = HirIdSet::default();
let mut impls = HirIdSet::default(); cx.tcx.for_each_impl(default_trait_id, |d| {
cx.tcx.for_each_impl(default_trait_id, |d| { if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { if let Some(local_def_id) = ty_def.did.as_local() {
if let Some(local_def_id) = ty_def.did.as_local() { impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
}
}
});
self.impling_types = Some(impls);
}
// Check if a Default implementation exists for the Self type, regardless of
// generics
if_chain! {
if let Some(ref impling_types) = self.impling_types;
if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
if let Some(self_local_did) = self_def.did.as_local();
then {
let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
if impling_types.contains(&self_id) {
return;
} }
} }
} });
self.impling_types = Some(impls);
let generics_sugg = snippet(cx, generics.span, "");
span_lint_hir_and_then(
cx,
NEW_WITHOUT_DEFAULT,
id,
impl_item.span,
&format!(
"you should consider adding a `Default` implementation for `{}`",
self_ty
),
|diag| {
diag.suggest_prepend_item(
cx,
item.span,
"try this",
&create_new_without_default_suggest_msg(self_ty, &generics_sugg),
Applicability::MaybeIncorrect,
);
},
);
} }
// Check if a Default implementation exists for the Self type, regardless of
// generics
if_chain! {
if let Some(ref impling_types) = self.impling_types;
if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
if let Some(self_local_did) = self_def.did.as_local();
let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
if impling_types.contains(&self_id);
then {
return;
}
}
let generics_sugg = snippet(cx, generics.span, "");
span_lint_hir_and_then(
cx,
NEW_WITHOUT_DEFAULT,
id,
impl_item.span,
&format!(
"you should consider adding a `Default` implementation for `{}`",
self_ty
),
|diag| {
diag.suggest_prepend_item(
cx,
item.span,
"try this",
&create_new_without_default_suggest_msg(self_ty, &generics_sugg),
Applicability::MaybeIncorrect,
);
},
);
} }
} }
} }

View File

@ -307,19 +307,17 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
// we should use here as a frozen variant is a potential to be frozen // we should use here as a frozen variant is a potential to be frozen
// similar to unknown layouts. // similar to unknown layouts.
// e.g. `layout_of(...).is_err() || has_frozen_variant(...);` // e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
if is_unfrozen(cx, normalized);
if is_value_unfrozen_poly(cx, *body_id, normalized);
then { then {
let ty = hir_ty_to_ty(cx.tcx, hir_ty); lint(
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); cx,
if is_unfrozen(cx, normalized) Source::Assoc {
&& is_value_unfrozen_poly(cx, *body_id, normalized) item: impl_item.span,
{ },
lint( );
cx,
Source::Assoc {
item: impl_item.span,
},
);
}
} }
} }
}, },

View File

@ -97,11 +97,10 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
fn get_outer_span(expr: &Expr<'_>) -> Span { fn get_outer_span(expr: &Expr<'_>) -> Span {
if_chain! { if_chain! {
if expr.span.from_expansion(); if expr.span.from_expansion();
let first = expr.span.ctxt().outer_expn_data(); let first = expr.span.ctxt().outer_expn_data().call_site;
if first.call_site.from_expansion(); if first.from_expansion();
let second = first.call_site.ctxt().outer_expn_data();
then { then {
second.call_site first.ctxt().outer_expn_data().call_site
} else { } else {
expr.span expr.span
} }

View File

@ -271,19 +271,18 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id:
GenericArg::Type(ty) => Some(ty), GenericArg::Type(ty) => Some(ty),
_ => None, _ => None,
}); });
let replacement = snippet_opt(cx, inner.span);
if let Some(r) = replacement;
then { then {
let replacement = snippet_opt(cx, inner.span); span_lint_and_sugg(
if let Some(r) = replacement { cx,
span_lint_and_sugg( PTR_ARG,
cx, arg.span,
PTR_ARG, "using a reference to `Cow` is not recommended",
arg.span, "change this to",
"using a reference to `Cow` is not recommended", "&".to_owned() + &r,
"change this to", Applicability::Unspecified,
"&".to_owned() + &r, );
Applicability::Unspecified,
);
}
} }
} }
} }

View File

@ -320,32 +320,29 @@ fn match_ident(e: &Expr<'_>) -> Option<Ident> {
} }
fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) { fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) {
let name = path.ident.as_str(); if_chain! {
if name == "zip" && args.len() == 2 { if path.ident.as_str() == "zip";
let iter = &args[0].kind; if let [iter, zip_arg] = args;
let zip_arg = &args[1]; // `.iter()` call
if_chain! { if let ExprKind::MethodCall(ref iter_path, _, ref iter_args, _) = iter.kind;
// `.iter()` call if iter_path.ident.name == sym::iter;
if let ExprKind::MethodCall(ref iter_path, _, ref iter_args, _) = *iter; // range expression in `.zip()` call: `0..x.len()`
if iter_path.ident.name == sym::iter; if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(zip_arg);
// range expression in `.zip()` call: `0..x.len()` if is_integer_const(cx, start, 0);
if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(zip_arg); // `.len()` call
if is_integer_const(cx, start, 0); if let ExprKind::MethodCall(ref len_path, _, ref len_args, _) = end.kind;
// `.len()` call if len_path.ident.name == sym!(len) && len_args.len() == 1;
if let ExprKind::MethodCall(ref len_path, _, ref len_args, _) = end.kind; // `.iter()` and `.len()` called on same `Path`
if len_path.ident.name == sym!(len) && len_args.len() == 1; if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind;
// `.iter()` and `.len()` called on same `Path` if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind;
if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind; if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind; then {
if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments); span_lint(cx,
then { RANGE_ZIP_WITH_LEN,
span_lint(cx, span,
RANGE_ZIP_WITH_LEN, &format!("it is more idiomatic to use `{}.iter().enumerate()`",
span, snippet(cx, iter_args[0].span, "_"))
&format!("it is more idiomatic to use `{}.iter().enumerate()`", );
snippet(cx, iter_args[0].span, "_"))
);
}
} }
} }
} }

View File

@ -113,8 +113,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
if_chain! { if_chain! {
if let hir::ExprKind::Call(ref closure, _) = expr.kind; if let hir::ExprKind::Call(ref closure, _) = expr.kind;
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = closure.kind; if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = closure.kind;
if self.path.segments[0].ident == path.segments[0].ident if self.path.segments[0].ident == path.segments[0].ident;
&& self.path.res == path.res; if self.path.res == path.res;
then { then {
self.count += 1; self.count += 1;
} }

View File

@ -68,14 +68,13 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
// Check for more than one binary operation in the implemented function // Check for more than one binary operation in the implemented function
// Linting when multiple operations are involved can result in false positives // Linting when multiple operations are involved can result in false positives
let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
if_chain! { if_chain! {
let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
if let hir::Node::ImplItem(impl_item) = cx.tcx.hir().get(parent_fn); if let hir::Node::ImplItem(impl_item) = cx.tcx.hir().get(parent_fn);
if let hir::ImplItemKind::Fn(_, body_id) = impl_item.kind; if let hir::ImplItemKind::Fn(_, body_id) = impl_item.kind;
let body = cx.tcx.hir().body(body_id);
let mut visitor = BinaryExprVisitor { nb_binops: 0 };
then { then {
let body = cx.tcx.hir().body(body_id);
let mut visitor = BinaryExprVisitor { nb_binops: 0 };
walk_expr(&mut visitor, &body.value); walk_expr(&mut visitor, &body.value);
if visitor.nb_binops > 1 { if visitor.nb_binops > 1 {
return; return;

View File

@ -36,10 +36,10 @@ pub(super) fn check<'tcx>(
if_chain! { if_chain! {
// if the expression is a float literal and it is unsuffixed then // if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous // add a suffix so the suggestion is valid and unambiguous
let op = format!("{}{}", arg, float_ty.name_str()).into();
if let ExprKind::Lit(lit) = &expr.kind; if let ExprKind::Lit(lit) = &expr.kind;
if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node;
then { then {
let op = format!("{}{}", arg, float_ty.name_str()).into();
match arg { match arg {
sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op),
_ => arg = sugg::Sugg::NonParen(op) _ => arg = sugg::Sugg::NonParen(op)

View File

@ -138,9 +138,8 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
if_chain! { if_chain! {
if let ty::Adt(_, subst) = ty.kind(); if let ty::Adt(_, subst) = ty.kind();
if is_type_diagnostic_item(cx, ty, sym::result_type); if is_type_diagnostic_item(cx, ty, sym::result_type);
let err_ty = subst.type_at(1);
then { then {
Some(err_ty) Some(subst.type_at(1))
} else { } else {
None None
} }
@ -156,10 +155,8 @@ fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind(); if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
if cx.tcx.is_diagnostic_item(sym::result_type, ready_def.did); if cx.tcx.is_diagnostic_item(sym::result_type, ready_def.did);
let err_ty = ready_subst.type_at(1);
then { then {
Some(err_ty) Some(ready_subst.type_at(1))
} else { } else {
None None
} }
@ -179,10 +176,8 @@ fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) ->
if let ty::Adt(some_def, some_subst) = some_ty.kind(); if let ty::Adt(some_def, some_subst) = some_ty.kind();
if cx.tcx.is_diagnostic_item(sym::result_type, some_def.did); if cx.tcx.is_diagnostic_item(sym::result_type, some_def.did);
let err_ty = some_subst.type_at(1);
then { then {
Some(err_ty) Some(some_subst.type_at(1))
} else { } else {
None None
} }

View File

@ -116,8 +116,8 @@ fn check_arg<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Option<(Spa
let ty = cx.tcx.erase_late_bound_regions(ret_ty); let ty = cx.tcx.erase_late_bound_regions(ret_ty);
if ty.is_unit(); if ty.is_unit();
then { then {
let body = cx.tcx.hir().body(body_id);
if_chain! { if_chain! {
let body = cx.tcx.hir().body(body_id);
if let ExprKind::Block(block, _) = body.value.kind; if let ExprKind::Block(block, _) = body.value.kind;
if block.expr.is_none(); if block.expr.is_none();
if let Some(stmt) = block.stmts.last(); if let Some(stmt) = block.stmts.last();

View File

@ -19,9 +19,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if is_questionmark_desugar_marked_call(expr) { if is_questionmark_desugar_marked_call(expr) {
return; return;
} }
let map = &cx.tcx.hir();
let opt_parent_node = map.find(map.get_parent_node(expr.hir_id));
if_chain! { if_chain! {
let map = &cx.tcx.hir();
let opt_parent_node = map.find(map.get_parent_node(expr.hir_id));
if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node; if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node;
if is_questionmark_desugar_marked_call(parent_expr); if is_questionmark_desugar_marked_call(parent_expr);
then { then {

View File

@ -116,8 +116,8 @@ impl LateLintPass<'_> for UnnamedAddress {
if_chain! { if_chain! {
if let ExprKind::Binary(binop, ref left, ref right) = expr.kind; if let ExprKind::Binary(binop, ref left, ref right) = expr.kind;
if is_comparison(binop.node); if is_comparison(binop.node);
if cx.typeck_results().expr_ty_adjusted(left).is_fn_ptr() && if cx.typeck_results().expr_ty_adjusted(left).is_fn_ptr();
cx.typeck_results().expr_ty_adjusted(right).is_fn_ptr(); if cx.typeck_results().expr_ty_adjusted(right).is_fn_ptr();
if is_fn_def(cx, left) || is_fn_def(cx, right); if is_fn_def(cx, left) || is_fn_def(cx, right);
then { then {
span_lint( span_lint(

View File

@ -49,21 +49,18 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
if assoc_item.fn_has_self_parameter; if assoc_item.fn_has_self_parameter;
if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
let body = cx.tcx.hir().body(*body_id); let body = cx.tcx.hir().body(*body_id);
if !body.params.is_empty(); if let [self_param, ..] = body.params;
let self_hir_id = self_param.pat.hir_id;
if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body);
then { then {
let self_param = &body.params[0]; span_lint_and_help(
let self_hir_id = self_param.pat.hir_id; cx,
if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body) { UNUSED_SELF,
span_lint_and_help( self_param.span,
cx, "unused `self` argument",
UNUSED_SELF, None,
self_param.span, "consider refactoring to a associated function",
"unused `self` argument", );
None,
"consider refactoring to a associated function",
);
return;
}
} }
} }
} }

View File

@ -110,31 +110,27 @@ impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
} }
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) { fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
if_chain! { if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindExpectUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item.def_id),
result: Vec::new(),
};
fpu.visit_expr(&body.value);
if let ImplItemKind::Fn(_, body_id) = impl_item.kind; // if we've found one, lint
then { if !fpu.result.is_empty() {
let body = cx.tcx.hir().body(body_id); span_lint_and_then(
let mut fpu = FindExpectUnwrap { cx,
lcx: cx, UNWRAP_IN_RESULT,
typeck_results: cx.tcx.typeck(impl_item.def_id), impl_span,
result: Vec::new(), "used unwrap or expect in a function that returns result or option",
}; move |diag| {
fpu.visit_expr(&body.value); diag.help("unwrap and expect should not be used in a function that returns result or option");
diag.span_note(fpu.result, "potential non-recoverable error(s)");
// if we've found one, lint },
if !fpu.result.is_empty() { );
span_lint_and_then(
cx,
UNWRAP_IN_RESULT,
impl_span,
"used unwrap or expect in a function that returns result or option",
move |diag| {
diag.help(
"unwrap and expect should not be used in a function that returns result or option" );
diag.span_note(fpu.result, "potential non-recoverable error(s)");
});
}
} }
} }
} }

View File

@ -103,25 +103,23 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
); );
} }
} }
if match_trait_method(cx, e, &paths::TRY_INTO_TRAIT) && &*name.ident.as_str() == "try_into" { if_chain! {
if_chain! { if match_trait_method(cx, e, &paths::TRY_INTO_TRAIT) && &*name.ident.as_str() == "try_into";
let a = cx.typeck_results().expr_ty(e); let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]); let b = cx.typeck_results().expr_ty(&args[0]);
if is_type_diagnostic_item(cx, a, sym::result_type); if is_type_diagnostic_item(cx, a, sym::result_type);
if let ty::Adt(_, substs) = a.kind(); if let ty::Adt(_, substs) = a.kind();
if let Some(a_type) = substs.types().next(); if let Some(a_type) = substs.types().next();
if TyS::same_type(a_type, b); if TyS::same_type(a_type, b);
then {
then { span_lint_and_help(
span_lint_and_help( cx,
cx, USELESS_CONVERSION,
USELESS_CONVERSION, e.span,
e.span, &format!("useless conversion to the same type: `{}`", b),
&format!("useless conversion to the same type: `{}`", b), None,
None, "consider removing `.try_into()`",
"consider removing `.try_into()`", );
);
}
} }
} }
}, },
@ -131,10 +129,9 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
if args.len() == 1; if args.len() == 1;
if let ExprKind::Path(ref qpath) = path.kind; if let ExprKind::Path(ref qpath) = path.kind;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]);
then { then {
let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]);
if_chain! { if_chain! {
if match_def_path(cx, def_id, &paths::TRY_FROM); if match_def_path(cx, def_id, &paths::TRY_FROM);
if is_type_diagnostic_item(cx, a, sym::result_type); if is_type_diagnostic_item(cx, a, sym::result_type);

View File

@ -1,8 +1,10 @@
use crate::consts::{constant_simple, Constant}; use crate::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::match_type; use clippy_utils::ty::match_type;
use clippy_utils::{is_expn_of, match_def_path, match_qpath, method_calls, path_to_res, paths, run_lints, SpanlessEq}; use clippy_utils::{
is_else_clause, is_expn_of, match_def_path, match_qpath, method_calls, path_to_res, paths, run_lints, SpanlessEq,
};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId}; use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId};
use rustc_ast::visit::FnKind; use rustc_ast::visit::FnKind;
@ -14,15 +16,17 @@ use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{ use rustc_hir::{
BinOpKind, Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Node, Path, StmtKind, Ty, TyKind, UnOp, BinOpKind, Block, Crate, Expr, ExprKind, HirId, Item, Local, MatchSource, MutTy, Mutability, Node, Path, Stmt,
StmtKind, Ty, TyKind, UnOp,
}; };
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::mir::interpret::ConstValue; use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::{Span, Spanned}; use rustc_span::source_map::Spanned;
use rustc_span::symbol::{Symbol, SymbolStr}; use rustc_span::symbol::{Symbol, SymbolStr};
use rustc_span::{BytePos, Span};
use rustc_typeck::hir_ty_to_ty; use rustc_typeck::hir_ty_to_ty;
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
@ -297,6 +301,13 @@ declare_clippy_lint! {
"unnecessary conversion between Symbol and string" "unnecessary conversion between Symbol and string"
} }
declare_clippy_lint! {
/// Finds unidiomatic usage of `if_chain!`
pub IF_CHAIN_STYLE,
internal,
"non-idiomatic `if_chain!` usage"
}
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
impl EarlyLintPass for ClippyLintsInternal { impl EarlyLintPass for ClippyLintsInternal {
@ -577,9 +588,9 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
if stmts.len() == 1 && block.expr.is_none(); if stmts.len() == 1 && block.expr.is_none();
if let StmtKind::Semi(only_expr) = &stmts[0].kind; if let StmtKind::Semi(only_expr) = &stmts[0].kind;
if let ExprKind::MethodCall(ref ps, _, ref span_call_args, _) = &only_expr.kind; if let ExprKind::MethodCall(ref ps, _, ref span_call_args, _) = &only_expr.kind;
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
then { then {
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
match &*ps.ident.as_str() { match &*ps.ident.as_str() {
"span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => { "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args)); suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args));
@ -1063,3 +1074,159 @@ impl<'tcx> SymbolStrExpr<'tcx> {
} }
} }
} }
declare_lint_pass!(IfChainStyle => [IF_CHAIN_STYLE]);
impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
let (local, after, if_chain_span) = if_chain! {
if let [Stmt { kind: StmtKind::Local(local), .. }, after @ ..] = block.stmts;
if let Some(if_chain_span) = is_expn_of(block.span, "if_chain");
then { (local, after, if_chain_span) } else { return }
};
if is_first_if_chain_expr(cx, block.hir_id, if_chain_span) {
span_lint(
cx,
IF_CHAIN_STYLE,
if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be above the `if_chain!`",
);
} else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
span_lint(
cx,
IF_CHAIN_STYLE,
if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be inside `then { .. }`",
)
}
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let (cond, then, els) = match expr.kind {
ExprKind::If(cond, then, els) => (Some(cond), then, els.is_some()),
ExprKind::Match(
_,
[arm, ..],
MatchSource::IfLetDesugar {
contains_else_clause: els,
},
) => (None, arm.body, els),
_ => return,
};
let then_block = match then.kind {
ExprKind::Block(block, _) => block,
_ => return,
};
let if_chain_span = is_expn_of(expr.span, "if_chain");
if !els {
check_nested_if_chains(cx, expr, then_block, if_chain_span);
}
let if_chain_span = match if_chain_span {
None => return,
Some(span) => span,
};
// check for `if a && b;`
if_chain! {
if let Some(cond) = cond;
if let ExprKind::Binary(op, _, _) = cond.kind;
if op.node == BinOpKind::And;
if cx.sess().source_map().is_multiline(cond.span);
then {
span_lint(cx, IF_CHAIN_STYLE, cond.span, "`if a && b;` should be `if a; if b;`");
}
}
if is_first_if_chain_expr(cx, expr.hir_id, if_chain_span)
&& is_if_chain_then(then_block.stmts, then_block.expr, if_chain_span)
{
span_lint(cx, IF_CHAIN_STYLE, expr.span, "`if_chain!` only has one `if`")
}
}
}
fn check_nested_if_chains(
cx: &LateContext<'_>,
if_expr: &Expr<'_>,
then_block: &Block<'_>,
if_chain_span: Option<Span>,
) {
#[rustfmt::skip]
let (head, tail) = match *then_block {
Block { stmts, expr: Some(tail), .. } => (stmts, tail),
Block {
stmts: &[
ref head @ ..,
Stmt { kind: StmtKind::Expr(tail) | StmtKind::Semi(tail), .. }
],
..
} => (head, tail),
_ => return,
};
if_chain! {
if matches!(tail.kind,
ExprKind::If(_, _, None)
| ExprKind::Match(.., MatchSource::IfLetDesugar { contains_else_clause: false }));
let sm = cx.sess().source_map();
if head
.iter()
.all(|stmt| matches!(stmt.kind, StmtKind::Local(..)) && !sm.is_multiline(stmt.span));
if if_chain_span.is_some() || !is_else_clause(cx.tcx, if_expr);
then {} else { return }
}
let (span, msg) = match (if_chain_span, is_expn_of(tail.span, "if_chain")) {
(None, Some(_)) => (if_expr.span, "this `if` can be part of the inner `if_chain!`"),
(Some(_), None) => (tail.span, "this `if` can be part of the outer `if_chain!`"),
(Some(a), Some(b)) if a != b => (b, "this `if_chain!` can be merged with the outer `if_chain!`"),
_ => return,
};
span_lint_and_then(cx, IF_CHAIN_STYLE, span, msg, |diag| {
let (span, msg) = match head {
[] => return,
[stmt] => (stmt.span, "this `let` statement can also be in the `if_chain!`"),
[a, .., b] => (
a.span.to(b.span),
"these `let` statements can also be in the `if_chain!`",
),
};
diag.span_help(span, msg);
});
}
fn is_first_if_chain_expr(cx: &LateContext<'_>, hir_id: HirId, if_chain_span: Span) -> bool {
cx.tcx
.hir()
.parent_iter(hir_id)
.find(|(_, node)| {
#[rustfmt::skip]
!matches!(node, Node::Expr(Expr { kind: ExprKind::Block(..), .. }) | Node::Stmt(_))
})
.map_or(false, |(id, _)| {
is_expn_of(cx.tcx.hir().span(id), "if_chain") != Some(if_chain_span)
})
}
/// Checks a trailing slice of statements and expression of a `Block` to see if they are part
/// of the `then {..}` portion of an `if_chain!`
fn is_if_chain_then(stmts: &[Stmt<'_>], expr: Option<&Expr<'_>>, if_chain_span: Span) -> bool {
let span = if let [stmt, ..] = stmts {
stmt.span
} else if let Some(expr) = expr {
expr.span
} else {
// empty `then {}`
return true;
};
is_expn_of(span, "if_chain").map_or(true, |span| span != if_chain_span)
}
/// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: Span) -> Span {
let mut span = local.pat.span;
if let Some(init) = local.init {
span = span.to(init.span);
}
span.adjust(if_chain_span.ctxt().outer_expn());
let sm = cx.sess().source_map();
let span = sm.span_extend_to_prev_str(span, "let", false);
let span = sm.span_extend_to_next_char(span, ';', false);
Span::new(span.lo() - BytePos(3), span.hi() + BytePos(1), span.ctxt())
}

View File

@ -108,22 +108,21 @@ impl LateLintPass<'_> for VecInitThenPush {
} }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if self.searcher.is_none() { if_chain! {
if_chain! { if self.searcher.is_none();
if !in_external_macro(cx.sess(), expr.span); if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Assign(left, right, _) = expr.kind; if let ExprKind::Assign(left, right, _) = expr.kind;
if let Some(id) = path_to_local(left); if let Some(id) = path_to_local(left);
if let Some(init_kind) = get_vec_init_kind(cx, right); if let Some(init_kind) = get_vec_init_kind(cx, right);
then { then {
self.searcher = Some(VecPushSearcher { self.searcher = Some(VecPushSearcher {
local_id: id, local_id: id,
init: init_kind, init: init_kind,
lhs_is_local: false, lhs_is_local: false,
lhs_span: left.span, lhs_span: left.span,
err_span: expr.span, err_span: expr.span,
found: 0, found: 0,
}); });
}
} }
} }
} }

View File

@ -0,0 +1,92 @@
#![warn(clippy::if_chain_style)]
#![allow(clippy::no_effect)]
extern crate if_chain;
use if_chain::if_chain;
fn main() {
if true {
let x = "";
// `if_chain!` inside `if`
if_chain! {
if true;
if true;
then {}
}
}
if_chain! {
if true
// multi-line AND'ed conditions
&& false;
if let Some(1) = Some(1);
// `let` before `then`
let x = "";
then {
();
}
}
if_chain! {
// single `if` condition
if true;
then {
let x = "";
// nested if
if true {}
}
}
if_chain! {
// starts with `let ..`
let x = "";
if let Some(1) = Some(1);
then {
let x = "";
let x = "";
// nested if_chain!
if_chain! {
if true;
if true;
then {}
}
}
}
}
fn negative() {
if true {
();
if_chain! {
if true;
if true;
then { (); }
}
}
if_chain! {
if true;
let x = "";
if true;
then { (); }
}
if_chain! {
if true;
if true;
then {
if true { 1 } else { 2 }
} else {
3
}
};
if true {
if_chain! {
if true;
if true;
then {}
}
} else if false {
if_chain! {
if true;
if false;
then {}
}
}
}

View File

@ -0,0 +1,85 @@
error: this `if` can be part of the inner `if_chain!`
--> $DIR/if_chain_style.rs:9:5
|
LL | / if true {
LL | | let x = "";
LL | | // `if_chain!` inside `if`
LL | | if_chain! {
... |
LL | | }
LL | | }
| |_____^
|
= note: `-D clippy::if-chain-style` implied by `-D warnings`
help: this `let` statement can also be in the `if_chain!`
--> $DIR/if_chain_style.rs:10:9
|
LL | let x = "";
| ^^^^^^^^^^^
error: `if a && b;` should be `if a; if b;`
--> $DIR/if_chain_style.rs:19:12
|
LL | if true
| ____________^
LL | | // multi-line AND'ed conditions
LL | | && false;
| |____________________^
error: `let` expression should be inside `then { .. }`
--> $DIR/if_chain_style.rs:24:9
|
LL | let x = "";
| ^^^^^^^^^^^
error: this `if` can be part of the outer `if_chain!`
--> $DIR/if_chain_style.rs:35:13
|
LL | if true {}
| ^^^^^^^^^^
|
help: this `let` statement can also be in the `if_chain!`
--> $DIR/if_chain_style.rs:33:13
|
LL | let x = "";
| ^^^^^^^^^^^
error: `if_chain!` only has one `if`
--> $DIR/if_chain_style.rs:29:5
|
LL | / if_chain! {
LL | | // single `if` condition
LL | | if true;
LL | | then {
... |
LL | | }
LL | | }
| |_____^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: `let` expression should be above the `if_chain!`
--> $DIR/if_chain_style.rs:40:9
|
LL | let x = "";
| ^^^^^^^^^^^
error: this `if_chain!` can be merged with the outer `if_chain!`
--> $DIR/if_chain_style.rs:46:13
|
LL | / if_chain! {
LL | | if true;
LL | | if true;
LL | | then {}
LL | | }
| |_____________^
|
help: these `let` statements can also be in the `if_chain!`
--> $DIR/if_chain_style.rs:43:13
|
LL | / let x = "";
LL | | let x = "";
| |_______________________^
error: aborting due to 7 previous errors