blob: 4446b912bf7ee649cd5086411b18116c056433a2 [file] [log] [blame] [edit]
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::res::MaybeDef;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::{Span, sym};
declare_clippy_lint! {
/// ### What it does
/// Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
///
/// ### Why is this bad?
/// `TryFrom` should be used if there's a possibility of failure.
///
/// ### Example
/// ```no_run
/// struct Foo(i32);
///
/// impl From<String> for Foo {
/// fn from(s: String) -> Self {
/// Foo(s.parse().unwrap())
/// }
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// struct Foo(i32);
///
/// impl TryFrom<String> for Foo {
/// type Error = ();
/// fn try_from(s: String) -> Result<Self, Self::Error> {
/// if let Ok(parsed) = s.parse() {
/// Ok(Foo(parsed))
/// } else {
/// Err(())
/// }
/// }
/// }
/// ```
#[clippy::version = "pre 1.29.0"]
pub FALLIBLE_IMPL_FROM,
nursery,
"Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
}
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 let hir::ItemKind::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.owner_id, item.span);
}
}
}
fn lint_impl_body(cx: &LateContext<'_>, item_def_id: hir::OwnerId, impl_span: Span) {
use rustc_hir::Expr;
use rustc_hir::intravisit::{self, Visitor};
struct FindPanicUnwrap<'a, 'tcx> {
lcx: &'a LateContext<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
result: Vec<Span>,
}
impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr)
&& is_panic(self.lcx, macro_call.def_id)
{
self.result.push(expr.span);
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &[sym::unwrap]) {
let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
if receiver_ty.is_diag_item(self.lcx, sym::Option) || receiver_ty.is_diag_item(self.lcx, sym::Result) {
self.result.push(expr.span);
}
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}
}
for impl_item in cx
.tcx
.associated_items(item_def_id)
.filter_by_name_unhygienic_and_kind(sym::from, ty::AssocTag::Fn)
{
let impl_item_def_id = impl_item.def_id.expect_local();
// check the body for `begin_panic` or `unwrap`
let body = cx.tcx.hir_body_owned_by(impl_item_def_id);
let mut fpu = FindPanicUnwrap {
lcx: cx,
typeck_results: cx.tcx.typeck(impl_item_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)");
},
);
}
}
}