Auto merge of #12563 - J-ZhengLi:issue11513, r=Alexendoo

make sure checked type implements `Try` trait when linting [`question_mark`]

(indirectly) fixes: #12412 and fixes: #11983

---

changelog: make sure checked type implements `Try` trait when linting [`question_mark`]
This commit is contained in:
bors 2024-03-30 13:21:20 +00:00
commit e0e7ee183f
4 changed files with 99 additions and 2 deletions

View File

@ -4,7 +4,7 @@
use clippy_config::types::MatchLintBehaviour; use clippy_config::types::MatchLintBehaviour;
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; 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::{ use clippy_utils::{
eq_expr_value, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, 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, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
@ -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<'_>) { 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, &[]))
}
if let StmtKind::Let(Local { if let StmtKind::Let(Local {
pat, pat,
init: Some(init_expr), init: Some(init_expr),
els: Some(els), els: Some(els),
.. ..
}) = stmt.kind }) = stmt.kind
&& init_expr_can_use_question_mark(cx, init_expr)
&& let Some(ret) = find_let_else_ret_expression(els) && let Some(ret) = find_let_else_ret_expression(els)
&& let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, ret) && let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, ret)
&& !span_contains_comment(cx.tcx.sess.source_map(), els.span) && !span_contains_comment(cx.tcx.sess.source_map(), els.span)

View File

@ -283,3 +283,37 @@ fn issue12337() -> Option<i32> {
}; };
Some(42) 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(())
}

View File

@ -323,3 +323,39 @@ fn issue12337() -> Option<i32> {
}; };
Some(42) 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(())
}

View File

@ -141,5 +141,13 @@ LL | | // https://github.com/rust-lang/rust-clippy/pull/11001#is
LL | | } LL | | }
| |_____________^ help: replace it with: `a?;` | |_____________^ 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