Skip to content

Commit

Permalink
new lint let_else_on_result_ok
Browse files Browse the repository at this point in the history
  • Loading branch information
Centri3 committed Jun 19, 2023
1 parent 8c8ff5f commit 5800fb5
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4890,6 +4890,7 @@ Released 2018-09-13
[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return
[`let_else_on_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_else_on_result_ok
[`let_underscore_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop
[`let_underscore_future`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_future
[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
Expand Down
4 changes: 2 additions & 2 deletions clippy_dev/src/setup/intellij.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl ClippyProjectInfo {
}

pub fn setup_rustc_src(rustc_path: &str) {
let Ok(rustc_source_dir) = check_and_get_rustc_dir(rustc_path) else {
let Some(rustc_source_dir) = check_and_get_rustc_dir(rustc_path).ok() else {
return
};

Expand Down Expand Up @@ -171,7 +171,7 @@ pub fn remove_rustc_src() {
}

fn remove_rustc_src_from_project(project: &ClippyProjectInfo) -> bool {
let Ok(mut cargo_content) = read_project_file(project.cargo_file) else {
let Some(mut cargo_content) = read_project_file(project.cargo_file).ok() else {
return false;
};
let Some(section_start) = cargo_content.find(RUSTC_PATH_SECTION) else {
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 @@ -233,6 +233,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
crate::len_zero::LEN_WITHOUT_IS_EMPTY_INFO,
crate::len_zero::LEN_ZERO_INFO,
crate::let_else_on_result_ok::LET_ELSE_ON_RESULT_OK_INFO,
crate::let_if_seq::USELESS_LET_IF_SEQ_INFO,
crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO,
crate::let_underscore::LET_UNDERSCORE_LOCK_INFO,
Expand Down
104 changes: 104 additions & 0 deletions clippy_lints/src/let_else_on_result_ok.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use clippy_utils::{
diagnostics::span_lint_and_note,
is_from_proc_macro, is_lang_item_or_ctor,
msrvs::{self, Msrv},
};
use rustc_hir::{LangItem, PatKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};

declare_clippy_lint! {
/// ### What it does
/// Checks for the usage of `Ok` in a `let...else` statement.
///
/// ### Why is this bad?
/// It's a missed opportunity to handle the `Err` variant gracefully. Alternatively, it can be
/// propagated to the caller.
///
/// ### Example
/// ```rust
/// # fn foo() -> Result<(), ()> {
/// # Ok(())
/// # }
/// let Ok(foo) = foo() else {
/// return;
/// };
/// ```
/// Use instead:
/// ```rust
/// # fn foo() -> Result<(), ()> {
/// # Err(())
/// # }
/// let foo = match foo() {
/// Ok(foo) => foo,
/// Err(e) => eprintln!("{e:#?}"),
/// };
/// ```
/// ```rust
/// # fn foo() -> Result<(), ()> {
/// # Ok(())
/// # }
/// let foo = foo()?;
/// # Ok::<(), ()>(())
/// ```
#[clippy::version = "1.72.0"]
pub LET_ELSE_ON_RESULT_OK,
pedantic,
"checks for usage of `Ok` in `let...else` statements"
}
impl_lint_pass!(LetElseOnResultOk => [LET_ELSE_ON_RESULT_OK]);

pub struct LetElseOnResultOk {
msrv: Msrv,
}

impl LetElseOnResultOk {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}

impl<'tcx> LateLintPass<'tcx> for LetElseOnResultOk {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) {
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
return;
}

if let StmtKind::Local(local) = stmt.kind && let Some(els) = local.els {
let spans = {
let mut spans = vec![];
local.pat.walk_always(|pat| {
if let PatKind::TupleStruct(qpath, _, _) = pat.kind
&& let Some(def_id) = cx.qpath_res(&qpath, pat.hir_id).opt_def_id()
&& is_lang_item_or_ctor(cx, def_id, LangItem::ResultOk)
{
spans.push(pat.span);
}
});
spans
};

if !spans.is_empty() && is_from_proc_macro(cx, els) {
return;
};

for span in spans {
span_lint_and_note(
cx,
LET_ELSE_ON_RESULT_OK,
span,
"usage of `let...else` on `Err`",
None,
"consider handling the `Err` variant gracefully or propagating it to the caller",
);
}
}
}
extract_msrv_attr!(LateContext);
}

// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
// TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ mod large_include_file;
mod large_stack_arrays;
mod large_stack_frames;
mod len_zero;
mod let_else_on_result_ok;
mod let_if_seq;
mod let_underscore;
mod let_with_type_underscore;
Expand Down Expand Up @@ -1055,6 +1056,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
store.register_late_pass(|_| Box::new(incorrect_impls::IncorrectImpls));
store.register_late_pass(move |_| Box::new(let_else_on_result_ok::LetElseOnResultOk::new(msrv())));
// add lints here, do not remove this comment, it's used in `new_lint`
}

Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/utils/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ define_Conf! {
///
/// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true),
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN.
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, LET_ELSE_ON_RETURN.
///
/// The minimum rust version that the project supports
(msrv: Option<String> = None),
Expand Down
20 changes: 12 additions & 8 deletions clippy_utils/src/check_proc_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,7 @@ fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
(expr_search_pat(tcx, e).0, Pat::Str("await"))
},
ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, tcx.hir().body(body).value).1),
ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
},
None,
) => (Pat::Str("unsafe"), Pat::Str("}")),
ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
ExprKind::Block(block, _) => block_search_pat(block),
ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)),
ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
ExprKind::Path(ref path) => qpath_search_pat(path),
Expand Down Expand Up @@ -348,6 +341,16 @@ fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
(Pat::OwnedStr(ident.name.as_str().to_owned()), Pat::Str(""))
}

