Skip to content
Merged
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
21 changes: 20 additions & 1 deletion clippy_lints/src/question_mark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use clippy_config::msrvs::Msrv;
use clippy_config::types::MatchLintBehaviour;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{
eq_expr_value, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, is_res_lang_ctor,
pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
Expand Down Expand Up @@ -109,12 +109,31 @@ fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir
}

fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
/// Make sure the init expr implements try trait so a valid suggestion could be given.
///
/// Because the init expr could have the type of `&Option<T>` which does not implements `Try`.
///
/// NB: This conveniently prevents the cause of
/// issue [#12412](https://github.com/rust-lang/rust-clippy/issues/12412),
/// since accessing an `Option` field from a borrowed struct requires borrow, such as
/// `&some_struct.opt`, which is type of `&Option`. And we can't suggest `&some_struct.opt?`
/// or `(&some_struct.opt)?` since the first one has different semantics and the later does
/// not implements `Try`.
fn init_expr_can_use_question_mark(cx: &LateContext<'_>, init_expr: &Expr<'_>) -> bool {
let init_ty = cx.typeck_results().expr_ty_adjusted(init_expr);
cx.tcx
.lang_items()
.try_trait()
.map_or(false, |did| implements_trait(cx, init_ty, did, &[]))
}
Comment on lines +122 to +128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this pattern come up often? I think I have seen something like this before. Perhaps we could have this in the utils mod.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cx.tcx.lang_items.something().map_or(false, |id| some_util(...)) comes up fairly often, some utils have different variants that take lang items or diagnostic names, maybe we could do something like

trait Definition {
    fn def_id(&self) -> Option<DefId>;
}

And impl it for DefId, LangItem, Symbol (for diagnostic names)

Then you could do implements_trait(..., LangItem::Try) or implements_trait(.., sym::Default). Could use it to get unify a good few utils, but things taking a trait is going to be slightly more difficult to understand


if let StmtKind::Let(Local {
pat,
init: Some(init_expr),
els: Some(els),
..
}) = stmt.kind
&& init_expr_can_use_question_mark(cx, init_expr)
&& let Some(ret) = find_let_else_ret_expression(els)
&& let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, ret)
&& !span_contains_comment(cx.tcx.sess.source_map(), els.span)
Expand Down
34 changes: 34 additions & 0 deletions tests/ui/question_mark.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,37 @@ fn issue12337() -> Option<i32> {
};
Some(42)
}

fn issue11983(option: &Option<String>) -> Option<()> {
// Don't lint, `&Option` dose not impl `Try`.
let Some(v) = option else { return None };

let opt = Some(String::new());
// Don't lint, `branch` method in `Try` takes ownership of `opt`,
// and `(&opt)?` also doesn't work since it's `&Option`.
let Some(v) = &opt else { return None };
let mov = opt;

Some(())
}

struct Foo {
owned: Option<String>,
}
struct Bar {
foo: Foo,
}
#[allow(clippy::disallowed_names)]
fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
// Don't lint, `owned` is behind a shared reference.
let Some(v) = &foo.owned else {
return None;
};
// Don't lint, `owned` is behind a shared reference.
let Some(v) = &bar.foo.owned else {
return None;
};
// lint
let v = bar.foo.owned.clone()?;
Some(())
}
36 changes: 36 additions & 0 deletions tests/ui/question_mark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,39 @@ fn issue12337() -> Option<i32> {
};
Some(42)
}

fn issue11983(option: &Option<String>) -> Option<()> {
// Don't lint, `&Option` dose not impl `Try`.
let Some(v) = option else { return None };

let opt = Some(String::new());
// Don't lint, `branch` method in `Try` takes ownership of `opt`,
// and `(&opt)?` also doesn't work since it's `&Option`.
let Some(v) = &opt else { return None };
let mov = opt;

Some(())
}

struct Foo {
owned: Option<String>,
}
struct Bar {
foo: Foo,
}
#[allow(clippy::disallowed_names)]
fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
// Don't lint, `owned` is behind a shared reference.
let Some(v) = &foo.owned else {
return None;
};
// Don't lint, `owned` is behind a shared reference.
let Some(v) = &bar.foo.owned else {
return None;
};
// lint
let Some(v) = bar.foo.owned.clone() else {
return None;
};
Some(())
}
10 changes: 9 additions & 1 deletion tests/ui/question_mark.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,13 @@ LL | | // https://github.com/rust-lang/rust-clippy/pull/11001#is
LL | | }
| |_____________^ help: replace it with: `a?;`

error: aborting due to 16 previous errors
error: this `let...else` may be rewritten with the `?` operator
--> tests/ui/question_mark.rs:357:5
|
LL | / let Some(v) = bar.foo.owned.clone() else {
LL | | return None;
LL | | };
| |______^ help: replace it with: `let v = bar.foo.owned.clone()?;`

error: aborting due to 17 previous errors