diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f06be45..a7ac4d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * Fixed bug in using indexing/dotting inside index bracket. +* `while` and `loop` statements are no longer considered _pure_ (since a loop can go on forever and this is a side effect). Version 1.0.0 diff --git a/src/ast.rs b/src/ast.rs index daffd793..cb366bb7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1073,7 +1073,9 @@ pub enum Stmt { Box<(BTreeMap, StmtBlock)>>, StmtBlock)>, Position, ), - /// `while` expr `{` stmt `}` + /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` + /// + /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. While(Expr, Box, Position), /// `do` `{` stmt `}` `while`|`until` expr /// @@ -1084,7 +1086,7 @@ pub enum Stmt { Do(Box, Expr, OptionFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` For(Expr, Box<(Ident, Option, StmtBlock)>, Position), - /// \[`export`\] `let`/`const` id `=` expr + /// \[`export`\] `let`|`const` id `=` expr /// /// ### Option Flags /// @@ -1298,10 +1300,22 @@ impl Stmt { }) && (x.1).0.iter().all(Stmt::is_pure) } - Self::While(condition, block, _) | Self::Do(block, condition, _, _) => { - condition.is_pure() && block.0.iter().all(Stmt::is_pure) + + // Loops that exit can be pure because it can never be infinite. + Self::While(Expr::BoolConstant(false, _), _, _) => true, + Self::Do(body, Expr::BoolConstant(x, _), options, _) + if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => + { + body.iter().all(Stmt::is_pure) } + + // Loops are never pure since they can be infinite - and that's a side effect. + Self::While(_, _, _) | Self::Do(_, _, _, _) => false, + + // For loops can be pure because if the iterable is pure, it is finite, + // so infinite loops can never occur. Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), + Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 222ce7b1..2d412c4b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -49,3 +49,16 @@ fn test_do() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_infinite_loops() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.set_max_operations(1024); + + assert!(engine.consume("loop {}").is_err()); + assert!(engine.consume("while true {}").is_err()); + assert!(engine.consume("do {} while true").is_err()); + + Ok(()) +}