Skip to content

Commit

Permalink
[move][move-linter] implement unnecessary while loop rules
Browse files Browse the repository at this point in the history
  • Loading branch information
tx-tomcat committed Aug 6, 2024
1 parent 0c7e7e2 commit 24aec0d
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 151 deletions.
32 changes: 23 additions & 9 deletions external-crates/move/crates/move-compiler/src/linters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
linters::constant_naming::ConstantNamingVisitor, typing::visitor::TypingVisitor,
};
pub mod constant_naming;
mod unnecessary_while_loop;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LintLevel {
Expand All @@ -34,26 +35,39 @@ pub const ALLOW_ATTR_CATEGORY: &str = "lint";
pub const LINT_WARNING_PREFIX: &str = "Lint ";
pub const CONSTANT_NAMING_FILTER_NAME: &str = "constant_naming";
pub const CONSTANT_NAMING_DIAG_CODE: u8 = 1;
pub const WHILE_TRUE_TO_LOOP_FILTER_NAME: &str = "while_true_to_loop";
pub const WHILE_TRUE_TO_LOOP_DIAG_CODE: u8 = 4;

pub fn known_filters() -> (Option<Symbol>, Vec<WarningFilter>) {
(
Some(ALLOW_ATTR_CATEGORY.into()),
vec![WarningFilter::code(
Some(LINT_WARNING_PREFIX),
LinterDiagnosticCategory::Style as u8,
CONSTANT_NAMING_DIAG_CODE,
Some(CONSTANT_NAMING_FILTER_NAME),
)],
vec![
WarningFilter::code(
Some(LINT_WARNING_PREFIX),
LinterDiagnosticCategory::Style as u8,
CONSTANT_NAMING_DIAG_CODE,
Some(CONSTANT_NAMING_FILTER_NAME),
),
WarningFilter::code(
Some(LINT_WARNING_PREFIX),
LinterDiagnosticCategory::Complexity as u8,
WHILE_TRUE_TO_LOOP_DIAG_CODE,
Some(WHILE_TRUE_TO_LOOP_FILTER_NAME),
),
],
)
}

pub fn linter_visitors(level: LintLevel) -> Vec<Visitor> {
match level {
LintLevel::None | LintLevel::Default => vec![],
LintLevel::All => {
vec![constant_naming::ConstantNamingVisitor::visitor(
ConstantNamingVisitor,
)]
vec![
constant_naming::ConstantNamingVisitor::visitor(ConstantNamingVisitor),
unnecessary_while_loop::WhileTrueToLoop::visitor(
unnecessary_while_loop::WhileTrueToLoop,
),
]
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! 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 move_ir_types::location::Loc;

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,
"",
);

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 {
if let UnannotatedExp_::While(_, cond, _) = &exp.exp.value {
if is_condition_always_true(&cond.exp.value) {
report_while_true_to_loop(self.env, exp.exp.loc);
}
}
false
}
}

fn is_condition_always_true(condition: &UnannotatedExp_) -> bool {
if let UnannotatedExp_::Value(val) = condition {
if let Value_::Bool(b) = &val.value {
return *b;
}
}
false
}
fn report_while_true_to_loop(env: &mut CompilationEnv, loc: Loc) {
let diag = diag!(
WHILE_TRUE_TO_LOOP_DIAG,
(
loc,
"Detected `while(true) {}` loop. Consider replacing with `loop {}`"
)
);
env.add_diag(diag);
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
warning[Lint W01004]:
┌─ tests/linter/incorrect_unnecessary_while_loop.move:6:9
6 │ ╭ while (true) {
7 │ │ // This loop will run forever
8 │ │ }
│ ╰─────────^ Detected `while(true) {}` loop. Consider replacing with `loop {}`
= This warning can be suppressed with '#[allow(lint(while_true_to_loop))]' applied to the 'module' or module member ('const', 'fun', or 'struct')

warning[Lint W01004]:
┌─ tests/linter/incorrect_unnecessary_while_loop.move:14:9
14 │ ╭ while (true) {
15 │ │ if (counter == 10) {
16 │ │ break
17 │ │ };
18 │ │ counter = counter + 1;
19 │ │ }
│ ╰─────────^ Detected `while(true) {}` loop. Consider replacing with `loop {}`
= This warning can be suppressed with '#[allow(lint(while_true_to_loop))]' applied to the 'module' or module member ('const', 'fun', or 'struct')

warning[Lint W01004]:
┌─ tests/linter/incorrect_unnecessary_while_loop.move:43:9
43 │ ╭ while (true) {
44 │ │ if (vector::length(&vec1) == 5) break;
45 │ │ vector::push_back(&mut vec1, vector::length(&vec1));
46 │ │ };
│ ╰─────────^ Detected `while(true) {}` loop. Consider replacing with `loop {}`
= This warning can be suppressed with '#[allow(lint(while_true_to_loop))]' applied to the 'module' or module member ('const', 'fun', or 'struct')

error[E07001]: referential transparency violated
┌─ tests/linter/incorrect_unnecessary_while_loop.move:45:57
45 │ vector::push_back(&mut vec1, vector::length(&vec1));
│ --------- ^^^^^ Invalid borrow of variable 'vec1'
│ │
│ It is still being mutably borrowed by this reference

error[E07001]: referential transparency violated
┌─ tests/linter/incorrect_unnecessary_while_loop.move:51:57
51 │ vector::push_back(&mut vec2, vector::length(&vec2));
│ --------- ^^^^^ Invalid borrow of variable 'vec2'
│ │
│ It is still being mutably borrowed by this reference

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module 0x42::loop_test {
use std::vector;

// This function should trigger the linter
public fun infinite_loop() {
while (true) {
// This loop will run forever
}
}

// This function should also trigger the linter
public fun finite_loop() {
let counter = 0;
while (true) {
if (counter == 10) {
break
};
counter = counter + 1;
}
}

// This function should not trigger the linter
public fun correct_infinite_loop() {
loop {
// This is the correct way to write an infinite loop
}
}

// This function should not trigger the linter
public fun while_with_condition(n: u64) {
let i = 0;
while (i < n) {
i = i + 1;
}
}

// This function uses both `while(true)` and `loop` for comparison
public fun mixed_loops() {
let vec1 = vector::empty<u64>();
let vec2 = vector::empty<u64>();

// This should trigger the linter
while (true) {
if (vector::length(&vec1) == 5) break;
vector::push_back(&mut vec1, vector::length(&vec1));
};

// This should not trigger the linter
loop {
if (vector::length(&vec2) == 5) break;
vector::push_back(&mut vec2, vector::length(&vec2));
};
}
}

0 comments on commit 24aec0d

Please sign in to comment.