Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

return statements are sometimes required, even when they shouldn't be #159

Open
ahicks92 opened this issue Nov 14, 2023 · 5 comments
Open
Labels
A-auto_enum Area: #[auto_enum] C-bug Category: related to a bug.

Comments

@ahicks92
Copy link

My repro is unfortunately private code, so sorry...if it's not obvious what's going on I'll work up something else for you. But if you do something like this:

#[auto_enum(Iterator)]
fn foo() -> Impl Iterator<Item=whatever> {
    if a {
        return std::iter::empty();
    }
    // Some more of those

    // And then return without an explicit return statement...
    std::iter::from_fn(|| stuff)
}

Then it only picks up the variants that explicitly had a return statement, and ignores the final expression in the function.

@taiki-e taiki-e added C-enhancement Category: A new feature or an improvement for an existing one C-bug Category: related to a bug. A-auto_enum Area: #[auto_enum] and removed C-enhancement Category: A new feature or an improvement for an existing one labels Nov 15, 2023
@taiki-e
Copy link
Owner

taiki-e commented Nov 15, 2023

I believe this is because we do not currently handle last expressions that are not considered branches.

match expr {
Expr::Block(ExprBlock { block, .. }) | Expr::Unsafe(ExprUnsafe { block, .. }) => {
if let Some(Stmt::Expr(expr, None)) = block.stmts.last_mut() {
child_expr(cx, expr);
}
}
Expr::Match(expr) => visit_last_expr_match(cx, expr),
Expr::If(expr) => visit_last_expr_if(cx, expr),
Expr::Loop(expr) => visit_last_expr_loop(cx, expr),
// Search recursively
Expr::MethodCall(ExprMethodCall { receiver: expr, .. })
| Expr::Paren(ExprParen { expr, .. }) => child_expr(cx, expr),
_ => {}
}

@ahicks92
Copy link
Author

My guess is that you will have to special case the last expression in a function because afaik that's the only place this edge case appears.

It's also probably worth noting that returning () is something a function might want to do even if it doesn't make sense in most cases, so there might be a holistic solution.

I can look at a PR if you don't have the time. I ended up not using this at work for other reasons unrelated to the quality of the crate. Yay code simplification, heh.

@taiki-e
Copy link
Owner

taiki-e commented Nov 16, 2023

My guess is that you will have to special case the last expression in a function because afaik that's the only place this edge case appears.

Yeah, the code I linked to is a function (child_expr) that handles that case, but even if the last expression itself is not considered to have a branch, it would indeed need to handle the last expression as one of the branches if a return is found in another expression.

match item.block.stmts.last_mut() {
Some(Stmt::Expr(expr, None)) => child_expr(cx, expr),

I can look at a PR if you don't have the time.

I don't have the bandwidth to work on the fix this right now, so if you could take a look that would be great!

@ahicks92
Copy link
Author

I'll try to at least poke at it this weekend.

@ahicks92
Copy link
Author

I couldn't untangle the many nested visitors and such to figure out how to funnel the data around generically, which is problematic because the same bug applies to closures. That said, here is a diff that shows some broken test cases:

diff --git a/tests/auto_enum.rs b/tests/auto_enum.rs
index 3c1f583..514997b 100644
--- a/tests/auto_enum.rs
+++ b/tests/auto_enum.rs
@@ -340,6 +340,22 @@ fn stable() {
     }
     assert_eq!(try_operator3(10).unwrap().sum::<i32>(), 54);

+    #[auto_enum(Iterator)]
+    fn trailing_expr_as_return(x: usize) -> impl Iterator<Item = i32> {
+        if x == 0 {
+            return 1..8;
+        }
+
+        if x > 3 {
+            return 2..=10;
+        }
+
+        (0..2).map(|x| x + 1)
+    }
+    for (i, x) in ANS.iter().enumerate() {
+        assert_eq!(trailing_expr_as_return(i).sum::<i32>(), *x);
+    }
+
     #[auto_enum]
     fn closure() {
         #[auto_enum(Iterator)]
@@ -360,6 +376,25 @@ fn stable() {
             assert_eq!(f(i).sum::<i32>(), *x);
         }

+        // Same as above, but the final else is a trailing return expression.
+        #[auto_enum(Iterator)]
+        let f = |x| {
+            if x > 10 {
+                return (0..x as _).map(|x| x - 1);
+            }
+            if x == 0 {
+                return 1..8;
+            } else if x > 3 {
+                return 2..=10;
+            }
+
+            (0..2).map(|x| x + 1)
+        };

Hopefully that helps you or whoever else comes after figure this out quickly. Unfortunately without familiarity, this is taking much longer than I can justify spending on it at the moment. I will circle back if this ever becomes a blocker, but since I'm not currently using the crate this bug is low priority for me at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-auto_enum Area: #[auto_enum] C-bug Category: related to a bug.
Projects
None yet
Development

No branches or pull requests

2 participants