Skip to content

Commit e970fa5

Browse files
committed
Auto merge of rust-lang#12341 - y21:issue12337, r=dswij
Check for try blocks in `question_mark` more consistently Fixes rust-lang#12337 I split this PR up into two commits since this moves a method out of an `impl`, which makes for a pretty bad diff (the `&self` parameter is now unused, and there isn't a reason for that function to be part of the `impl` now). The first commit is the actual relevant change and the 2nd commit just moves stuff (github's "hide whitespace" makes the diff easier to look at) ------------ Now for the actual issue: `?` within `try {}` blocks desugars to a `break` to the block, rather than a `return`, so that changes behavior in those cases. The lint has multiple patterns to look for and in *some* of them it already does correctly check whether we're in a try block, but this isn't done for all of its patterns. We could add another `self.inside_try_block()` check to the function that looks for `let-else-return`, but I chose to actually just move those checks out and instead have them in `LintPass::check_{stmt,expr}`. This has the advantage that we can't (easily) accidentally forget to add that check in new patterns that might be added in the future. (There's also a bit of a subtle interaction between two lints, where `question_mark`'s LintPass calls into `manual_let_else`, so I added a check to make sure we don't avoid linting for something that doesn't have anything to do with `?`) changelog: [`question_mark`]: avoid linting on try blocks in more cases
2 parents f75d8c7 + 1430623 commit e970fa5

File tree

5 files changed

+170
-133
lines changed

5 files changed

+170
-133
lines changed

clippy_lints/src/question_mark.rs

+103-102
Original file line numberDiff line numberDiff line change
@@ -203,108 +203,106 @@ fn expr_return_none_or_err(
203203
}
204204
}
205205

206-
impl QuestionMark {
207-
fn inside_try_block(&self) -> bool {
208-
self.try_block_depth_stack.last() > Some(&0)
209-
}
210-
211-
/// Checks if the given expression on the given context matches the following structure:
212-
///
213-
/// ```ignore
214-
/// if option.is_none() {
215-
/// return None;
216-
/// }
217-
/// ```
218-
///
219-
/// ```ignore
220-
/// if result.is_err() {
221-
/// return result;
222-
/// }
223-
/// ```
224-
///
225-
/// If it matches, it will suggest to use the question mark operator instead
226-
fn check_is_none_or_err_and_early_return<'tcx>(&self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
227-
if !self.inside_try_block()
228-
&& let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr)
229-
&& !is_else_clause(cx.tcx, expr)
230-
&& let ExprKind::MethodCall(segment, caller, ..) = &cond.kind
231-
&& let caller_ty = cx.typeck_results().expr_ty(caller)
232-
&& let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then)
233-
&& (is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block))
234-
{
235-
let mut applicability = Applicability::MachineApplicable;
236-
let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
237-
let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx, cx.param_env)
238-
&& !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
239-
let sugg = if let Some(else_inner) = r#else {
240-
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
241-
format!("Some({receiver_str}?)")
242-
} else {
243-
return;
244-
}
206+
/// Checks if the given expression on the given context matches the following structure:
207+
///
208+
/// ```ignore
209+
/// if option.is_none() {
210+
/// return None;
211+
/// }
212+
/// ```
213+
///
214+
/// ```ignore
215+
/// if result.is_err() {
216+
/// return result;
217+
/// }
218+
/// ```
219+
///
220+
/// If it matches, it will suggest to use the question mark operator instead
221+
fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
222+
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr)
223+
&& !is_else_clause(cx.tcx, expr)
224+
&& let ExprKind::MethodCall(segment, caller, ..) = &cond.kind
225+
&& let caller_ty = cx.typeck_results().expr_ty(caller)
226+
&& let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then)
227+
&& (is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block))
228+
{
229+
let mut applicability = Applicability::MachineApplicable;
230+
let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
231+
let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx, cx.param_env)
232+
&& !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
233+
let sugg = if let Some(else_inner) = r#else {
234+
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
235+
format!("Some({receiver_str}?)")
245236
} else {
246-
format!("{receiver_str}{}?;", if by_ref { ".as_ref()" } else { "" })
247-
};
237+
return;
238+
}
239+
} else {
240+
format!("{receiver_str}{}?;", if by_ref { ".as_ref()" } else { "" })
241+
};
248242

249-
span_lint_and_sugg(
250-
cx,
251-
QUESTION_MARK,
252-
expr.span,
253-
"this block may be rewritten with the `?` operator",
254-
"replace it with",
255-
sugg,
256-
applicability,
257-
);
258-
}
243+
span_lint_and_sugg(
244+
cx,
245+
QUESTION_MARK,
246+
expr.span,
247+
"this block may be rewritten with the `?` operator",
248+
"replace it with",
249+
sugg,
250+
applicability,
251+
);
259252
}
253+
}
260254

