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

[Linter] Combinable bool conditions #16479

Merged

Conversation

tx-tomcat
Copy link
Contributor

@tx-tomcat tx-tomcat commented Mar 1, 2024

Description

The rules detects and warns about comparison operations in Move code that can be simplified. It identifies comparisons over the same operands joined with a boolean operation, and suggests how to collapse them to a single operation.

Testing

  • New tests added

Release notes

  • Protocol:
  • Nodes (Validators and Full nodes):
  • Indexer:
  • JSON-RPC:
  • GraphQL:
  • CLI: Move will now lint against pairs of comparison operations that can be combined to a single comparison.
  • Rust SDK:

Copy link

vercel bot commented Mar 1, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
mysten-ui ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 7:23pm
sui-core ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 7:23pm
sui-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 7:23pm
4 Skipped Deployments
Name Status Preview Comments Updated (UTC)
explorer ⬜️ Ignored (Inspect) Visit Preview Dec 6, 2024 7:23pm
multisig-toolkit ⬜️ Ignored (Inspect) Visit Preview Dec 6, 2024 7:23pm
sui-kiosk ⬜️ Ignored (Inspect) Visit Preview Dec 6, 2024 7:23pm
sui-typescript-docs ⬜️ Ignored (Inspect) Visit Preview Dec 6, 2024 7:23pm


impl TypingVisitorContext for Context<'_> {
fn visit_exp_custom(&mut self, exp: &mut T::Exp) -> bool {
if let UnannotatedExp_::BinopExp(e1, _op, _, e2) = &exp.exp.value {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this rule really works as is. Few flaws

  • We don't consider the _op here (wouldn't work really for &&)
    Bigger flaw
  • We don't consider the left/right operands for op1 and op2. For example, you couldn't combine x < y || y == zintox <= y`.

The overall structure here is okay, but it will need a lot of work to come up with a good heuristic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored

Copy link
Contributor

@tnowacki tnowacki left a comment

Choose a reason for hiding this comment

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

Conceptually this lint has some flaws by not checking for the same e1 and e2 between comparison operations.

Otherwise we would erroneously report errors for x > y && y != z


impl TypingVisitorContext for Context<'_> {
fn visit_exp_custom(&mut self, exp: &mut T::Exp) -> bool {
if let UnannotatedExp_::BinopExp(e1, _op, _, e2) = &exp.exp.value {
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to check for && here, yeah?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or a different set of rules for ||

Copy link
Contributor Author

Choose a reason for hiding this comment

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

implemented

Comment on lines 50 to 95
if let UnannotatedExp_::BinopExp(e1, op, _, e2) = &exp.exp.value {
if let (
UnannotatedExp_::BinopExp(lhs1, op1, _, rhs1),
UnannotatedExp_::BinopExp(lhs2, op2, _, rhs2),
) = (&e1.exp.value, &e2.exp.value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's switch these to let-else to reduce nesting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

) = (&e1.exp.value, &e2.exp.value)
{
// Check both exp side are the same
if lhs1 == lhs2 && rhs1 == rhs2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

@tzakian @awelc @cgswords thoughts on this condition? It feels a bit... unsatisfactory. But I'm not sure there is much else we can do without some form of symbolic execution.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this guard will incorrectly catch this code:

let mut v = make_vector();
if (v.pop_back() == 1 && v.pop_back() == 1) { .. }

Copy link
Contributor

Choose a reason for hiding this comment

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

Great catch! Yeah this will not work for any function call, or any operation that has side effects, for example

{ let c = x; x = true; c } && { let c = x; x = true; c }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its difficult to determine the effect of functions call so that I ignored the module call case.

exp.exp.loc,
"This is always contradictory and can be simplified to false",
);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about where op.value is == or !=?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is that == AND != or == OR != ?

Comment on lines 61 to 73
if op.value == BinOp_::And {
add_replaceable_comparison_diag(
self.env,
exp.exp.loc,
"This is always contradictory and can be simplified to false",
);
} else {
add_replaceable_comparison_diag(
self.env,
exp.exp.loc,
"Consider simplifying to `<=`.",
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd combine these branches to something like

let msg = if op.value == BinOp_::And { ... } else { ... };
add_replaceable_comparison_diag(self.env, exp.exp.loc, msg)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

Comment on lines 120 to 137
(BinOp_::Neq, BinOp_::Lt) | (BinOp_::Lt, BinOp_::Neq) => {
if op.value == BinOp_::And {
add_replaceable_comparison_diag(
self.env,
exp.exp.loc,
"Consider simplifying to `<`.",
);
}
}
(BinOp_::Neq, BinOp_::Gt) | (BinOp_::Gt, BinOp_::Neq) => {
if op.value == BinOp_::And {
add_replaceable_comparison_diag(
self.env,
exp.exp.loc,
"Consider simplifying to `>`.",
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These cases feel a bit different. It is not really "simplifying" rather the != is already covered by the GT and LT

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

Comment on lines 6 to 17
use crate::{command_line::compiler::Visitor, diagnostics::codes::WarningFilter};

use crate::{
command_line::compiler::Visitor, diagnostics::codes::WarningFilter,
linters::combinable_bool_conditions::CombinableBool, typing::visitor::TypingVisitor,
};
pub mod combinable_bool_conditions;
Copy link
Contributor

Choose a reason for hiding this comment

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

This will have be redone on main

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

Comment on lines 6 to 16
if (x == y || z < y) {};
if (x < y || x == y) {}; // should be x <= y
if (x == y || x > y) {}; // should be x >= y
if (x > y || x == y) {}; // should be x >= y
if (x != y || x < y) {}; // same as x < y
if (x < y || x != y) {}; // same as x < y
if (x != y || x > y) {}; // same as x > y
if (x > y || x != y) {}; // same as x > y
if (x > y || y != x) {}; // same as x > y
if (x > y && y == x) {}; // same as x >= y
if (m == n || m < n) {}; // should be m <= n
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably have some tests that don't involve if

env: &'a mut CompilationEnv,
}

impl TypingVisitorConstructor for CombinableBool {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should move this to the CFGIR to take advantage of constant folding and inlining

Copy link
Contributor

Choose a reason for hiding this comment

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

We will need to setup a non-absint visitor for you

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is that mean you setup visitor for us or we do it by ourself?

@@ -0,0 +1,25 @@
module 0x42::M {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please follow the pattern for tests we arrived at for the constant naming

  • Correct linter dir
  • Separate positive/negative tests
  • a case to the allow(lint(_)) case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

@@ -17,16 +20,33 @@ pub enum LintLevel {

pub const ALLOW_ATTR_CATEGORY: &str = "lint";
pub const LINT_WARNING_PREFIX: &str = "Lint ";
pub const COMBINABLE_BOOL_FILTER_NAME: &str = "combinable_bool_conditions";
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub const COMBINABLE_BOOL_FILTER_NAME: &str = "combinable_bool_conditions";
pub const COMBINABLE_COMPARISON_FILTER_NAME: &str = "combinable_comparison";

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

Copy link

vercel bot commented May 20, 2024

@dzungdinh94 is attempting to deploy a commit to the Mysten Labs Team on Vercel.

A member of the Team first needs to authorize it.

@tnowacki tnowacki temporarily deployed to sui-typescript-aws-kms-test-env December 6, 2024 19:22 — with GitHub Actions Inactive
@tnowacki tnowacki merged commit 5c06cf6 into MystenLabs:main Dec 6, 2024
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants