forked from MystenLabs/sui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Linter] Unnecessary Conditional (MystenLabs#16856)
# Description - Added a lint to check for unnecessary conditionals - Triggered when - Each branch is a single value and the values are equal (consider removing the conditional) - Each branch is a single bool, and the bools are not equal (use the condition directly) ## Testing - New tests ## Release notes - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [X] CLI: Move will now lint against unnecessary conditionals, `if-else` expressions. - [ ] Rust SDK: --------- Co-authored-by: jamedzung <[email protected]> Co-authored-by: Todd Nowacki <[email protected]>
- Loading branch information
1 parent
cd6dfa4
commit 28a0c87
Showing
8 changed files
with
250 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
external-crates/move/crates/move-compiler/src/linters/unnecessary_conditional.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright (c) The Move Contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! Detects and suggests simplification for `if (c) e1 else e2` can be removed | ||
use move_proc_macros::growing_stack; | ||
|
||
use crate::expansion::ast::Value; | ||
use crate::linters::StyleCodes; | ||
use crate::{ | ||
diag, | ||
diagnostics::WarningFilters, | ||
expansion::ast::Value_, | ||
shared::CompilationEnv, | ||
typing::{ | ||
ast::{self as T, SequenceItem_, UnannotatedExp_}, | ||
visitor::{TypingVisitorConstructor, TypingVisitorContext}, | ||
}, | ||
}; | ||
|
||
pub struct UnnecessaryConditional; | ||
|
||
pub struct Context<'a> { | ||
env: &'a mut CompilationEnv, | ||
} | ||
|
||
impl TypingVisitorConstructor for UnnecessaryConditional { | ||
type Context<'a> = Context<'a>; | ||
|
||
fn context<'a>(env: &'a mut CompilationEnv, _program: &T::Program) -> Self::Context<'a> { | ||
Context { env } | ||
} | ||
} | ||
|
||
impl TypingVisitorContext for Context<'_> { | ||
fn add_warning_filter_scope(&mut self, filter: WarningFilters) { | ||
self.env.add_warning_filter_scope(filter) | ||
} | ||
fn pop_warning_filter_scope(&mut self) { | ||
self.env.pop_warning_filter_scope() | ||
} | ||
|
||
fn visit_exp_custom(&mut self, exp: &T::Exp) -> bool { | ||
let UnannotatedExp_::IfElse(_, etrue, efalse) = &exp.exp.value else { | ||
return false; | ||
}; | ||
let Some(vtrue) = extract_value(etrue) else { | ||
return false; | ||
}; | ||
let Some(vfalse) = extract_value(efalse) else { | ||
return false; | ||
}; | ||
|
||
match (&vtrue.value, &vfalse.value) { | ||
(Value_::Bool(v1 @ true), Value_::Bool(false)) | ||
| (Value_::Bool(v1 @ false), Value_::Bool(true)) => { | ||
let negation = if *v1 { "" } else { "!" }; | ||
let msg = format!( | ||
"Detected an unnecessary conditional expression 'if (cond)'. Consider using \ | ||
the condition directly, i.e. '{negation}cond'", | ||
); | ||
self.env.add_diag(diag!( | ||
StyleCodes::UnnecessaryConditional.diag_info(), | ||
(exp.exp.loc, msg) | ||
)); | ||
} | ||
(v1, v2) if v1 == v2 => { | ||
let msg = | ||
"Detected a redundant conditional expression 'if (..) v else v', where each \ | ||
branch results in the same value 'v'. Consider using the value directly"; | ||
self.env.add_diag(diag!( | ||
StyleCodes::UnnecessaryConditional.diag_info(), | ||
(exp.exp.loc, msg), | ||
(vtrue.loc, "This value"), | ||
(vfalse.loc, "is the same as this value"), | ||
)); | ||
} | ||
_ => (), | ||
} | ||
|
||
// if let (Some(if_bool), Some(else_bool)) = ( | ||
// extract_bool_literal_from_block(if_block), | ||
// extract_bool_literal_from_block(else_block), | ||
// ) { | ||
// if if_bool != else_bool { | ||
// let msg = format!( | ||
// "Detected a redundant conditional expression `if (...) {} else {}`. Consider using the condition directly.", | ||
// if_bool, else_bool | ||
// ); | ||
// let diag = diag!( | ||
// StyleCodes::UnnecessaryConditional.diag_info(), | ||
// (exp.exp.loc, msg) | ||
// ); | ||
|
||
// self.env.add_diag(diag); | ||
// } | ||
// } | ||
// } | ||
false | ||
} | ||
} | ||
|
||
#[growing_stack] | ||
fn extract_value(block: &T::Exp) -> Option<&Value> { | ||
match &block.exp.value { | ||
UnannotatedExp_::Block((_, seq)) if seq.len() == 1 => extract_value_seq_item(&seq[0]), | ||
UnannotatedExp_::Value(v) => Some(v), | ||
UnannotatedExp_::Annotate(e, _) => extract_value(e), | ||
_ => None, | ||
} | ||
} | ||
|
||
#[growing_stack] | ||
fn extract_value_seq_item(sp!(_, item_): &T::SequenceItem) -> Option<&Value> { | ||
match &item_ { | ||
SequenceItem_::Declare(_) | SequenceItem_::Bind(_, _, _) => None, | ||
SequenceItem_::Seq(e) => extract_value(e), | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
...crates/move/crates/move-compiler/tests/linter/false_negative_unnecessary_conditional.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
module a::m { | ||
// These very simply could be rewritten but we are overly conservative when it comes to blocks | ||
public fun t0(condition: bool) { | ||
if (condition) { (); true } else false; | ||
if (condition) b"" else { (); (); vector[] }; | ||
} | ||
|
||
// we don't do this check after constant folding | ||
public fun t1(condition: bool) { | ||
if (condition) 1 + 1 else 2; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
external-crates/move/crates/move-compiler/tests/linter/suppress_unnecessary_conditional.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module a::m { | ||
|
||
#[allow(lint(unnecessary_conditional))] | ||
public fun t0(condition: bool) { | ||
if (condition) true else false; | ||
} | ||
|
||
#[allow(lint(unnecessary_conditional))] | ||
public fun t1(condition: bool) { | ||
if (condition) vector<u8>[] else vector[]; | ||
} | ||
|
||
#[allow(lint(unnecessary_conditional))] | ||
public fun t2(condition: bool) { | ||
if (condition) @0 else @0; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...-crates/move/crates/move-compiler/tests/linter/true_negative_unnecessary_conditional.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// true negative cases for redundant conditional | ||
module a::m { | ||
public fun t0(condition: bool) { | ||
if (condition) 1 else { 0 }; | ||
if (condition) vector[] else vector[1]; | ||
} | ||
|
||
public fun t1(x: u64, y: u64) { | ||
let _ = if (x > y) { x } else y; | ||
} | ||
|
||
// has side effects, too complex to analyze | ||
public fun t2(condition: &mut bool) { | ||
let _ = if (*condition) { *condition = false; true } else { *condition = true; false }; | ||
} | ||
|
||
} |
52 changes: 52 additions & 0 deletions
52
...al-crates/move/crates/move-compiler/tests/linter/true_positive_unnecessary_conitional.exp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
warning[Lint W01007]: 'if' expression can be removed | ||
┌─ tests/linter/true_positive_unnecessary_conitional.move:4:9 | ||
│ | ||
4 │ if (!condition) true else false; | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Detected an unnecessary conditional expression 'if (cond)'. Consider using the condition directly, i.e. 'cond' | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(unnecessary_conditional))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01007]: 'if' expression can be removed | ||
┌─ tests/linter/true_positive_unnecessary_conitional.move:5:9 | ||
│ | ||
5 │ if (condition) { { false } } else { (true: bool) }; | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Detected an unnecessary conditional expression 'if (cond)'. Consider using the condition directly, i.e. '!cond' | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(unnecessary_conditional))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01007]: 'if' expression can be removed | ||
┌─ tests/linter/true_positive_unnecessary_conitional.move:9:9 | ||
│ | ||
9 │ if (true) true else true; | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
│ │ │ │ | ||
│ │ │ is the same as this value | ||
│ │ This value | ||
│ Detected a redundant conditional expression 'if (..) v else v', where each branch results in the same value 'v'. Consider using the value directly | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(unnecessary_conditional))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01007]: 'if' expression can be removed | ||
┌─ tests/linter/true_positive_unnecessary_conitional.move:10:9 | ||
│ | ||
10 │ if (foo()) 0 else 0; | ||
│ ^^^^^^^^^^^^^^^^^^^ | ||
│ │ │ │ | ||
│ │ │ is the same as this value | ||
│ │ This value | ||
│ Detected a redundant conditional expression 'if (..) v else v', where each branch results in the same value 'v'. Consider using the value directly | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(unnecessary_conditional))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01007]: 'if' expression can be removed | ||
┌─ tests/linter/true_positive_unnecessary_conitional.move:11:9 | ||
│ | ||
11 │ if (!foo()) b"" else x""; | ||
│ ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
│ │ │ │ | ||
│ │ │ is the same as this value | ||
│ │ This value | ||
│ Detected a redundant conditional expression 'if (..) v else v', where each branch results in the same value 'v'. Consider using the value directly | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(unnecessary_conditional))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
15 changes: 15 additions & 0 deletions
15
...l-crates/move/crates/move-compiler/tests/linter/true_positive_unnecessary_conitional.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// tests cases where a lint is reported for a redundant conditional expression | ||
module a::m { | ||
public fun t0(condition: bool) { | ||
if (!condition) true else false; | ||
if (condition) { { false } } else { (true: bool) }; | ||
} | ||
|
||
public fun t1() { | ||
if (true) true else true; | ||
if (foo()) 0 else 0; | ||
if (!foo()) b"" else x""; | ||
} | ||
|
||
fun foo(): bool { true } | ||
} |