Skip to content

Commit e0e7ee1

Browse files
committedMar 30, 2024·
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`]
2 parents 88d842e + 91f3fad commit e0e7ee1

File tree

4 files changed

+99
-2
lines changed

4 files changed

+99
-2
lines changed
 

‎clippy_lints/src/question_mark.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clippy_config::msrvs::Msrv;
44
use clippy_config::types::MatchLintBehaviour;
55
use clippy_utils::diagnostics::span_lint_and_sugg;
66
use clippy_utils::source::snippet_with_applicability;
7-
use clippy_utils::ty::is_type_diagnostic_item;
7+
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
88
use clippy_utils::{
99
eq_expr_value, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item, is_res_lang_ctor,
1010
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
109109
}
110110

111111
fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
112+
/// Make sure the init expr implements try trait so a valid suggestion could be given.
113+
///
114+
/// Because the init expr could have the type of `&Option<T>` which does not implements `Try`.
115+
///
116+
/// NB: This conveniently prevents the cause of
117+
/// issue [#12412](https://github.com/rust-lang/rust-clippy/issues/12412),
118+
/// since accessing an `Option` field from a borrowed struct requires borrow, such as
119+
/// `&some_struct.opt`, which is type of `&Option`. And we can't suggest `&some_struct.opt?`
120+
/// or `(&some_struct.opt)?` since the first one has different semantics and the later does
121+
/// not implements `Try`.
122+
fn init_expr_can_use_question_mark(cx: &LateContext<'_>, init_expr: &Expr<'_>) -> bool {
123+
let init_ty = cx.typeck_results().expr_ty_adjusted(init_expr);
124+
cx.tcx
125+
.lang_items()
126+
.try_trait()
127+
.map_or(false, |did| implements_trait(cx, init_ty, did, &[]))
128+
}
129+
112130
if let StmtKind::Let(Local {
113131
pat,
114132
init: Some(init_expr),
115133
els: Some(els),
116134
..
117135
}) = stmt.kind
136+
&& init_expr_can_use_question_mark(cx, init_expr)
118137
&& let Some(ret) = find_let_else_ret_expression(els)
119138
&& let Some(inner_pat) = pat_and_expr_can_be_question_mark(cx, pat, ret)
120139
&& !span_contains_comment(cx.tcx.sess.source_map(), els.span)

‎tests/ui/question_mark.fixed

+34
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,37 @@ fn issue12337() -> Option<i32> {
283283
};
284284
Some(42)
285285
}
286+
287+
fn issue11983(option: &Option<String>) -> Option<()> {
288+
// Don't lint, `&Option` dose not impl `Try`.
289+
let Some(v) = option else { return None };
290+
291+
let opt = Some(String::new());
292+
// Don't lint, `branch` method in `Try` takes ownership of `opt`,
293+
// and `(&opt)?` also doesn't work since it's `&Option`.
294+
let Some(v) = &opt else { return None };
295+
let mov = opt;
296+
297+
Some(())
298+
}
299+
300+
struct Foo {
301+
owned: Option<String>,
302+
}
303+
struct Bar {
304+
foo: Foo,
305+
}
306+
#[allow(clippy::disallowed_names)]
307+
fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
308+
// Don't lint, `owned` is behind a shared reference.
309+
let Some(v) = &foo.owned else {
310+
return None;
311+
};
312+
// Don't lint, `owned` is behind a shared reference.
313+
let Some(v) = &bar.foo.owned else {
314+
return None;
315+
};
316+
// lint
317+
let v = bar.foo.owned.clone()?;
318+
Some(())
319+
}

‎tests/ui/question_mark.rs

+36
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,39 @@ fn issue12337() -> Option<i32> {
323323
};
324324
Some(42)
325325
}
326+
327+
fn issue11983(option: &Option<String>) -> Option<()> {
328+
// Don't lint, `&Option` dose not impl `Try`.
329+
let Some(v) = option else { return None };
330+
331+
let opt = Some(String::new());
332+
// Don't lint, `branch` method in `Try` takes ownership of `opt`,
333+
// and `(&opt)?` also doesn't work since it's `&Option`.
334+
let Some(v) = &opt else { return None };
335+
let mov = opt;
336+
337+
Some(())
338+
}
339+
340+
struct Foo {
341+
owned: Option<String>,
342+
}
343+
struct Bar {
344+
foo: Foo,
345+
}
346+
#[allow(clippy::disallowed_names)]
347+
fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
348+
// Don't lint, `owned` is behind a shared reference.
349+
let Some(v) = &foo.owned else {
350+
return None;
351+
};
352+
// Don't lint, `owned` is behind a shared reference.
353+
let Some(v) = &bar.foo.owned else {
354+
return None;
355+
};
356+
// lint
357+
let Some(v) = bar.foo.owned.clone() else {
358+
return None;
359+
};
360+
Some(())
361+
}

‎tests/ui/question_mark.stderr

+9-1
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,13 @@ LL | | // https://github.com/rust-lang/rust-clippy/pull/11001#is
141141
LL | | }
142142
| |_____________^ help: replace it with: `a?;`
143143

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

0 commit comments

Comments
 (0)
Please sign in to comment.