From 0ed507dd74b003dd86dae44164590fa58f037637 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 16 Jan 2025 17:10:27 -0300 Subject: [PATCH 1/8] feat: `loop` keyword in runtime and comptime code --- .../noirc_evaluator/src/ssa/opt/unrolling.rs | 20 ++++--- .../src/ssa/ssa_gen/context.rs | 12 ++--- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 45 ++++++++++++++-- .../noirc_frontend/src/elaborator/lints.rs | 1 + .../src/elaborator/statements.rs | 20 +++++-- .../src/hir/comptime/hir_to_display_ast.rs | 1 + .../src/hir/comptime/interpreter.rs | 21 ++++++++ .../src/hir/resolution/errors.rs | 9 ++++ compiler/noirc_frontend/src/hir_def/stmt.rs | 1 + .../src/monomorphization/ast.rs | 1 + .../src/monomorphization/mod.rs | 4 ++ .../src/monomorphization/printer.rs | 10 ++++ compiler/noirc_frontend/src/tests.rs | 1 + .../loop_keyword/Nargo.toml | 5 ++ .../loop_keyword/src/main.nr | 52 +++++++++++++++++++ 15 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 test_programs/compile_success_empty/loop_keyword/Nargo.toml create mode 100644 test_programs/compile_success_empty/loop_keyword/src/main.nr diff --git a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index 79181b7e74e..435f9e752cb 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -310,11 +310,13 @@ impl Loop { // simplified to a simple jump. return None; } - assert_eq!( - instructions.len(), - 1, - "The header should just compare the induction variable and jump" - ); + + if instructions.len() != 1 { + // The header should just compare the induction variable and jump. + // If that's not the case, this might be a `loop` and not a `for` loop. + return None; + } + match &function.dfg[instructions[0]] { Instruction::Binary(Binary { lhs: _, operator: BinaryOp::Lt, rhs }) => { function.dfg.get_numeric_constant(*rhs) @@ -750,7 +752,13 @@ fn get_induction_variable(function: &Function, block: BasicBlockId) -> Result, pub(super) loop_end: BasicBlockId, } @@ -1010,13 +1011,8 @@ impl<'a> FunctionContext<'a> { } } - pub(crate) fn enter_loop( - &mut self, - loop_entry: BasicBlockId, - loop_index: ValueId, - loop_end: BasicBlockId, - ) { - self.loops.push(Loop { loop_entry, loop_index, loop_end }); + pub(crate) fn enter_loop(&mut self, loop_: Loop) { + self.loops.push(loop_); } pub(crate) fn exit_loop(&mut self) { diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index d73a5946b4c..a77dc71b940 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -6,7 +6,7 @@ use acvm::AcirField; use noirc_frontend::token::FmtStrFragment; pub(crate) use program::Ssa; -use context::SharedContext; +use context::{Loop, SharedContext}; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use noirc_frontend::ast::{UnaryOp, Visibility}; @@ -152,6 +152,7 @@ impl<'a> FunctionContext<'a> { Expression::Index(index) => self.codegen_index(index), Expression::Cast(cast) => self.codegen_cast(cast), Expression::For(for_expr) => self.codegen_for(for_expr), + Expression::Loop(block) => self.codegen_loop(block), Expression::If(if_expr) => self.codegen_if(if_expr), Expression::Tuple(tuple) => self.codegen_tuple(tuple), Expression::ExtractTupleField(tuple, index) => { @@ -557,7 +558,7 @@ impl<'a> FunctionContext<'a> { // Remember the blocks and variable used in case there are break/continue instructions // within the loop which need to jump to them. - self.enter_loop(loop_entry, loop_index, loop_end); + self.enter_loop(Loop { loop_entry, loop_index: Some(loop_index), loop_end }); // Set the location of the initial jmp instruction to the start range. This is the location // used to issue an error if the start range cannot be determined at compile-time. @@ -587,6 +588,38 @@ impl<'a> FunctionContext<'a> { Ok(Self::unit_value()) } + /// Codegens a loop, creating three new blocks in the process. + /// The return value of a loop is always a unit literal. + /// + /// For example, the loop `loop { body }` is codegen'd as: + /// + /// ```text + /// br loop_body() + /// loop_body(): + /// v3 = ... codegen body ... + /// br loop_body(v4) + /// loop_end(): + /// ... This is the current insert point after codegen_for finishes ... + /// ``` + fn codegen_loop(&mut self, block: &Expression) -> Result { + let loop_body = self.builder.insert_block(); + let loop_end = self.builder.insert_block(); + + self.enter_loop(Loop { loop_entry: loop_body, loop_index: None, loop_end }); + + self.builder.terminate_with_jmp(loop_body, vec![]); + + // Compile the loop body + self.builder.switch_to_block(loop_body); + self.codegen_expression(block)?; + self.builder.terminate_with_jmp(loop_body, vec![]); + + // Finish by switching to the end of the loop + self.builder.switch_to_block(loop_end); + self.exit_loop(); + Ok(Self::unit_value()) + } + /// Codegens an if expression, handling the case of what to do if there is no 'else'. /// /// For example, the expression `if cond { a } else { b }` is codegen'd as: @@ -852,8 +885,12 @@ impl<'a> FunctionContext<'a> { let loop_ = self.current_loop(); // Must remember to increment i before jumping - let new_loop_index = self.make_offset(loop_.loop_index, 1); - self.builder.terminate_with_jmp(loop_.loop_entry, vec![new_loop_index]); + if let Some(loop_index) = loop_.loop_index { + let new_loop_index = self.make_offset(loop_index, 1); + self.builder.terminate_with_jmp(loop_.loop_entry, vec![new_loop_index]); + } else { + self.builder.terminate_with_jmp(loop_.loop_entry, vec![]); + } Self::unit_value() } } diff --git a/compiler/noirc_frontend/src/elaborator/lints.rs b/compiler/noirc_frontend/src/elaborator/lints.rs index d3b776bea24..82a5e19054b 100644 --- a/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/compiler/noirc_frontend/src/elaborator/lints.rs @@ -282,6 +282,7 @@ fn can_return_without_recursing(interner: &NodeInterner, func_id: FuncId, expr_i HirStatement::Semi(e) => check(e), // Rust doesn't seem to check the for loop body (it's bounds might mean it's never called). HirStatement::For(e) => check(e.start_range) && check(e.end_range), + HirStatement::Loop(e) => check(e), HirStatement::Constrain(_) | HirStatement::Comptime(_) | HirStatement::Break diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 24653772f9f..a01b24c2f0f 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -271,11 +271,25 @@ impl<'context> Elaborator<'context> { pub(super) fn elaborate_loop( &mut self, - _block: Expression, + block: Expression, span: noirc_errors::Span, ) -> (HirStatement, Type) { - self.push_err(ResolverError::LoopNotYetSupported { span }); - (HirStatement::Error, Type::Unit) + let in_constrained_function = self.in_constrained_function(); + if in_constrained_function { + self.push_err(ResolverError::LoopInConstrainedFn { span }); + } + + self.nested_loops += 1; + self.push_scope(); + + let (block, _block_type) = self.elaborate_expression(block); + + self.pop_scope(); + self.nested_loops -= 1; + + let statement = HirStatement::Loop(block); + + (statement, Type::Unit) } fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) { diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 9338c0fc37f..95f18630f4a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -59,6 +59,7 @@ impl HirStatement { block: for_stmt.block.to_display_ast(interner), span, }), + HirStatement::Loop(block) => StatementKind::Loop(block.to_display_ast(interner)), HirStatement::Break => StatementKind::Break, HirStatement::Continue => StatementKind::Continue, HirStatement::Expression(expr) => { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index ec4d33c3ca4..76060dc282b 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1550,6 +1550,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { HirStatement::Constrain(constrain) => self.evaluate_constrain(constrain), HirStatement::Assign(assign) => self.evaluate_assign(assign), HirStatement::For(for_) => self.evaluate_for(for_), + HirStatement::Loop(expression) => self.evaluate_loop(expression), HirStatement::Break => self.evaluate_break(statement), HirStatement::Continue => self.evaluate_continue(statement), HirStatement::Expression(expression) => self.evaluate(expression), @@ -1741,6 +1742,26 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(Value::Unit) } + fn evaluate_loop(&mut self, expr: ExprId) -> IResult { + let was_in_loop = std::mem::replace(&mut self.in_loop, true); + + loop { + self.push_scope(); + + match self.evaluate(expr) { + Ok(_) => (), + Err(InterpreterError::Break) => break, + Err(InterpreterError::Continue) => continue, + Err(other) => return Err(other), + } + + self.pop_scope(); + } + + self.in_loop = was_in_loop; + Ok(Value::Unit) + } + fn evaluate_break(&mut self, id: StmtId) -> IResult { if self.in_loop { Err(InterpreterError::Break) diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index e0e09d53311..77ba76a0595 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -98,6 +98,8 @@ pub enum ResolverError { DependencyCycle { span: Span, item: String, cycle: String }, #[error("break/continue are only allowed in unconstrained functions")] JumpInConstrainedFn { is_break: bool, span: Span }, + #[error("loop is only allowed in unconstrained functions")] + LoopInConstrainedFn { span: Span }, #[error("break/continue are only allowed within loops")] JumpOutsideLoop { is_break: bool, span: Span }, #[error("Only `comptime` globals can be mutable")] @@ -434,6 +436,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::LoopInConstrainedFn { span } => { + Diagnostic::simple_error( + "loop is only allowed in unconstrained functions".into(), + "Constrained code must always have a known number of loop iterations".into(), + *span, + ) + }, ResolverError::JumpOutsideLoop { is_break, span } => { let item = if *is_break { "break" } else { "continue" }; Diagnostic::simple_error( diff --git a/compiler/noirc_frontend/src/hir_def/stmt.rs b/compiler/noirc_frontend/src/hir_def/stmt.rs index c42b8230290..8a580e735b1 100644 --- a/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -16,6 +16,7 @@ pub enum HirStatement { Constrain(HirConstrainStatement), Assign(HirAssignStatement), For(HirForStatement), + Loop(ExprId), Break, Continue, Expression(ExprId), diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index d219e8f7c2d..65bddcb6807 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -36,6 +36,7 @@ pub enum Expression { Index(Index), Cast(Cast), For(For), + Loop(Box), If(If), Tuple(Vec), ExtractTupleField(Box, usize), diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index b0c8744ea8f..ae7732f680e 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -695,6 +695,10 @@ impl<'interner> Monomorphizer<'interner> { block, })) } + HirStatement::Loop(block) => { + let block = Box::new(self.expr(block)?); + Ok(ast::Expression::Loop(block)) + } HirStatement::Expression(expr) => self.expr(expr), HirStatement::Semi(expr) => { self.expr(expr).map(|expr| ast::Expression::Semi(Box::new(expr))) diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index 25ac1336075..665f4dcd371 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -49,6 +49,7 @@ impl AstPrinter { write!(f, " as {})", cast.r#type) } Expression::For(for_expr) => self.print_for(for_expr, f), + Expression::Loop(block) => self.print_loop(block, f), Expression::If(if_expr) => self.print_if(if_expr, f), Expression::Tuple(tuple) => self.print_tuple(tuple, f), Expression::ExtractTupleField(expr, index) => { @@ -209,6 +210,15 @@ impl AstPrinter { write!(f, "}}") } + fn print_loop(&mut self, block: &Expression, f: &mut Formatter) -> Result<(), std::fmt::Error> { + write!(f, "loop {{")?; + self.indent_level += 1; + self.print_expr_expect_block(block, f)?; + self.indent_level -= 1; + self.next_line(f)?; + write!(f, "}}") + } + fn print_if( &mut self, if_expr: &super::ast::If, diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 637b15e7197..500017f74f5 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -903,6 +903,7 @@ fn find_lambda_captures(stmts: &[StmtId], interner: &NodeInterner, result: &mut HirStatement::Constrain(constr_stmt) => constr_stmt.0, HirStatement::Semi(semi_expr) => semi_expr, HirStatement::For(for_loop) => for_loop.block, + HirStatement::Loop(block) => block, HirStatement::Error => panic!("Invalid HirStatement!"), HirStatement::Break => panic!("Unexpected break"), HirStatement::Continue => panic!("Unexpected continue"), diff --git a/test_programs/compile_success_empty/loop_keyword/Nargo.toml b/test_programs/compile_success_empty/loop_keyword/Nargo.toml new file mode 100644 index 00000000000..8189b407cd9 --- /dev/null +++ b/test_programs/compile_success_empty/loop_keyword/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "loop_keyword" +type = "bin" +authors = [""] +[dependencies] diff --git a/test_programs/compile_success_empty/loop_keyword/src/main.nr b/test_programs/compile_success_empty/loop_keyword/src/main.nr new file mode 100644 index 00000000000..b038ae22343 --- /dev/null +++ b/test_programs/compile_success_empty/loop_keyword/src/main.nr @@ -0,0 +1,52 @@ +fn main() { + /// Safety: test code + unsafe { + check_loop(); + } + + check_comptime_loop(); +} + +unconstrained fn check_loop() { + let mut i = 0; + let mut sum = 0; + + loop { + if i == 4 { + break; + } + + if i == 2 { + i += 1; + continue; + } + + sum += i; + i += 1; + } + + assert_eq(sum, 1 + 3); +} + +fn check_comptime_loop() { + comptime { + let mut i = 0; + let mut sum = 0; + + loop { + if i == 4 { + break; + } + + if i == 2 { + i += 1; + continue; + } + + sum += i; + i += 1; + } + + assert_eq(sum, 1 + 3); + } +} From f2f8ca8b2b4c9aff9a330bfb0a70273d3d4fd9c2 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 08:22:58 -0300 Subject: [PATCH 2/8] Avoid hanging the LSP server on infinite loops --- compiler/noirc_frontend/src/hir/comptime/errors.rs | 13 ++++++++++++- .../noirc_frontend/src/hir/comptime/interpreter.rs | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 6ff918328a1..39497e60fd9 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -246,6 +246,9 @@ pub enum InterpreterError { GlobalsDependencyCycle { location: Location, }, + LoopHaltedForUiResponsiveness { + location: Location, + }, // These cases are not errors, they are just used to prevent us from running more code // until the loop can be resumed properly. These cases will never be displayed to users. @@ -323,7 +326,8 @@ impl InterpreterError { | InterpreterError::CannotSetFunctionBody { location, .. } | InterpreterError::UnknownArrayLength { location, .. } | InterpreterError::CannotInterpretFormatStringWithErrors { location } - | InterpreterError::GlobalsDependencyCycle { location } => *location, + | InterpreterError::GlobalsDependencyCycle { location } + | InterpreterError::LoopHaltedForUiResponsiveness { location } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -683,6 +687,13 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = String::new(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::LoopHaltedForUiResponsiveness { location } => { + let msg = "This loop took too much time to execute so it was halted for UI responsiveness" + .to_string(); + let secondary = + "This error doesn't happen in normal executions of `nargo`".to_string(); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 76060dc282b..d48a27c4181 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -1744,6 +1744,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn evaluate_loop(&mut self, expr: ExprId) -> IResult { let was_in_loop = std::mem::replace(&mut self.in_loop, true); + let in_lsp = self.elaborator.interner.is_in_lsp_mode(); + let mut counter = 0; loop { self.push_scope(); @@ -1756,6 +1758,12 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } self.pop_scope(); + + counter += 1; + if in_lsp && counter == 10_000 { + let location = self.elaborator.interner.expr_location(&expr); + return Err(InterpreterError::LoopHaltedForUiResponsiveness { location }); + } } self.in_loop = was_in_loop; From bc138cd58ffd3aa1494693ce9b3d1667dac07667 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 08:31:05 -0300 Subject: [PATCH 3/8] Briefly document `loop` --- docs/docs/noir/concepts/control_flow.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/docs/noir/concepts/control_flow.md b/docs/docs/noir/concepts/control_flow.md index b365bb22728..aeb2dc72f38 100644 --- a/docs/docs/noir/concepts/control_flow.md +++ b/docs/docs/noir/concepts/control_flow.md @@ -29,10 +29,9 @@ if a == 0 { assert(x == 2); ``` -## Loops +## For loops -Noir has one kind of loop: the `for` loop. `for` loops allow you to repeat a block of code multiple -times. +`for` loops allow you to repeat a block of code multiple times. The following block of code between the braces is run 10 times. @@ -46,9 +45,26 @@ Alternatively, `start..=end` can be used for a range that is inclusive on both e The index for loops is of type `u64`. +## Loops + +In unconstrained code, `loop` is allowed for loops that end after a certain condition is met +(or to loop forever): + +```rust +let mut i = 10 +loop { + println(i); + i -= 1; + + if i == 0 { + break; + } +} +``` + ### Break and Continue -In unconstrained code, `break` and `continue` are also allowed in `for` loops. These are only allowed +In unconstrained code, `break` and `continue` are also allowed in `for` and `loop` loops. These are only allowed in unconstrained code since normal constrained code requires that Noir knows exactly how many iterations a loop may have. `break` and `continue` can be used like so: From 9a8644087d0bacfe19253986288ecbc74fefac6e Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 12:59:16 -0300 Subject: [PATCH 4/8] Put loop docs after break, and explain why they must be in unconstrained code --- docs/docs/noir/concepts/control_flow.md | 36 +++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/docs/noir/concepts/control_flow.md b/docs/docs/noir/concepts/control_flow.md index aeb2dc72f38..a11db545e32 100644 --- a/docs/docs/noir/concepts/control_flow.md +++ b/docs/docs/noir/concepts/control_flow.md @@ -45,23 +45,6 @@ Alternatively, `start..=end` can be used for a range that is inclusive on both e The index for loops is of type `u64`. -## Loops - -In unconstrained code, `loop` is allowed for loops that end after a certain condition is met -(or to loop forever): - -```rust -let mut i = 10 -loop { - println(i); - i -= 1; - - if i == 0 { - break; - } -} -``` - ### Break and Continue In unconstrained code, `break` and `continue` are also allowed in `for` and `loop` loops. These are only allowed @@ -93,3 +76,22 @@ above, `continue` will jump to `println("Iteration start")` when used. Note that The iteration variable `i` is still increased by one as normal when `continue` is used. `break` and `continue` cannot currently be used to jump out of more than a single loop at a time. + +## Loops + +In unconstrained code, `loop` is allowed for loops that end with a `break`. This is only allowed +in unconstrained code since normal constrained code requires that Noir knows exactly how many iterations +a loop may have. + +```rust +let mut i = 10 +loop { + println(i); + i -= 1; + + if i == 0 { + break; + } +} +``` + From a19d23261b7261af4cfd2b2fd97b9cc1c593da43 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 13:00:34 -0300 Subject: [PATCH 5/8] Make the "look took a long time" error a warning --- compiler/noirc_frontend/src/hir/comptime/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 39497e60fd9..e9a615f2c59 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -692,7 +692,7 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { .to_string(); let secondary = "This error doesn't happen in normal executions of `nargo`".to_string(); - CustomDiagnostic::simple_error(msg, secondary, location.span) + CustomDiagnostic::simple_warning(msg, secondary, location.span) } } } From a87ce0eac32206615448ca5514d4510fc553a148 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 13:01:10 -0300 Subject: [PATCH 6/8] Fix SSA in doc comment --- compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index a77dc71b940..fbb2b306bdf 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -597,7 +597,7 @@ impl<'a> FunctionContext<'a> { /// br loop_body() /// loop_body(): /// v3 = ... codegen body ... - /// br loop_body(v4) + /// br loop_body() /// loop_end(): /// ... This is the current insert point after codegen_for finishes ... /// ``` From 03776cae1fa59937e7c159c1b62bdd332a193b74 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 13:18:19 -0300 Subject: [PATCH 7/8] Loops are experimental --- compiler/noirc_frontend/src/parser/parser/statement.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/parser/parser/statement.rs b/compiler/noirc_frontend/src/parser/parser/statement.rs index 465e48e3bad..8c6d18d90ef 100644 --- a/compiler/noirc_frontend/src/parser/parser/statement.rs +++ b/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -294,10 +294,13 @@ impl<'a> Parser<'a> { /// LoopStatement = 'loop' Block fn parse_loop(&mut self) -> Option { + let start_span = self.current_token_span; if !self.eat_keyword(Keyword::Loop) { return None; } + self.push_error(ParserErrorReason::ExperimentalFeature("loops"), start_span); + let block_start_span = self.current_token_span; let block = if let Some(block) = self.parse_block() { Expression { @@ -819,7 +822,8 @@ mod tests { #[test] fn parses_empty_loop() { let src = "loop { }"; - let statement = parse_statement_no_errors(src); + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement_or_error(); let StatementKind::Loop(block) = statement.kind else { panic!("Expected loop"); }; @@ -832,7 +836,8 @@ mod tests { #[test] fn parses_loop_with_statements() { let src = "loop { 1; 2 }"; - let statement = parse_statement_no_errors(src); + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement_or_error(); let StatementKind::Loop(block) = statement.kind else { panic!("Expected loop"); }; From 65063b58e326e20353b4838ac709778a05aced0b Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 17 Jan 2025 13:18:26 -0300 Subject: [PATCH 8/8] Move program to execution_success_empty --- .../loop_keyword/Nargo.toml | 0 .../loop_keyword/src/main.nr | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test_programs/{compile_success_empty => execution_success}/loop_keyword/Nargo.toml (100%) rename test_programs/{compile_success_empty => execution_success}/loop_keyword/src/main.nr (100%) diff --git a/test_programs/compile_success_empty/loop_keyword/Nargo.toml b/test_programs/execution_success/loop_keyword/Nargo.toml similarity index 100% rename from test_programs/compile_success_empty/loop_keyword/Nargo.toml rename to test_programs/execution_success/loop_keyword/Nargo.toml diff --git a/test_programs/compile_success_empty/loop_keyword/src/main.nr b/test_programs/execution_success/loop_keyword/src/main.nr similarity index 100% rename from test_programs/compile_success_empty/loop_keyword/src/main.nr rename to test_programs/execution_success/loop_keyword/src/main.nr