Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ document.

[df995e...master](https://github.com/rust-lang/rust-clippy/compare/df995e...master)

### New Lints

* Added [`result_and_inner_method`] to `suspicious`
[#16847](https://github.com/rust-lang/rust-clippy/pull/16847)

## Rust 1.95

Current stable, released 2026-04-16
Expand Down Expand Up @@ -7181,6 +7186,7 @@ Released 2018-09-13
[`repr_packed_without_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#repr_packed_without_abi
[`reserve_after_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#reserve_after_initialization
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
[`result_and_inner_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_and_inner_method
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
[`result_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_filter_map
[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::REDUNDANT_AS_STR_INFO,
crate::methods::REDUNDANT_ITER_CLONED_INFO,
crate::methods::REPEAT_ONCE_INFO,
crate::methods::RESULT_AND_INNER_METHOD_INFO,
crate::methods::RESULT_FILTER_MAP_INFO,
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
crate::methods::RETURN_AND_THEN_INFO,
Expand Down
44 changes: 44 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ mod read_line_without_trim;
mod readonly_write_lock;
mod redundant_as_str;
mod repeat_once;
mod result_and_inner_method;
mod result_map_or_else_none;
mod return_and_then;
mod search_is_some;
Expand Down Expand Up @@ -3273,6 +3274,45 @@ declare_clippy_lint! {
"using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` "
}

declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `Result::and` when the `Ok` inner type has an
/// inherent method also named `and`.
///
/// ### Why is this bad?
/// `Result::and` discards the first `Ok` value and returns the second
/// `Result`. When the inner type also has `and`, the call is likely a
/// mistake: the programmer probably intended to call the inner type's
/// `and` method but forgot to unwrap with `?`.
///
/// ### Example
/// ```rust
/// #[derive(Clone, Copy)]
/// struct Flags(u8);
/// impl Flags {
/// fn and(self, other: Self) -> Self { Self(self.0 & other.0) }
/// }
/// fn flags(x: u8) -> Result<Flags, ()> { Ok(Flags(x)) }
/// // Calls Result::and — discards the first Ok value entirely
/// let _ = flags(0b0001).and(flags(0b1111));
/// ```
/// Use instead:
/// ```rust
/// #[derive(Clone, Copy)]
/// struct Flags(u8);
/// impl Flags {
/// fn and(self, other: Self) -> Self { Self(self.0 & other.0) }
/// }
/// fn flags(x: u8) -> Result<Flags, ()> { Ok(Flags(x)) }
/// // Explicit: calls Result::and
/// let _ = Result::and(flags(0b0001), flags(0b1111));
/// ```
#[clippy::version = "1.89.0"]
pub RESULT_AND_INNER_METHOD,
suspicious,
"calling `Result::and` when the inner type has an inherent `and` method"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for iterators of `Result`s using `.filter(Result::is_ok).map(Result::unwrap)` that may
Expand Down Expand Up @@ -4888,6 +4928,7 @@ impl_lint_pass!(Methods => [
REDUNDANT_AS_STR,
REDUNDANT_ITER_CLONED,
REPEAT_ONCE,
RESULT_AND_INNER_METHOD,
RESULT_FILTER_MAP,
RESULT_MAP_OR_INTO_OPTION,
RETURN_AND_THEN,
Expand Down Expand Up @@ -5146,6 +5187,9 @@ impl Methods {
_ => {},
}
},
(sym::and, [arg]) => {
result_and_inner_method::check(cx, expr, recv, arg);
},
(sym::and_then, [arg]) => {
manual_option_zip::check(cx, expr, recv, arg, self.msrv);
let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg);
Expand Down
36 changes: 36 additions & 0 deletions clippy_lints/src/methods/result_and_inner_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::get_adt_inherent_method;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::symbol::sym;

use super::RESULT_AND_INNER_METHOD;

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) {
let recv_ty = cx.typeck_results().expr_ty(recv);
if !recv_ty.is_diag_item(cx, sym::Result) {
return;
}
let ty::Adt(_, args) = recv_ty.kind() else { return };
let Some(inner_ty) = args.types().next() else { return };
if get_adt_inherent_method(cx, inner_ty, sym::and).is_none() {
return;
}

let mut applicability = Applicability::MachineApplicable;
let recv_snip = snippet_with_applicability(cx, recv.span, "..", &mut applicability);
let arg_snip = snippet_with_applicability(cx, arg.span, "..", &mut applicability);
span_lint_and_sugg(
cx,
RESULT_AND_INNER_METHOD,
expr.span,
"called `Result::and` when the inner type also has an `and` method",
"use fully-qualified syntax to make the intent explicit",
format!("Result::and({recv_snip}, {arg_snip})"),
applicability,
);
}
34 changes: 34 additions & 0 deletions tests/ui/result_and_inner_method.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![warn(clippy::result_and_inner_method)]
#![allow(unused)]

#[derive(Clone, Copy)]
struct Flags(u8);

impl Flags {
fn and(self, other: Self) -> Self {
Self(self.0 & other.0)
}
}

fn flags(x: u8) -> Result<Flags, ()> {
Ok(Flags(x))
}

fn should_lint() {
let _ = Result::and(flags(0b0001), flags(0b1111));
//~^ result_and_inner_method
}

fn no_lint_no_inner_and() {
let a: Result<u8, ()> = Ok(1);
let b: Result<u8, ()> = Ok(2);
// u8 has no inherent `and` method — should not lint
let _ = a.and(b);
}

fn no_lint_already_qualified() {
// Calling Result::and with qualified syntax — no lint
let _ = Result::and(flags(0b0001), flags(0b1111));
}

fn main() {}
34 changes: 34 additions & 0 deletions tests/ui/result_and_inner_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![warn(clippy::result_and_inner_method)]
#![allow(unused)]

#[derive(Clone, Copy)]
struct Flags(u8);

impl Flags {
fn and(self, other: Self) -> Self {
Self(self.0 & other.0)
}
}

fn flags(x: u8) -> Result<Flags, ()> {
Ok(Flags(x))
}

fn should_lint() {
let _ = flags(0b0001).and(flags(0b1111));
//~^ result_and_inner_method
}

fn no_lint_no_inner_and() {
let a: Result<u8, ()> = Ok(1);
let b: Result<u8, ()> = Ok(2);
// u8 has no inherent `and` method — should not lint
let _ = a.and(b);
}

fn no_lint_already_qualified() {
// Calling Result::and with qualified syntax — no lint
let _ = Result::and(flags(0b0001), flags(0b1111));
}

fn main() {}
11 changes: 11 additions & 0 deletions tests/ui/result_and_inner_method.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: called `Result::and` when the inner type also has an `and` method
--> tests/ui/result_and_inner_method.rs:18:13
|
LL | let _ = flags(0b0001).and(flags(0b1111));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use fully-qualified syntax to make the intent explicit: `Result::and(flags(0b0001), flags(0b1111))`
|
= note: `-D clippy::result-and-inner-method` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::result_and_inner_method)]`

error: aborting due to 1 previous error

Loading