-
Notifications
You must be signed in to change notification settings - Fork 11.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Description Detects empty `else` branches in conditional structures, suggesting their removal for cleaner code. Aims to flag potentially unnecessary or unimplemented placeholders within `if-else` statements. Encourages code clarity and maintainability by eliminating redundant branches. # Run the linter ``` cargo test move_check_testsuit ``` # Testing File testing: needless_else.move ## Release notes - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: --------- Co-authored-by: jamedzung <[email protected]> Co-authored-by: Todd Nowacki <[email protected]>
- Loading branch information
1 parent
3bf947d
commit e0b51bc
Showing
11 changed files
with
385 additions
and
12 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
128 changes: 128 additions & 0 deletions
128
external-crates/move/crates/move-compiler/src/linters/unnecessary_unit.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,128 @@ | ||
//! Detects an unnecessary unit expression in a block, sequence, if, or else. | ||
|
||
use crate::{ | ||
diag, | ||
diagnostics::WarningFilters, | ||
ice, | ||
linters::StyleCodes, | ||
shared::CompilationEnv, | ||
typing::{ | ||
ast::{self as T, SequenceItem_, UnannotatedExp_}, | ||
visitor::{TypingVisitorConstructor, TypingVisitorContext}, | ||
}, | ||
}; | ||
use move_ir_types::location::Loc; | ||
|
||
pub struct UnnecessaryUnit; | ||
|
||
pub struct Context<'a> { | ||
env: &'a mut CompilationEnv, | ||
} | ||
|
||
impl TypingVisitorConstructor for UnnecessaryUnit { | ||
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_seq_custom(&mut self, loc: Loc, (_, seq_): &T::Sequence) -> bool { | ||
let n = seq_.len(); | ||
match n { | ||
0 => { | ||
self.env | ||
.add_diag(ice!((loc, "Unexpected empty block without a value"))); | ||
} | ||
1 => { | ||
// TODO probably too noisy for now, we would need more information about | ||
// blocks were added by the programmer | ||
// self.env.add_diag(diag!( | ||
// StyleCodes::UnnecessaryBlock.diag_info(), | ||
// (e.exp.loc, "Unnecessary block expression '{}')" | ||
// (e.exp.loc, if_msg), | ||
// )); | ||
} | ||
n => { | ||
let last = n - 1; | ||
for (i, stmt) in seq_.iter().enumerate() { | ||
if i != last && is_unit_seq(self, stmt) { | ||
let msg = "Unnecessary unit in sequence '();'. Consider removing"; | ||
self.env.add_diag(diag!( | ||
StyleCodes::UnnecessaryUnit.diag_info(), | ||
(stmt.loc, msg), | ||
)); | ||
} | ||
} | ||
} | ||
} | ||
false | ||
} | ||
|
||
fn visit_exp_custom(&mut self, e: &T::Exp) -> bool { | ||
use UnannotatedExp_ as TE; | ||
let TE::IfElse(e_cond, e_true, e_false_opt) = &e.exp.value else { | ||
return false; | ||
}; | ||
if is_unit(self, e_true) { | ||
let u_msg = "Unnecessary unit '()'"; | ||
let if_msg = "Consider negating the 'if' condition and simplifying"; | ||
let mut diag = diag!( | ||
StyleCodes::UnnecessaryUnit.diag_info(), | ||
(e_true.exp.loc, u_msg), | ||
(e_cond.exp.loc, if_msg), | ||
); | ||
diag.add_note("For example 'if (cond) () else e' can be simplified to 'if (!cond) e'"); | ||
self.env.add_diag(diag); | ||
} | ||
if let Some(e_false) = e_false_opt { | ||
if is_unit(self, e_false) { | ||
let u_msg = "Unnecessary 'else ()'."; | ||
let if_msg = "An 'if' without an 'else' has an implicit 'else ()'. \ | ||
Consider removing the 'else' branch"; | ||
let mut diag = diag!( | ||
StyleCodes::UnnecessaryUnit.diag_info(), | ||
(e_false.exp.loc, u_msg), | ||
(e.exp.loc, if_msg), | ||
); | ||
diag.add_note( | ||
"For example 'if (cond) e else ()' can be simplified to 'if (cond) e'", | ||
); | ||
self.env.add_diag(diag); | ||
} | ||
} | ||
false | ||
} | ||
} | ||
|
||
fn is_unit_seq(context: &mut Context, s: &T::SequenceItem) -> bool { | ||
match &s.value { | ||
SequenceItem_::Seq(e) => is_unit(context, e), | ||
SequenceItem_::Declare(_) | SequenceItem_::Bind(_, _, _) => false, | ||
} | ||
} | ||
|
||
fn is_unit(context: &mut Context, e: &T::Exp) -> bool { | ||
use UnannotatedExp_ as TE; | ||
match &e.exp.value { | ||
TE::Unit { .. } => true, | ||
TE::Annotate(inner, _) => is_unit(context, inner), | ||
TE::Block((_, seq)) if seq.is_empty() => { | ||
context | ||
.env | ||
.add_diag(ice!((e.exp.loc, "Unexpected empty block without a value"))); | ||
false | ||
} | ||
TE::Block((_, seq)) if seq.len() == 1 => is_unit_seq(context, &seq[0]), | ||
_ => false, | ||
} | ||
} |
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
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
6 changes: 4 additions & 2 deletions
6
...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 |
---|---|---|
@@ -1,12 +1,14 @@ | ||
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[] }; | ||
if (condition) { foo(); true } else false; | ||
if (condition) b"" else { foo(); foo(); vector[] }; | ||
} | ||
|
||
// we don't do this check after constant folding | ||
public fun t1(condition: bool) { | ||
if (condition) 1 + 1 else 2; | ||
} | ||
|
||
fun foo() {} | ||
} |
12 changes: 12 additions & 0 deletions
12
external-crates/move/crates/move-compiler/tests/linter/suppress_unnecessary_unit.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 @@ | ||
// suppress unnecessary_unit lint | ||
module a::m { | ||
|
||
#[allow(lint(unnecessary_unit))] | ||
public fun test_empty_else(x: bool): bool { | ||
if (x) { x = true; } else {}; | ||
if (!x) () else { test_empty_else(x); }; | ||
{ (); }; | ||
(); | ||
x | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
external-crates/move/crates/move-compiler/tests/linter/true_negative_unnecessary_unit.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 @@ | ||
// tests unnecessary units. These caeses are not errors and should not be reported | ||
module a::unnecessary_unit { | ||
public fun t_if_without_else(cond: bool): u64 { | ||
let x = 0; | ||
if (cond) x = 1; | ||
x | ||
} | ||
|
||
public fun t() { | ||
() // unit here is okay | ||
} | ||
} |
Oops, something went wrong.