Skip to content

Add a function to check whether binary oprands are nontrivial #11907

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

Merged
merged 5 commits into from
Dec 8, 2023

Conversation

cocodery
Copy link
Contributor

@cocodery cocodery commented Dec 2, 2023

fixes #issue11885

It's hard to check whether operator is overrided through context of lint.
So, assume non-trivial structure like tuple, array or sturt, using a overrided binary operator in this lint, which might cause a side effict.
This is not detected before.
Althrough this might weaken the ability of this lint, it may more useful than before. Maybe this lint will cause an error, but now, it not. And assuming side effect of non-trivial structure with operator is not a bad thing, right?

changelog: Fix: [no_effect] check if binary operands are nontrivial

It's hard to check whether oprator is overrided through context of lint
So assume nontrivial has overrided binary operator
@rustbot
Copy link
Collaborator

rustbot commented Dec 2, 2023

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @xFrednet (or someone else) soon.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Dec 2, 2023
@cocodery
Copy link
Contributor Author

cocodery commented Dec 2, 2023

Ahhh, I forget to add the case of the issue to test...
Is it needed to add?

@xFrednet
Copy link
Member

xFrednet commented Dec 2, 2023

Yes, every bug and code should be tested. You can just push a new commit with the tests 👍

},
_ => false,
};
false
Copy link
Member

Choose a reason for hiding this comment

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

Does this actually work? It looks like this always returns false in any case. The match ends in a semicolon so its value looks to be effectively ignored. The false here should probably go?

Copy link
Contributor Author

@cocodery cocodery Dec 3, 2023

Choose a reason for hiding this comment

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

Thanks! I have fixed this.

match res {
Res::Def(defkind, _) => match defkind {
// user-defined
DefKind::Struct | DefKind::Ctor(_, _) => true,
Copy link
Member

Choose a reason for hiding this comment

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

What about references to structs? Or function calls that return a struct? Or slices of structs? It might be easier to just get the type of the expression and check if it's a scalar value (char, bool, float, int).
Or does this introduce too many false negatives?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, you're right. Thanks!
When I fixing this issue, I have the same question. But I'm not familiar with these utils functions clearly.
I'll fix this in next commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi, @y21 :)
I use method is_method_call in rust/compiler/rustc_middle/src/ty/typeck_results.rs for ExprKind::Binary to check whether operator is overrided or not. It's quit simple.
As you say, if excluding scalar value of operands may introduce too many false negatives, in fact.
So I think maybe only check Struct Enum Union is better? Not many people override operator with other structure, and only few of them have side effects?
I don't know what you think about it. Wait your reply!

Copy link
Member

Choose a reason for hiding this comment

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

Ah I wasn't aware of that is_method_call method, that looks useful here :)
Sounds like a good idea to me

Copy link
Contributor Author

@cocodery cocodery Dec 4, 2023

Choose a reason for hiding this comment

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

Yeah, it helps me reduce a lot of work to check operator override !
I have pushed my code just now. You can review it.

Check whether operator is overrided with a `struct` operand.
The struct here refers to `struct`, `enum`, `union`.
Add and fix test for `no_effect` lint.
@cocodery cocodery requested a review from y21 December 4, 2023 10:17
Comment on lines 179 to 182
let ud_lhs = match ty_lhs {
ty::Adt(adt_def, _) => adt_def.is_struct() || adt_def.is_enum() || adt_def.is_union(),
_ => false,
};
Copy link
Member

Choose a reason for hiding this comment

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

Small nit but: an ADT is always either a struct, enum or union, so the condition in the arm always returns true ^^
This could just be let ud_lhs = matches!(ty_lhs, ty::Adt(..));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, I forgot this, I'll fix it.


// reference: rust/compiler/rustc_middle/src/ty/typeck_results.rs: `is_method_call`.
// use this function to check whether operator is overrided in `ExprKind::Binary`.
(ud_lhs || ud_rhs) && tyck_result.is_method_call(expr)
Copy link
Member

Choose a reason for hiding this comment

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

Isn't the is_method_call already implied by ud_lhs || ud_rhs? If either the LHS or RHS is an adt, then that must mean that it's a custom operator impl, right?

Actually thinking about it, can't we use just tyck_result.is_method_call(expr), without checking if lhs and rhs is an ADT? I think that does the same but is simpler.
is_method_call returns false for operators on scalar types like integers, so it would still lint correctly for things like int + int;.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Absoulutely! Fixed in next commit!

Simpfy code of `is_operator_overrided`, directly use `is_method_call` to
check
if operator is overrided, at least one oprand of binary-expr must be ADT-type
So no need to check type of lhs and rhs
@cocodery cocodery requested a review from y21 December 5, 2023 10:52
Copy link
Member

@y21 y21 left a comment

Choose a reason for hiding this comment

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

This logic of checking for a call to a custom operator impl looks good to me! I have one more suggestion (and the typo), hopefully @xFrednet doesn't mind 😅

fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if expr.span.from_expansion() {
return false;
fn is_operator_overrided(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
fn is_operator_overrided(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn is_operator_overriden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added check for unary operator and fix my typo, test for unary operator is added to ui/test/no_effect.* also.
It's nice of you to give a lot reliable suggestions.
Ready to be merged !

Fix typo and add test for unary-opeator
Copy link
Member

@xFrednet xFrednet left a comment

Choose a reason for hiding this comment

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

Three small nits, but the rest looks good to me.

Also big thanks to @y21 for the review ❤️

fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if expr.span.from_expansion() {
return false;
fn is_operator_overriden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

NIT:

Suggested change
fn is_operator_overriden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn is_operator_overridden(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {

Comment on lines 95 to 98
// assume nontrivial oprand of `Binary` Expr can skip `check_unnecessary_operation`
if is_operator_overriden(cx, expr) {
return true;
}
Copy link
Member

@xFrednet xFrednet Dec 8, 2023

Choose a reason for hiding this comment

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

NIT(1): I find it a bit weird that this line also influences unnecessary_operation but I have no idea how to rewrite it better. I think this adjustment will make it a bit clearer and then it should be fine:

Suggested change
// assume nontrivial oprand of `Binary` Expr can skip `check_unnecessary_operation`
if is_operator_overriden(cx, expr) {
return true;
}
if is_operator_overridden(cx, expr) {
// Return `true`, to prevent `check_unnecessary_operation` from
// linting on this statement as well.
return true;
}

NIT(2): Could you also add tests to the file for unnecessary_operation to ensure that it doesn't lint on this expression?

@xFrednet
Copy link
Member

xFrednet commented Dec 8, 2023

Looks good to me, thank you for the fix!

@y21 Thank you for the review as well!

@bors r=y21,xFrednet

@bors
Copy link
Contributor

bors commented Dec 8, 2023

📌 Commit 56d20c2 has been approved by y21,xFrednet

It is now in the queue for this repository.

@bors
Copy link
Contributor

bors commented Dec 8, 2023

⌛ Testing commit 56d20c2 with merge 1c8cbe7...

@cocodery
Copy link
Contributor Author

cocodery commented Dec 8, 2023

Thanks for you two too!

@bors
Copy link
Contributor

bors commented Dec 8, 2023

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: y21,xFrednet
Pushing 1c8cbe7 to master...

@bors bors merged commit 1c8cbe7 into rust-lang:master Dec 8, 2023
@cocodery cocodery deleted the issue11885 branch December 8, 2023 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties
Projects
None yet
Development

Successfully merging this pull request may close these issues.

unnecessary_operation false positive on std::ops impl with side effects
5 participants