use crate::utils::{
- attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, iter_input_pats, match_def_path,
- must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help, span_lint_and_then,
- trait_ref_of_method, type_is_unsafe_function,
+ attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, is_type_diagnostic_item, iter_input_pats,
+ last_path_segment, match_def_path, must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint,
+ span_lint_and_help, span_lint_and_then, trait_ref_of_method, type_is_unsafe_function,
};
+use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
+use rustc_span::sym;
use rustc_target::spec::abi::Abi;
+use rustc_typeck::hir_ty_to_ty;
declare_clippy_lint! {
/// **What it does:** Checks for functions with too many parameters.
"function or method that could take a `#[must_use]` attribute"
}
+declare_clippy_lint! {
+ /// **What it does:** Checks for public functions that return a `Result`
+ /// with an `Err` type of `()`. It suggests using a custom type that
+ /// implements [`std::error::Error`].
+ ///
+ /// **Why is this bad?** Unit does not implement `Error` and carries no
+ /// further information about what went wrong.
+ ///
+ /// **Known problems:** Of course, this lint assumes that `Result` is used
+ /// for a fallible operation (which is after all the intended use). However
+ /// code may opt to (mis)use it as a basic two-variant-enum. In that case,
+ /// the suggestion is misguided, and the code should use a custom enum
+ /// instead.
+ ///
+ /// **Examples:**
+ /// ```rust
+ /// pub fn read_u8() -> Result<u8, ()> { Err(()) }
+ /// ```
+ /// should become
+ /// ```rust,should_panic
+ /// use std::fmt;
+ ///
+ /// #[derive(Debug)]
+ /// pub struct EndOfStream;
+ ///
+ /// impl fmt::Display for EndOfStream {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// write!(f, "End of Stream")
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for EndOfStream { }
+ ///
+ /// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
+ ///# fn main() {
+ ///# read_u8().unwrap();
+ ///# }
+ /// ```
+ ///
+ /// Note that there are crates that simplify creating the error type, e.g.
+ /// [`thiserror`](https://docs.rs/thiserror).
+ pub RESULT_UNIT_ERR,
+ style,
+ "public function returning `Result` with an `Err` type of `()`"
+}
+
#[derive(Copy, Clone)]
pub struct Functions {
threshold: u64,
MUST_USE_UNIT,
DOUBLE_MUST_USE,
MUST_USE_CANDIDATE,
+ RESULT_UNIT_ERR,
]);
impl<'tcx> LateLintPass<'tcx> for Functions {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attr = must_use_attr(&item.attrs);
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
+ let is_public = cx.access_levels.is_exported(item.hir_id);
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ if is_public {
+ check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
+ }
if let Some(attr) = attr {
- let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
return;
}
- if cx.access_levels.is_exported(item.hir_id)
- && !is_proc_macro(cx.sess(), &item.attrs)
- && attr_by_name(&item.attrs, "no_mangle").is_none()
- {
+ if is_public && !is_proc_macro(cx.sess(), &item.attrs) && attr_by_name(&item.attrs, "no_mangle").is_none() {
check_must_use_candidate(
cx,
&sig.decl,
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
+ let is_public = cx.access_levels.is_exported(item.hir_id);
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ if is_public && trait_ref_of_method(cx, item.hir_id).is_none() {
+ check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
+ }
let attr = must_use_attr(&item.attrs);
if let Some(attr) = attr {
- let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
- } else if cx.access_levels.is_exported(item.hir_id)
+ } else if is_public
&& !is_proc_macro(cx.sess(), &item.attrs)
&& trait_ref_of_method(cx, item.hir_id).is_none()
{
if sig.header.abi == Abi::Rust {
self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi()));
}
+ let is_public = cx.access_levels.is_exported(item.hir_id);
+ let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
+ if is_public {
+ check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
+ }
let attr = must_use_attr(&item.attrs);
if let Some(attr) = attr {
- let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
check_needless_must_use(cx, &sig.decl, item.hir_id, item.span, fn_header_span, attr);
}
if let hir::TraitFn::Provided(eid) = *eid {
let body = cx.tcx.hir().body(eid);
Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id);
- if attr.is_none() && cx.access_levels.is_exported(item.hir_id) && !is_proc_macro(cx.sess(), &item.attrs)
- {
+ if attr.is_none() && is_public && !is_proc_macro(cx.sess(), &item.attrs) {
check_must_use_candidate(
cx,
&sig.decl,
}
}
+fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
+ if_chain! {
+ if !in_external_macro(cx.sess(), item_span);
+ if let hir::FnRetTy::Return(ref ty) = decl.output;
+ if let hir::TyKind::Path(ref qpath) = ty.kind;
+ if is_type_diagnostic_item(cx, hir_ty_to_ty(cx.tcx, ty), sym::result_type);
+ if let Some(ref args) = last_path_segment(qpath).args;
+ if let [_, hir::GenericArg::Type(ref err_ty)] = args.args;
+ if let hir::TyKind::Tup(t) = err_ty.kind;
+ if t.is_empty();
+ then {
+ span_lint_and_help(
+ cx,
+ RESULT_UNIT_ERR,
+ fn_header_span,
+ "this returns a `Result<_, ()>",
+ None,
+ "use a custom Error type instead",
+ );
+ }
+ }
+}
+
fn check_needless_must_use(
cx: &LateContext<'_>,
decl: &hir::FnDecl<'_>,
if let hir::PatKind::Wild = pat.kind {
return false; // ignore `_` patterns
}
- let def_id = pat.hir_id.owner.to_def_id();
- if cx.tcx.has_typeck_results(def_id) {
- is_mutable_ty(cx, &cx.tcx.typeck(def_id.expect_local()).pat_ty(pat), pat.span, tys)
+ if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
+ is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
} else {
false
}
Call(_, args) | MethodCall(_, _, args, _) => {
let mut tys = FxHashSet::default();
for arg in args {
- let def_id = arg.hir_id.owner.to_def_id();
- if self.cx.tcx.has_typeck_results(def_id)
+ if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
self.cx,
- self.cx.tcx.typeck(def_id.expect_local()).expr_ty(arg),
+ self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
arg.span,
&mut tys,
)