261-
fn check_if_let_some_or_err_and_early_return<'tcx>(&self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
262-
if !self.inside_try_block()
263-
&& let Some(higher::IfLet {
264-
let_pat,
265-
let_expr,
266-
if_then,
267-
if_else,
268-
..
269-
}) = higher::IfLet::hir(cx, expr)
270-
&& !is_else_clause(cx.tcx, expr)
271-
&& let PatKind::TupleStruct(ref path1, [field], ddpos) = let_pat.kind
272-
&& ddpos.as_opt_usize().is_none()
273-
&& let PatKind::Binding(BindingAnnotation(by_ref, _), bind_id, ident, None) = field.kind
274-
&& let caller_ty = cx.typeck_results().expr_ty(let_expr)
275-
&& let if_block = IfBlockType::IfLet(
276-
cx.qpath_res(path1, let_pat.hir_id),
277-
caller_ty,
278-
ident.name,
279-
let_expr,
280-
if_then,
281-
if_else,
282-
)
283-
&& ((is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
284-
|| is_early_return(sym::Result, cx, &if_block))
285-
&& if_else
286-
.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e)))
287-
.filter(|e| *e)
288-
.is_none()
289-
{
290-
let mut applicability = Applicability::MachineApplicable;
291-
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
292-
let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_));
293-
let sugg = format!(
294-
"{receiver_str}{}?{}",
295-
if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
296-
if requires_semi { ";" } else { "" }
297-
);
298-
span_lint_and_sugg(
299-
cx,
300-
QUESTION_MARK,
301-
expr.span,
302-
"this block may be rewritten with the `?` operator",
303-
"replace it with",
304-
sugg,
305-
applicability,
306-
);
307-
}
255+
fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
256+
if let Some(higher::IfLet {
257+
let_pat,
258+
let_expr,
259+
if_then,
260+
if_else,
261+
..
262+
}) = higher::IfLet::hir(cx, expr)
263+
&& !is_else_clause(cx.tcx, expr)
264+
&& let PatKind::TupleStruct(ref path1, [field], ddpos) = let_pat.kind
265+
&& ddpos.as_opt_usize().is_none()
266+
&& let PatKind::Binding(BindingAnnotation(by_ref, _), bind_id, ident, None) = field.kind
267+
&& let caller_ty = cx.typeck_results().expr_ty(let_expr)
268+
&& let if_block = IfBlockType::IfLet(
269+
cx.qpath_res(path1, let_pat.hir_id),
270+
caller_ty,
271+
ident.name,
272+
let_expr,
273+
if_then,
274+
if_else,
275+
)
276+
&& ((is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
277+
|| is_early_return(sym::Result, cx, &if_block))
278+
&& if_else
279+
.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e)))
280+
.filter(|e| *e)
281+
.is_none()
282+
{
283+
let mut applicability = Applicability::MachineApplicable;
284+
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
285+
let requires_semi = matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(_));
286+
let sugg = format!(
287+
"{receiver_str}{}?{}",
288+
if by_ref == ByRef::Yes { ".as_ref()" } else { "" },
289+
if requires_semi { ";" } else { "" }
290+
);
291+
span_lint_and_sugg(
292+
cx,
293+
QUESTION_MARK,
294+
expr.span,
295+
"this block may be rewritten with the `?` operator",
296+
"replace it with",
297+
sugg,
298+
applicability,
299+
);
300+
}
301+
}
302+
303+
impl QuestionMark {
304+
fn inside_try_block(&self) -> bool {
305+
self.try_block_depth_stack.last() > Some(&0)
308306
}
309307
}
310308

@@ -324,15 +322,18 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark {
324322
return;
325323
}
326324

327-
if !in_constant(cx, stmt.hir_id) {
325+
if !self.inside_try_block() && !in_constant(cx, stmt.hir_id) {
328326
check_let_some_else_return_none(cx, stmt);
329327
}
330328
self.check_manual_let_else(cx, stmt);
331329
}
332330
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
333-
if !in_constant(cx, expr.hir_id) && is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id) {
334-
self.check_is_none_or_err_and_early_return(cx, expr);
335-
self.check_if_let_some_or_err_and_early_return(cx, expr);
331+
if !self.inside_try_block()
332+
&& !in_constant(cx, expr.hir_id)
333+
&& is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id)
334+
{
335+
check_is_none_or_err_and_early_return(cx, expr);
336+
check_if_let_some_or_err_and_early_return(cx, expr);
336337
}
337338
}
338339

tests/ui/manual_let_else.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![feature(try_blocks)]
12
#![allow(unused_braces, unused_variables, dead_code)]
23
#![allow(
34
clippy::collapsible_else_if,
@@ -446,3 +447,12 @@ struct U<T> {
446447
w: T,
447448
x: T,
448449
}
450+
451+
fn issue12337() {
452+
// We want to generally silence question_mark lints within try blocks, since `?` has different
453+
// behavior to `return`, and question_mark calls into manual_let_else logic, so make sure that
454+
// we still emit a lint for manual_let_else
455+
let _: Option<()> = try {
456+
let v = if let Some(v_some) = g() { v_some } else { return };
457+
};
458+
}

0 commit comments

Comments
 (0)