Auto merge of #12150 - ithinuel:add_misleading_use_of_ok, r=y21

Add lint for `unused_result_ok`

This PR adds a lint to capture the use of `expr.ok();` when the result is not _really_ used.

This could be interpreted as the result being checked (like it is with `unwrap()` or `expect`) but
it actually only ignores the result.

`let _ = expr;` expresses that intent better.

This was also mentionned in #8994 (although not being the main topic of that issue).

changelog: [`misleading_use_of_ok`]: Add new lint to capture `.ok();` when the result is not _really_ used.
This commit is contained in:
bors 2024-08-06 19:01:41 +00:00
commit 5ead90f13a
8 changed files with 200 additions and 5 deletions

View file

@ -5999,6 +5999,7 @@ Released 2018-09-13
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
[`unused_peekable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_peekable
[`unused_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_result_ok
[`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding
[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit

View file

@ -372,14 +372,14 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
// Some lints have their own directories, delete them
if path.is_dir() {
fs::remove_dir_all(path).ok();
let _ = fs::remove_dir_all(path);
return;
}
// Remove all related test files
fs::remove_file(path.with_extension("rs")).ok();
fs::remove_file(path.with_extension("stderr")).ok();
fs::remove_file(path.with_extension("fixed")).ok();
let _ = fs::remove_file(path.with_extension("rs"));
let _ = fs::remove_file(path.with_extension("stderr"));
let _ = fs::remove_file(path.with_extension("fixed"));
}
fn remove_impl_lint_pass(lint_name_upper: &str, content: &mut String) {
@ -422,7 +422,7 @@ fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io
lint_mod_path.set_file_name(name);
lint_mod_path.set_extension("rs");
fs::remove_file(lint_mod_path).ok();
let _ = fs::remove_file(lint_mod_path);
}
let mut content =

View file

@ -739,6 +739,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::unused_async::UNUSED_ASYNC_INFO,
crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO,
crate::unused_peekable::UNUSED_PEEKABLE_INFO,
crate::unused_result_ok::UNUSED_RESULT_OK_INFO,
crate::unused_rounding::UNUSED_ROUNDING_INFO,
crate::unused_self::UNUSED_SELF_INFO,
crate::unused_unit::UNUSED_UNIT_INFO,

View file

@ -370,6 +370,7 @@ mod unsafe_removed_from_name;
mod unused_async;
mod unused_io_amount;
mod unused_peekable;
mod unused_result_ok;
mod unused_rounding;
mod unused_self;
mod unused_unit;
@ -671,6 +672,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf)));
store.register_late_pass(|_| Box::new(missing_inline::MissingInline));
store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems));
store.register_late_pass(|_| Box::new(unused_result_ok::UnusedResultOk));
store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk));
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));

View file

@ -0,0 +1,59 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::declare_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `Result::ok()` without using the returned `Option`.
///
/// ### Why is this bad?
/// Using `Result::ok()` may look like the result is checked like `unwrap` or `expect` would do
/// but it only silences the warning caused by `#[must_use]` on the `Result`.
///
/// ### Example
/// ```no_run
/// # fn some_function() -> Result<(), ()> { Ok(()) }
/// some_function().ok();
/// ```
/// Use instead:
/// ```no_run
/// # fn some_function() -> Result<(), ()> { Ok(()) }
/// let _ = some_function();
/// ```
#[clippy::version = "1.70.0"]
pub UNUSED_RESULT_OK,
restriction,
"Use of `.ok()` to silence `Result`'s `#[must_use]` is misleading. Use `let _ =` instead."
}
declare_lint_pass!(UnusedResultOk => [UNUSED_RESULT_OK]);
impl LateLintPass<'_> for UnusedResultOk {
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
if let StmtKind::Semi(expr) = stmt.kind
&& let ExprKind::MethodCall(ok_path, recv, [], ..) = expr.kind //check is expr.ok() has type Result<T,E>.ok(, _)
&& ok_path.ident.as_str() == "ok"
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
&& !in_external_macro(cx.sess(), stmt.span)
{
let ctxt = expr.span.ctxt();
let mut applicability = Applicability::MaybeIncorrect;
let snippet = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
let sugg = format!("let _ = {snippet}");
span_lint_and_sugg(
cx,
UNUSED_RESULT_OK,
expr.span,
"ignoring a result with `.ok()` is misleading",
"consider using `let _ =` and removing the call to `.ok()` instead",
sugg,
applicability,
);
}
}
}

View file

@ -0,0 +1,40 @@
//@aux-build:proc_macros.rs
#![warn(clippy::unused_result_ok)]
#![allow(dead_code)]
#[macro_use]
extern crate proc_macros;
fn bad_style(x: &str) {
let _ = x.parse::<u32>();
}
fn good_style(x: &str) -> Option<u32> {
x.parse::<u32>().ok()
}
#[rustfmt::skip]
fn strange_parse(x: &str) {
let _ = x . parse::<i32>();
}
macro_rules! v {
() => {
Ok::<(), ()>(())
};
}
macro_rules! w {
() => {
let _ = Ok::<(), ()>(());
};
}
fn main() {
let _ = v!();
w!();
external! {
Ok::<(),()>(()).ok();
};
}

View file

@ -0,0 +1,40 @@
//@aux-build:proc_macros.rs
#![warn(clippy::unused_result_ok)]
#![allow(dead_code)]
#[macro_use]
extern crate proc_macros;
fn bad_style(x: &str) {
x.parse::<u32>().ok();
}
fn good_style(x: &str) -> Option<u32> {
x.parse::<u32>().ok()
}
#[rustfmt::skip]
fn strange_parse(x: &str) {
x . parse::<i32>() . ok ();
}
macro_rules! v {
() => {
Ok::<(), ()>(())
};
}
macro_rules! w {
() => {
Ok::<(), ()>(()).ok();
};
}
fn main() {
v!().ok();
w!();
external! {
Ok::<(),()>(()).ok();
};
}

View file

@ -0,0 +1,52 @@
error: ignoring a result with `.ok()` is misleading
--> tests/ui/unused_result_ok.rs:9:5
|
LL | x.parse::<u32>().ok();
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::unused-result-ok` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::unused_result_ok)]`
help: consider using `let _ =` and removing the call to `.ok()` instead
|
LL | let _ = x.parse::<u32>();
| ~~~~~~~~~~~~~~~~~~~~~~~~
error: ignoring a result with `.ok()` is misleading
--> tests/ui/unused_result_ok.rs:18:5
|
LL | x . parse::<i32>() . ok ();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider using `let _ =` and removing the call to `.ok()` instead
|
LL | let _ = x . parse::<i32>();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: ignoring a result with `.ok()` is misleading
--> tests/ui/unused_result_ok.rs:34:5
|
LL | v!().ok();
| ^^^^^^^^^
|
help: consider using `let _ =` and removing the call to `.ok()` instead
|
LL | let _ = v!();
| ~~~~~~~~~~~~
error: ignoring a result with `.ok()` is misleading
--> tests/ui/unused_result_ok.rs:29:9
|
LL | Ok::<(), ()>(()).ok();
| ^^^^^^^^^^^^^^^^^^^^^
...
LL | w!();
| ---- in this macro invocation
|
= note: this error originates in the macro `w` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using `let _ =` and removing the call to `.ok()` instead
|
LL | let _ = Ok::<(), ()>(());
| ~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to 4 previous errors