[unconditional_recursion]: catch From -> Into -> From
This commit is contained in:
parent
7ee75f896f
commit
65defdb474
4 changed files with 174 additions and 85 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{expr_or_init, get_trait_def_id, path_def_id};
|
||||
use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id};
|
||||
use rustc_ast::BinOpKind;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir as hir;
|
||||
|
|
@ -19,11 +19,11 @@ use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks that there isn't an infinite recursion in `PartialEq` trait
|
||||
/// implementation.
|
||||
/// Checks that there isn't an infinite recursion in trait
|
||||
/// implementations.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// This is a hard to find infinite recursion which will crashing any code
|
||||
/// This is a hard to find infinite recursion that will crash any code.
|
||||
/// using it.
|
||||
///
|
||||
/// ### Example
|
||||
|
|
@ -201,7 +201,6 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
|
||||
let args = cx
|
||||
.tcx
|
||||
|
|
@ -224,7 +223,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
|
|||
&& let Some(trait_) = impl_.of_trait
|
||||
&& let Some(trait_def_id) = trait_.trait_def_id()
|
||||
// The trait is `ToString`.
|
||||
&& Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"])
|
||||
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_def_id)
|
||||
{
|
||||
let is_bad = match expr.kind {
|
||||
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
|
||||
|
|
@ -291,7 +290,6 @@ where
|
|||
self.map
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
if self.found_default_call {
|
||||
return;
|
||||
|
|
@ -303,7 +301,7 @@ where
|
|||
&& is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id)
|
||||
&& let Some(method_def_id) = path_def_id(self.cx, f)
|
||||
&& let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id)
|
||||
&& Some(trait_def_id) == get_trait_def_id(self.cx, &["core", "default", "Default"])
|
||||
&& self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id)
|
||||
{
|
||||
self.found_default_call = true;
|
||||
span_error(self.cx, self.method_span, expr);
|
||||
|
|
@ -312,10 +310,9 @@ where
|
|||
}
|
||||
|
||||
impl UnconditionalRecursion {
|
||||
#[allow(clippy::unnecessary_def_path)]
|
||||
fn init_default_impl_for_type_if_needed(&mut self, cx: &LateContext<'_>) {
|
||||
if self.default_impl_for_type.is_empty()
|
||||
&& let Some(default_trait_id) = get_trait_def_id(cx, &["core", "default", "Default"])
|
||||
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
|
||||
{
|
||||
let impls = cx.tcx.trait_impls_of(default_trait_id);
|
||||
for (ty, impl_def_ids) in impls.non_blanket_impls() {
|
||||
|
|
@ -394,6 +391,34 @@ impl UnconditionalRecursion {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, expr: &Expr<'_>) {
|
||||
let Some(sig) = cx
|
||||
.typeck_results()
|
||||
.liberated_fn_sigs()
|
||||
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check if we are calling `Into::into` where the node args match with our `From::from` signature:
|
||||
// From::from signature: fn(S1) -> S2
|
||||
// <S1 as Into<S2>>::into(s1), node_args=[S1, S2]
|
||||
// If they do match, then it must mean that it is the blanket impl,
|
||||
// which calls back into our `From::from` again (`Into` is not specializable).
|
||||
// rustc's unconditional_recursion already catches calling `From::from` directly
|
||||
if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr)
|
||||
&& let [s1, s2] = **node_args
|
||||
&& let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type())
|
||||
&& let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id)
|
||||
&& cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)
|
||||
&& get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From)
|
||||
&& s1 == sig.inputs()[0]
|
||||
&& s2 == sig.output()
|
||||
{
|
||||
span_error(cx, method_span, expr);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
|
|
@ -410,10 +435,11 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
|
|||
// Doesn't have a conditional return.
|
||||
&& !has_conditional_return(body, expr)
|
||||
{
|
||||
if name.name == sym::eq || name.name == sym::ne {
|
||||
check_partial_eq(cx, method_span, method_def_id, name, expr);
|
||||
} else if name.name == sym::to_string {
|
||||
check_to_string(cx, method_span, method_def_id, name, expr);
|
||||
match name.name {
|
||||
sym::eq | sym::ne => check_partial_eq(cx, method_span, method_def_id, name, expr),
|
||||
sym::to_string => check_to_string(cx, method_span, method_def_id, name, expr),
|
||||
sym::from => check_from(cx, method_span, method_def_id, expr),
|
||||
_ => {},
|
||||
}
|
||||
self.check_default_new(cx, decl, body, method_span, method_def_id);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue