-
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.
[Linter] Unnecessary while loop (#16876)
# Description This linter encourages replacing `while(true)` loops with the more idiomatic loop construct. Here's a breakdown of how it works: It checks each expression in the AST. If the expression is a While loop, it examines the condition. If the condition is always true (using the `is_condition_always_true` function), it reports a diagnostic suggesting to use loop instead. The `is_condition_always_true` function checks if the condition is a boolean literal with the value true. ## Test plan Added more use case including false positive, false negative case ## Release notes - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [X] CLI: Move will now lint against `while (true)`, which should be replaced by `loop` - [ ] Rust SDK: --------- Co-authored-by: jamedzung <[email protected]> Co-authored-by: Todd Nowacki <[email protected]>
- Loading branch information
1 parent
b490bb1
commit c4343a2
Showing
7 changed files
with
144 additions
and
9 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
68 changes: 68 additions & 0 deletions
68
external-crates/move/crates/move-compiler/src/linters/unnecessary_while_loop.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,68 @@ | ||
//! Encourages replacing `while(true)` with `loop` for infinite loops in Move for clarity and conciseness. | ||
//! Identifies `while(true)` patterns, suggesting a more idiomatic approach using `loop`. | ||
//! Aims to enhance code readability and adherence to Rust idioms. | ||
use crate::{ | ||
diag, | ||
diagnostics::{ | ||
codes::{custom, DiagnosticInfo, Severity}, | ||
WarningFilters, | ||
}, | ||
expansion::ast::Value_, | ||
shared::CompilationEnv, | ||
typing::{ | ||
ast::{self as T, UnannotatedExp_}, | ||
visitor::{TypingVisitorConstructor, TypingVisitorContext}, | ||
}, | ||
}; | ||
|
||
use super::{LinterDiagnosticCategory, LINT_WARNING_PREFIX, WHILE_TRUE_TO_LOOP_DIAG_CODE}; | ||
|
||
const WHILE_TRUE_TO_LOOP_DIAG: DiagnosticInfo = custom( | ||
LINT_WARNING_PREFIX, | ||
Severity::Warning, | ||
LinterDiagnosticCategory::Complexity as u8, | ||
WHILE_TRUE_TO_LOOP_DIAG_CODE, | ||
"unnecessary 'while (true)', replace with 'loop'", | ||
); | ||
|
||
pub struct WhileTrueToLoop; | ||
|
||
pub struct Context<'a> { | ||
env: &'a mut CompilationEnv, | ||
} | ||
|
||
impl TypingVisitorConstructor for WhileTrueToLoop { | ||
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: &mut T::Exp) -> bool { | ||
let UnannotatedExp_::While(_, cond, _) = &exp.exp.value else { | ||
return false; | ||
}; | ||
let UnannotatedExp_::Value(sp!(_, Value_::Bool(true))) = &cond.exp.value else { | ||
return false; | ||
}; | ||
|
||
let msg = "'while (true)' can be always replaced with 'loop'"; | ||
let mut diag = diag!(WHILE_TRUE_TO_LOOP_DIAG, (exp.exp.loc, msg)); | ||
diag.add_note( | ||
"A 'loop' is more useful in these cases. Unlike 'while', 'loop' can have a \ | ||
'break' with a value, e.g. 'let x = loop { break 42 };'", | ||
); | ||
self.env.add_diag(diag); | ||
|
||
false | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...-crates/move/crates/move-compiler/tests/linter/false_negative_unnecessary_while_loop.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,11 @@ | ||
module 0x42::loop_test { | ||
|
||
// These should trigger but currently dont | ||
public fun false_negative_obfuscated_true() { | ||
let always_true = true; | ||
while (always_true) {}; | ||
while (true && true) {}; | ||
while (true || false) {}; | ||
while (1 > 0) {}; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
external-crates/move/crates/move-compiler/tests/linter/suppress_unnecessary_while_loop.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,7 @@ | ||
module 0x42::loop_test { | ||
|
||
#[allow(lint(while_true))] | ||
public fun suppressed_while_true() { | ||
while (true) {}; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...l-crates/move/crates/move-compiler/tests/linter/true_negative_unnecessary_while_loop.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,11 @@ | ||
module 0x42::loop_test { | ||
|
||
public fun true_negative_while_with_condition() { | ||
let b = false; | ||
while (false) {}; | ||
while (b) {}; | ||
while (false && true) {}; | ||
while (false || false) {}; | ||
while (0 > 1) {}; | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...al-crates/move/crates/move-compiler/tests/linter/true_positive_unnecessary_while_loop.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,18 @@ | ||
warning[Lint W01004]: unnecessary 'while (true)', replace with 'loop' | ||
┌─ tests/linter/true_positive_unnecessary_while_loop.move:3:9 | ||
│ | ||
3 │ while (true) {}; | ||
│ ^^^^^^^^^^^^^^^ 'while (true)' can be always replaced with 'loop' | ||
│ | ||
= A 'loop' is more useful in these cases. Unlike 'while', 'loop' can have a 'break' with a value, e.g. 'let x = loop { break 42 };' | ||
= This warning can be suppressed with '#[allow(lint(while_true))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01004]: unnecessary 'while (true)', replace with 'loop' | ||
┌─ tests/linter/true_positive_unnecessary_while_loop.move:4:9 | ||
│ | ||
4 │ while (true) { break } | ||
│ ^^^^^^^^^^^^^^^^^^^^^^ 'while (true)' can be always replaced with 'loop' | ||
│ | ||
= A 'loop' is more useful in these cases. Unlike 'while', 'loop' can have a 'break' with a value, e.g. 'let x = loop { break 42 };' | ||
= This warning can be suppressed with '#[allow(lint(while_true))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
6 changes: 6 additions & 0 deletions
6
...l-crates/move/crates/move-compiler/tests/linter/true_positive_unnecessary_while_loop.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,6 @@ | ||
module 0x42::loop_test { | ||
public fun true_positive_infinite_loop() { | ||
while (true) {}; | ||
while (true) { break } | ||
} | ||
} |