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

fix: match stmts conversion to expr and their interaction with return #11342

Closed
wants to merge 3 commits into from

Conversation

feniljain
Copy link
Contributor

@feniljain feniljain commented Aug 15, 2023

Should fix #10390

Eariler I opened a PR to fix an issue in needless_return lint here: #10593. This fixed issue's mentioned particular scenario, but didn't fix other scenarios like #10390 . Thanks to @kadiwa4 's tag I noticed that particular issue, this time I tried to go back to square one and think of different scenarios and made changes to hopefully cover all cases. So this PR may also cover other different issues which could have been lingering around to be found.

Explaining changes:

According to my understanding this lint's function is:

  • Try to convert match stmt to match expr if it is last stmt of function/block, and then remove returns from it

  • Try to remove returns in a match expr

  • For cases of match expr, we don't want to change our current analysis

  • For cases of match stmt, we need to check if match's return type is equal to function's return type and only do further analysis if they match exactly, for cases showed in code in above issue:

fn func(input: u8) {
    match input {
        3 => 4,
        _ => {
            // side effect ...
            return;
        },
    };
}

match expr return type is u8 which is different from function's return type and hence we do not take any action :)

Do let me know if you still think there's some problem in some change 😅


changelog: [needless_return]: Fix various scenarios of match stmt/expr transformation and them apperaing with returns

@rustbot
Copy link
Collaborator

rustbot commented Aug 15, 2023

r? @Alexendoo

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Aug 15, 2023
clippy_lints/src/returns.rs Show resolved Hide resolved
clippy_lints/src/returns.rs Show resolved Hide resolved
clippy_lints/src/returns.rs Show resolved Hide resolved
clippy_lints/src/returns.rs Show resolved Hide resolved
match parent_fn_return_type {
Some(FnRetTy::DefaultReturn(_)) if let ty::Tuple(tuple) = match_expr_ret_type.kind() =>
tuple.len() == 0,
Some(FnRetTy::Return(_ret_ty)) => matches!(match_expr_ret_type.kind(), _ret_ty),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches! is always true since the pattern is just a binding, you can't neatly compare a HIR type against a rustc_middle one

I think we can step around this though as the only case where I think it would matter is if the return type is explicitly (), we should be able to make this

Suggested change
Some(FnRetTy::Return(_ret_ty)) => matches!(match_expr_ret_type.kind(), _ret_ty),
Some(FnRetTy::Return(Ty { kind: TyKind::Tup([]), .. })) => match_expr_ret_type.is_unit(),

and a test

fn match_explicit_unit_return() -> () {
    match 0 {
        0 => 1,
        _ => {
            return;
        },
    };
}

If you can think of an example with a non unit type though please let me know

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I was wondering why it didn't work, when I checked the type back then both showed TyKind, didn't realize they were from different module 🤦🏻

So for the suggestion, I think that won't work out cause of this comment:

check if function return type is equal to match return type 
if yes then we want can convert this match stmt to match expr 
otherwise dont do it

We want to compare parent function's return type to match's type, and if they are the same then we can do the conversion of removing semicolon from end, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For non unit types that wouldn't type check since the implicit return of () would not apply, e.g. the following doesn't compile

fn f() -> u32 {
    match 1 {
        0 => 1, // ERROR: mismatched types
        _ => return 2,
    };
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I am sorry I wasn't able to follow what you meant, could you please explain it in some other way 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'll give it another shot, I had trouble trying to explain it so I don't blame you 😄

To annotate the example from #10390:

fn func(input: u8) {
    match input {
        // this arm's expr has type `i32`
        3 => 4,
        
        // this arm's expr has a pre-adjustment type of `!` (never) because of
        // the return, but adjusts to `i32` to match the other arm
        _ => {
            return;
        }, 
    };
    // the match expression has type i32, which is discarded by the semicolon statement

    // if the `3 => 4` arm was taken, the function returns () implicitly here
}

The FP occurs because of the combination of the discarded result and the implicit return of ()

When a function isn't returning unit it can no longer use that implicit return because it would be a type mismatch, so every arm must diverge (return/panic/etc)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this example is returning the match expression

Ah ig this was the point I was missing, here it is match expr 🤦🏻, yeah then I guess your stmt of just checking unit tuple makes sense, lemme make a fix and push it!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so interestingly, we have another lint called useless-unit:

+++ <stderr output>
+error: unneeded unit return type
+  --> $DIR/needless_return.rs:336:52
+   |
+LL | fn issue10390_different_arm_return_types(input: u8) -> () {
+   |                                                    ^^^^^^ help: remove the `-> ()`
+   |
+   = note: `-D clippy::unused-unit` implied by `-D warnings`

Applying this would make it same as default case, do we still keep this part of code around then? I am not sure what is the policy of project in cases where another lint applies and hence changes output of it 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we want lints to be correct when applied individually, since it doesn't add much code I'd say keep it

For the test file you can add an #![allow(clippy::unused_unit)]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think I found a case where we may need to check types, what about this:

fn issue8156(x: u8) -> u64 {
    match x {
        80 => {
            return 10;
        },
        _ => {
            return 100;
        },
    };
}

In this case, it is a match statement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would it be guarding against? That example compiles fine if you remove the return/semicolons

tests/ui/needless_return.fixed Show resolved Hide resolved
@Alexendoo Alexendoo added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Sep 11, 2023
@bors
Copy link
Collaborator

bors commented Nov 22, 2023

☔ The latest upstream changes (presumably #11627) made this pull request unmergeable. Please resolve the merge conflicts.

@xFrednet
Copy link
Member

Hey @feniljain, this is a ping from triage, since there hasn't been any activity in some time. Are you still planning to continue this implementation?

If you have any questions, you're always welcome to ask them in this PR or on Zulip.

@feniljain
Copy link
Contributor Author

Hey @xFrednet! Thanks for the reminder, I think I have lost all context at this point, and it would be better if someone else picks this up. Thanks for everyone's time! 🙇🏻

@xFrednet
Copy link
Member

xFrednet commented Apr 1, 2024

No problem at all.

Interested parties are welcome to pick this implementation up as well :)

@rustbot label +S-inactive-closed -S-waiting-on-author -S-waiting-on-review

@xFrednet xFrednet closed this Apr 1, 2024
@rustbot rustbot added S-inactive-closed Status: Closed due to inactivity and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Apr 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive-closed Status: Closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

needless_return false positive in match arm
5 participants