diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 899568a93..f0155ed60 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -1,16 +1,19 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt}; +use clippy_utils::{ + eq_expr_value, get_parent_node, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, + peel_blocks_with_stmt, +}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk}; -use rustc_hir::{BindingAnnotation, Expr, ExprKind, PatKind}; +use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; +use rustc_span::{sym, symbol::Symbol}; declare_clippy_lint! { /// ### What it does @@ -39,135 +42,190 @@ declare_clippy_lint! { declare_lint_pass!(QuestionMark => [QUESTION_MARK]); -impl QuestionMark { - /// Checks if the given expression on the given context matches the following structure: +enum IfBlockType<'hir> { + /// An `if x.is_xxx() { a } else { b } ` expression. /// - /// ```ignore - /// if option.is_none() { - /// return None; - /// } - /// ``` + /// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b) + IfIs( + &'hir Expr<'hir>, + Ty<'hir>, + Symbol, + &'hir Expr<'hir>, + Option<&'hir Expr<'hir>>, + ), + /// An `if let Xxx(a) = b { c } else { d }` expression. /// - /// ```ignore - /// if result.is_err() { - /// return result; - /// } - /// ``` - /// - /// If it matches, it will suggest to use the question mark operator instead - fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr); - if let ExprKind::MethodCall(segment, args, _) = &cond.kind; - if let Some(subject) = args.get(0); - if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) || - (Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err)); - then { - let mut applicability = Applicability::MachineApplicable; - let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability); - let mut replacement: Option = None; - if let Some(else_inner) = r#else { - if eq_expr_value(cx, subject, peel_blocks(else_inner)) { - replacement = Some(format!("Some({}?)", receiver_str)); - } - } else if Self::moves_by_default(cx, subject) - && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) - { - replacement = Some(format!("{}.as_ref()?;", receiver_str)); + /// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c), + /// if_else (d) + IfLet( + &'hir QPath<'hir>, + Ty<'hir>, + Symbol, + &'hir Expr<'hir>, + &'hir Expr<'hir>, + Option<&'hir Expr<'hir>>, + ), +} + +/// Checks if the given expression on the given context matches the following structure: +/// +/// ```ignore +/// if option.is_none() { +/// return None; +/// } +/// ``` +/// +/// ```ignore +/// if result.is_err() { +/// return result; +/// } +/// ``` +/// +/// If it matches, it will suggest to use the question mark operator instead +fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if_chain! { + if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr); + if !is_else_clause(cx.tcx, expr); + if let ExprKind::MethodCall(segment, args, _) = &cond.kind; + if let Some(caller) = args.get(0); + let caller_ty = cx.typeck_results().expr_ty(caller); + let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else); + if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block); + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability); + let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx.at(caller.span), cx.param_env) && + !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)); + let sugg = if let Some(else_inner) = r#else { + if eq_expr_value(cx, caller, peel_blocks(else_inner)) { + format!("Some({}?)", receiver_str) } else { - replacement = Some(format!("{}?;", receiver_str)); + return; } + } else { + format!("{}{}?;", receiver_str, if by_ref { ".as_ref()" } else { "" }) + }; - if let Some(replacement_str) = replacement { - span_lint_and_sugg( - cx, - QUESTION_MARK, - expr.span, - "this block may be rewritten with the `?` operator", - "replace it with", - replacement_str, - applicability, - ); - } - } + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this block may be rewritten with the `?` operator", + "replace it with", + sugg, + applicability, + ); } } +} - fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) - = higher::IfLet::hir(cx, expr); - if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind; - if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) || - (Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk)); - - if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind; +fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if_chain! { + if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr); + if !is_else_clause(cx.tcx, expr); + if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind; + if let PatKind::Binding(annot, bind_id, ident, _) = fields[0].kind; + let caller_ty = cx.typeck_results().expr_ty(let_expr); + let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_else); + if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id)) + || is_early_return(sym::Result, cx, &if_block); + if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none(); + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); - if path_to_local_id(peel_blocks(if_then), bind_id); - then { - let mut applicability = Applicability::MachineApplicable; - let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); - let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },); + let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_))); + let sugg = format!( + "{}{}?{}", + receiver_str, + if by_ref { ".as_ref()" } else { "" }, + if requires_semi { ";" } else { "" } + ); + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this block may be rewritten with the `?` operator", + "replace it with", + sugg, + applicability, + ); + } + } +} - span_lint_and_sugg( - cx, - QUESTION_MARK, - expr.span, - "this if-let-else may be rewritten with the `?` operator", - "replace it with", - replacement, - applicability, - ); +fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool { + match *if_block { + IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => { + // If the block could be identified as `if x.is_none()/is_err()`, + // we then only need to check the if_then return to see if it is none/err. + is_type_diagnostic_item(cx, caller_ty, smbl) + && expr_return_none_or_err(smbl, cx, if_then, caller, None) + && match smbl { + sym::Option => call_sym == sym!(is_none), + sym::Result => call_sym == sym!(is_err), + _ => false, + } + }, + IfBlockType::IfLet(qpath, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => { + is_type_diagnostic_item(cx, let_expr_ty, smbl) + && match smbl { + sym::Option => { + // We only need to check `if let Some(x) = option` not `if let None = option`, + // because the later one will be suggested as `if option.is_none()` thus causing conflict. + is_lang_ctor(cx, qpath, OptionSome) + && if_else.is_some() + && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None) + }, + sym::Result => { + (is_lang_ctor(cx, qpath, ResultOk) + && if_else.is_some() + && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym))) + || is_lang_ctor(cx, qpath, ResultErr) + && expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym)) + }, + _ => false, + } + }, + } +} + +fn expr_return_none_or_err( + smbl: Symbol, + cx: &LateContext<'_>, + expr: &Expr<'_>, + cond_expr: &Expr<'_>, + err_sym: Option, +) -> bool { + match peel_blocks_with_stmt(expr).kind { + ExprKind::Ret(Some(ret_expr)) => expr_return_none_or_err(smbl, cx, ret_expr, cond_expr, err_sym), + ExprKind::Path(ref qpath) => match smbl { + sym::Option => is_lang_ctor(cx, qpath, OptionNone), + sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr), + _ => false, + }, + ExprKind::Call(call_expr, args_expr) => { + if_chain! { + if smbl == sym::Result; + if let ExprKind::Path(QPath::Resolved(_, path)) = &call_expr.kind; + if let Some(segment) = path.segments.first(); + if let Some(err_sym) = err_sym; + if let Some(arg) = args_expr.first(); + if let ExprKind::Path(QPath::Resolved(_, arg_path)) = &arg.kind; + if let Some(PathSegment { ident, .. }) = arg_path.segments.first(); + then { + return segment.ident.name == sym::Err && err_sym == ident.name; + } } - } - } - - fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool { - Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr) - } - - fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool { - Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr) - } - - fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expression); - - !expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env) - } - - fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expression); - - is_type_diagnostic_item(cx, expr_ty, sym::Option) - } - - fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expression); - - is_type_diagnostic_item(cx, expr_ty, sym::Result) - } - - fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - match peel_blocks_with_stmt(expression).kind { - ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr), - ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone), - _ => false, - } - } - - fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool { - match peel_blocks_with_stmt(expr).kind { - ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr), - ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr), - _ => false, - } + false + }, + _ => false, } } impl<'tcx> LateLintPass<'tcx> for QuestionMark { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - Self::check_is_none_or_err_and_early_return(cx, expr); - Self::check_if_let_some_or_err_and_early_return(cx, expr); + check_is_none_or_err_and_early_return(cx, expr); + check_if_let_some_or_err_and_early_return(cx, expr); } } diff --git a/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed index b997e5316..0c9178fb8 100644 --- a/tests/ui/needless_match.fixed +++ b/tests/ui/needless_match.fixed @@ -99,6 +99,7 @@ fn if_let_result() { let _: Result = x; let _: Result = x; // Input type mismatch, don't trigger + #[allow(clippy::question_mark)] let _: Result = if let Err(e) = Ok(1) { Err(e) } else { x }; } diff --git a/tests/ui/needless_match.rs b/tests/ui/needless_match.rs index 90482775a..f66f01d7c 100644 --- a/tests/ui/needless_match.rs +++ b/tests/ui/needless_match.rs @@ -122,6 +122,7 @@ fn if_let_result() { let _: Result = if let Err(e) = x { Err(e) } else { x }; let _: Result = if let Ok(val) = x { Ok(val) } else { x }; // Input type mismatch, don't trigger + #[allow(clippy::question_mark)] let _: Result = if let Err(e) = Ok(1) { Err(e) } else { x }; } diff --git a/tests/ui/needless_match.stderr b/tests/ui/needless_match.stderr index 2d679631c..5bc79800a 100644 --- a/tests/ui/needless_match.stderr +++ b/tests/ui/needless_match.stderr @@ -84,7 +84,7 @@ LL | let _: Result = if let Ok(val) = x { Ok(val) } else { x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x` error: this if-let expression is unnecessary - --> $DIR/needless_match.rs:129:21 + --> $DIR/needless_match.rs:130:21 | LL | let _: Simple = if let Simple::A = x { | _____________________^ @@ -97,7 +97,7 @@ LL | | }; | |_____^ help: replace it with: `x` error: this match expression is unnecessary - --> $DIR/needless_match.rs:168:26 + --> $DIR/needless_match.rs:169:26 | LL | let _: Complex = match ce { | __________________________^ diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 13ce0f32d..c4c9c8214 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -1,5 +1,6 @@ // run-rustfix #![allow(unreachable_code)] +#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] fn some_func(a: Option) -> Option { @@ -154,26 +155,56 @@ fn f() -> NotOption { NotOption::First } -fn main() { - some_func(Some(42)); - some_func(None); - some_other_func(Some(42)); +fn do_something() {} - let copy_struct = CopyStruct { opt: Some(54) }; - copy_struct.func(); - - let move_struct = MoveStruct { - opt: Some(vec![42, 1337]), - }; - move_struct.ref_func(); - move_struct.clone().mov_func_reuse(); - move_struct.mov_func_no_use(); - - let so = SeemsOption::Some(45); - returns_something_similar_to_option(so); - - func(); - - let _ = result_func(Ok(42)); - let _ = f(); +fn err_immediate_return() -> Result { + func_returning_result()?; + Ok(1) } + +fn err_immediate_return_and_do_something() -> Result { + func_returning_result()?; + do_something(); + Ok(1) +} + +// No warning +fn no_immediate_return() -> Result { + if let Err(err) = func_returning_result() { + do_something(); + return Err(err); + } + Ok(1) +} + +// No warning +fn mixed_result_and_option() -> Option { + if let Err(err) = func_returning_result() { + return Some(err); + } + None +} + +// No warning +fn else_if_check() -> Result { + if true { + Ok(1) + } else if let Err(e) = func_returning_result() { + Err(e) + } else { + Err(-1) + } +} + +// No warning +#[allow(clippy::manual_map)] +#[rustfmt::skip] +fn option_map() -> Option { + if let Some(a) = Some(false) { + Some(!a) + } else { + None + } +} + +fn main() {} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 60590fd93..cdbc7b160 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -1,5 +1,6 @@ // run-rustfix #![allow(unreachable_code)] +#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] fn some_func(a: Option) -> Option { @@ -186,26 +187,60 @@ fn f() -> NotOption { NotOption::First } -fn main() { - some_func(Some(42)); - some_func(None); - some_other_func(Some(42)); +fn do_something() {} - let copy_struct = CopyStruct { opt: Some(54) }; - copy_struct.func(); - - let move_struct = MoveStruct { - opt: Some(vec![42, 1337]), - }; - move_struct.ref_func(); - move_struct.clone().mov_func_reuse(); - move_struct.mov_func_no_use(); - - let so = SeemsOption::Some(45); - returns_something_similar_to_option(so); - - func(); - - let _ = result_func(Ok(42)); - let _ = f(); +fn err_immediate_return() -> Result { + if let Err(err) = func_returning_result() { + return Err(err); + } + Ok(1) } + +fn err_immediate_return_and_do_something() -> Result { + if let Err(err) = func_returning_result() { + return Err(err); + } + do_something(); + Ok(1) +} + +// No warning +fn no_immediate_return() -> Result { + if let Err(err) = func_returning_result() { + do_something(); + return Err(err); + } + Ok(1) +} + +// No warning +fn mixed_result_and_option() -> Option { + if let Err(err) = func_returning_result() { + return Some(err); + } + None +} + +// No warning +fn else_if_check() -> Result { + if true { + Ok(1) + } else if let Err(e) = func_returning_result() { + Err(e) + } else { + Err(-1) + } +} + +// No warning +#[allow(clippy::manual_map)] +#[rustfmt::skip] +fn option_map() -> Option { + if let Some(a) = Some(false) { + Some(!a) + } else { + None + } +} + +fn main() {} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 8d782b71d..1b6cd524b 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -1,5 +1,5 @@ error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:6:5 + --> $DIR/question_mark.rs:7:5 | LL | / if a.is_none() { LL | | return None; @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::question-mark` implied by `-D warnings` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:51:9 + --> $DIR/question_mark.rs:52:9 | LL | / if (self.opt).is_none() { LL | | return None; @@ -17,7 +17,7 @@ LL | | } | |_________^ help: replace it with: `(self.opt)?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:55:9 + --> $DIR/question_mark.rs:56:9 | LL | / if self.opt.is_none() { LL | | return None @@ -25,7 +25,7 @@ LL | | } | |_________^ help: replace it with: `self.opt?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:59:17 + --> $DIR/question_mark.rs:60:17 | LL | let _ = if self.opt.is_none() { | _________________^ @@ -35,8 +35,8 @@ LL | | self.opt LL | | }; | |_________^ help: replace it with: `Some(self.opt?)` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:65:17 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:66:17 | LL | let _ = if let Some(x) = self.opt { | _________________^ @@ -47,7 +47,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:82:9 + --> $DIR/question_mark.rs:83:9 | LL | / if self.opt.is_none() { LL | | return None; @@ -55,7 +55,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:90:9 + --> $DIR/question_mark.rs:91:9 | LL | / if self.opt.is_none() { LL | | return None; @@ -63,15 +63,15 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:98:9 + --> $DIR/question_mark.rs:99:9 | LL | / if self.opt.is_none() { LL | | return None; LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:105:26 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:106:26 | LL | let v: &Vec<_> = if let Some(ref v) = self.opt { | __________________________^ @@ -81,8 +81,8 @@ LL | | return None; LL | | }; | |_________^ help: replace it with: `self.opt.as_ref()?` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:115:17 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:116:17 | LL | let v = if let Some(v) = self.opt { | _________________^ @@ -93,26 +93,42 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:130:5 + --> $DIR/question_mark.rs:131:5 | LL | / if f().is_none() { LL | | return None; LL | | } | |_____^ help: replace it with: `f()?;` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:142:13 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:143:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:144:5 + --> $DIR/question_mark.rs:145:5 | LL | / if x.is_err() { LL | | return x; LL | | } | |_____^ help: replace it with: `x?;` -error: aborting due to 13 previous errors +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:193:5 + | +LL | / if let Err(err) = func_returning_result() { +LL | | return Err(err); +LL | | } + | |_____^ help: replace it with: `func_returning_result()?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:200:5 + | +LL | / if let Err(err) = func_returning_result() { +LL | | return Err(err); +LL | | } + | |_____^ help: replace it with: `func_returning_result()?;` + +error: aborting due to 15 previous errors