Skip to content

Commit 57dd25e

Browse files
committedDec 26, 2023
FP: needless_return_with_question_mark with implicit Error Conversion
Return with a question mark was triggered in situations where the `?` desuraging was performing error conversion via `Into`/`From`. The desugared `?` produces a match over an expression with type `std::ops::ControlFlow<B,C>` with `B:Result<Infallible, E:Error>` and `C:Result<_, E':Error>`, and the arms perform the conversion. The patch adds another check in the lint that checks that `E == E'`. If `E == E'`, then the `?` is indeed unnecessary. changelog: False Positive: `needless_return_with_question_mark` when implicit Error Conversion occurs.
1 parent 9dd2252 commit 57dd25e

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed
 

‎clippy_lints/src/returns.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,37 @@ fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool {
176176
})
177177
}
178178

179+
///
180+
/// The expression of the desugared `try` operator is a match over an expression with type:
181+
/// `ControlFlow<A:Result<Infallible, E>, B:Result<_, E'>>`, with final type `B`.
182+
/// If E and E' are the same type, then there is no error conversion happening.
183+
/// Error conversion happens when E can be transformed into E' via a `From` or `Into` conversion.
184+
fn desugar_expr_performs_error_conversion(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
185+
let ty = cx.typeck_results().expr_ty(expr);
186+
187+
if let ty::Adt(_, generics) = ty.kind()
188+
&& let Some(brk) = generics.first()
189+
&& let Some(cont) = generics.get(1)
190+
&& let Some(brk_type) = brk.as_type()
191+
&& let Some(cont_type) = cont.as_type()
192+
&& let ty::Adt(_, brk_generics) = brk_type.kind()
193+
&& let ty::Adt(_, cont_generics) = cont_type.kind()
194+
&& let Some(brk_err) = brk_generics.get(1)
195+
&& let Some(cont_err) = cont_generics.get(1)
196+
&& let Some(brk_err_type) = brk_err.as_type()
197+
&& let Some(cont_err_type) = cont_err.as_type()
198+
{
199+
return brk_err_type != cont_err_type;
200+
}
201+
false
202+
}
203+
179204
impl<'tcx> LateLintPass<'tcx> for Return {
180205
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
181206
if !in_external_macro(cx.sess(), stmt.span)
182207
&& let StmtKind::Semi(expr) = stmt.kind
183208
&& let ExprKind::Ret(Some(ret)) = expr.kind
184-
&& let ExprKind::Match(.., MatchSource::TryDesugar(_)) = ret.kind
209+
&& let ExprKind::Match(match_expr, _, MatchSource::TryDesugar(..)) = ret.kind
185210
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
186211
&& let OwnerNode::Item(item) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id))
187212
&& let ItemKind::Fn(_, _, body) = item.kind
@@ -192,6 +217,7 @@ impl<'tcx> LateLintPass<'tcx> for Return {
192217
&& final_stmt.hir_id != stmt.hir_id
193218
&& !is_from_proc_macro(cx, expr)
194219
&& !stmt_needs_never_type(cx, stmt.hir_id)
220+
&& !desugar_expr_performs_error_conversion(cx, match_expr)
195221
{
196222
span_lint_and_sugg(
197223
cx,

‎tests/ui/needless_return_with_question_mark.fixed

+27
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,30 @@ fn issue11616() -> Result<(), ()> {
7777
};
7878
Ok(())
7979
}
80+
81+
/// This is a false positive that occurs because of the way `?` is handled.
82+
/// The `?` operator is also doing a conversion from `Result<T, E>` to `Result<T, E'>`.
83+
/// In this case the conversion is needed, and thus the `?` operator is also needed.
84+
fn issue11982() {
85+
mod bar {
86+
pub struct Error;
87+
pub fn foo(_: bool) -> Result<(), Error> {
88+
Ok(())
89+
}
90+
}
91+
92+
pub struct Error;
93+
94+
impl From<bar::Error> for Error {
95+
fn from(_: bar::Error) -> Self {
96+
Error
97+
}
98+
}
99+
100+
fn foo(ok: bool) -> Result<(), Error> {
101+
if !ok {
102+
return bar::foo(ok).map(|_| Ok::<(), Error>(()))?;
103+
};
104+
Ok(())
105+
}
106+
}

‎tests/ui/needless_return_with_question_mark.rs

+27
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,30 @@ fn issue11616() -> Result<(), ()> {
7777
};
7878
Ok(())
7979
}
80+
81+
/// This is a false positive that occurs because of the way `?` is handled.
82+
/// The `?` operator is also doing a conversion from `Result<T, E>` to `Result<T, E'>`.
83+
/// In this case the conversion is needed, and thus the `?` operator is also needed.
84+
fn issue11982() {
85+
mod bar {
86+
pub struct Error;
87+
pub fn foo(_: bool) -> Result<(), Error> {
88+
Ok(())
89+
}
90+
}
91+
92+
pub struct Error;
93+
94+
impl From<bar::Error> for Error {
95+
fn from(_: bar::Error) -> Self {
96+
Error
97+
}
98+
}
99+
100+
fn foo(ok: bool) -> Result<(), Error> {
101+
if !ok {
102+
return bar::foo(ok).map(|_| Ok::<(), Error>(()))?;
103+
};
104+
Ok(())
105+
}
106+
}

0 commit comments

Comments
 (0)