fn block_search_pat(block: &Block<'_>) -> (Pat, Pat) {
let start = if matches!(block.rules, BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)) {
"unsafe"
} else {
"{"
};

(Pat::Str(start), Pat::Str("}"))
}

pub trait WithSearchPat<'cx> {
type Context: LintContext;
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
Expand Down Expand Up @@ -375,6 +378,7 @@ impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat);
impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat);
impl_with_search_pat!(LateContext: Variant with variant_search_pat);
impl_with_search_pat!(LateContext: Ty with ty_search_pat);
impl_with_search_pat!(LateContext: Block with block_search_pat);

impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
type Context = LateContext<'cx>;
Expand Down
17 changes: 10 additions & 7 deletions clippy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2506,13 +2506,16 @@ pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
/// Checks whether a given span has any comment token
/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
let Ok(snippet) = sm.span_to_snippet(span) else { return false };
return tokenize(&snippet).any(|token| {
matches!(
token.kind,
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
)
});
if let Ok(snippet) = sm.span_to_snippet(span) {
return tokenize(&snippet).any(|token| {
matches!(
token.kind,
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }
)
});
}

false
}

/// Return all the comments a given span contains
Expand Down
56 changes: 56 additions & 0 deletions tests/ui/let_else_on_result_ok.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//@aux-build:proc_macros.rs
#![allow(clippy::redundant_pattern_matching, unused)]
#![warn(clippy::let_else_on_result_ok)]

#[macro_use]
extern crate proc_macros;

struct A(Result<&'static A, ()>);

enum AnEnum {
A(Result<&'static A, ()>),
}

fn a() -> Result<(), ()> {
Ok(())
}

fn a_constructor() -> A {
todo!();
}

fn an_enum_constructor() -> AnEnum {
todo!();
}

fn main() {
// Lint
let Ok(_) = a() else {
return;
};
let (Ok(_), true) = (a(), true) else {
return;
};
let [Ok(_), Ok(_)] = [a(), Err(())] else {
return;
};
let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
return;
};
let AnEnum::A(Ok(A(Err(_)))) = an_enum_constructor() else {
return;
};
// Don't lint
let Err(_) = a() else {
return;
};
match a() {
Ok(a) => a,
Err(e) => eprintln!("{e:#?}"),
};
external! {
let Ok(_) = a() else {
return;
};
}
}
75 changes: 75 additions & 0 deletions tests/ui/let_else_on_result_ok.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:28:9
|
LL | let Ok(_) = a() else {
| ^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller
= note: `-D clippy::let-else-on-result-ok` implied by `-D warnings`

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:31:10
|
LL | let (Ok(_), true) = (a(), true) else {
| ^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:34:10
|
LL | let [Ok(_), Ok(_)] = [a(), Err(())] else {
| ^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:34:17
|
LL | let [Ok(_), Ok(_)] = [a(), Err(())] else {
| ^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:37:11
|
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:37:16
|
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
| ^^^^^^^^^^^^^^^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:37:21
|
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
| ^^^^^^^^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:37:26
|
LL | let A(Ok(A(Ok(A(Ok(A(Ok(_)))))))) = a_constructor() else {
| ^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: usage of `let...else` on `Err`
--> $DIR/let_else_on_result_ok.rs:40:19
|
LL | let AnEnum::A(Ok(A(Err(_)))) = an_enum_constructor() else {
| ^^^^^^^^^^^^^
|
= note: consider handling the `Err` variant gracefully or propagating it to the caller

error: aborting due to 9 previous errors

0 comments on commit 5800fb5

Please sign in to comment.