From 1946b90958a3f2329d90dceaffae5b8211d758c8 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 15 Mar 2024 14:02:16 -0500 Subject: [PATCH 01/13] Add break & continue keywords --- compiler/noirc_frontend/src/lexer/token.rs | 6 ++++++ compiler/noirc_frontend/src/parser/parser.rs | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 3dc9d05b15e..1e341d34510 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -644,10 +644,12 @@ pub enum Keyword { Assert, AssertEq, Bool, + Break, CallData, Char, CompTime, Constrain, + Continue, Contract, Crate, Dep, @@ -685,10 +687,12 @@ impl fmt::Display for Keyword { Keyword::Assert => write!(f, "assert"), Keyword::AssertEq => write!(f, "assert_eq"), Keyword::Bool => write!(f, "bool"), + Keyword::Break => write!(f, "break"), Keyword::Char => write!(f, "char"), Keyword::CallData => write!(f, "call_data"), Keyword::CompTime => write!(f, "comptime"), Keyword::Constrain => write!(f, "constrain"), + Keyword::Continue => write!(f, "continue"), Keyword::Contract => write!(f, "contract"), Keyword::Crate => write!(f, "crate"), Keyword::Dep => write!(f, "dep"), @@ -729,10 +733,12 @@ impl Keyword { "assert" => Keyword::Assert, "assert_eq" => Keyword::AssertEq, "bool" => Keyword::Bool, + "break" => Keyword::Break, "call_data" => Keyword::CallData, "char" => Keyword::Char, "comptime" => Keyword::CompTime, "constrain" => Keyword::Constrain, + "continue" => Keyword::Continue, "contract" => Keyword::Contract, "crate" => Keyword::Crate, "dep" => Keyword::Dep, diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 383a1ffafc9..bca5501bb42 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -46,6 +46,7 @@ use crate::{ }; use chumsky::prelude::*; +use chumsky::text::keyword; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; @@ -421,6 +422,8 @@ where declaration(expr_parser.clone()), assignment(expr_parser.clone()), for_loop(expr_no_constructors, statement), + break_statement(), + continue_statement(), return_statement(expr_parser.clone()), expr_parser.map(StatementKind::Expression), )) @@ -431,6 +434,14 @@ fn fresh_statement() -> impl NoirParser { statement(expression(), expression_no_constructors(expression())) } +fn break_statement() -> impl NoirParser { + keyword(Keyword::Break) +} + +fn continue_statement() -> impl NoirParser { + keyword(Keyword::Continue) +} + fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, From 84ec91b7ecf6cb086e4d81439e797a9887954b20 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 15 Mar 2024 15:11:12 -0500 Subject: [PATCH 02/13] Implement break & continue; no frontend checks --- .../src/ssa/ssa_gen/context.rs | 33 ++++++++++++++++++- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 22 +++++++++++++ compiler/noirc_frontend/src/ast/statement.rs | 6 ++++ .../src/hir/resolution/resolver.rs | 2 ++ .../noirc_frontend/src/hir/type_check/stmt.rs | 2 +- compiler/noirc_frontend/src/hir_def/stmt.rs | 2 ++ .../src/monomorphization/ast.rs | 2 ++ .../src/monomorphization/mod.rs | 2 ++ .../src/monomorphization/printer.rs | 2 ++ compiler/noirc_frontend/src/parser/parser.rs | 5 ++- 10 files changed, 73 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 409b99958a9..a9a707ca8ed 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -39,6 +39,11 @@ pub(super) struct FunctionContext<'a> { pub(super) builder: FunctionBuilder, shared_context: &'a SharedContext, + + /// Contains any loops we're currently in the middle of translating. + /// These are ordered such that an inner loop is at the end of the vector and + /// outer loops are at the beginning. When a loop is finished, it is popped. + loops: Vec, } /// Shared context for all functions during ssa codegen. This is the only @@ -72,6 +77,13 @@ pub(super) struct SharedContext { pub(super) program: Program, } +#[derive(Copy, Clone)] +pub(super) struct Loop { + pub(super) loop_entry: BasicBlockId, + pub(super) loop_index: ValueId, + pub(super) loop_end: BasicBlockId, +} + /// The queue of functions remaining to compile type FunctionQueue = Vec<(ast::FuncId, IrFunctionId)>; @@ -97,7 +109,8 @@ impl<'a> FunctionContext<'a> { .1; let builder = FunctionBuilder::new(function_name, function_id, runtime); - let mut this = Self { definitions: HashMap::default(), builder, shared_context }; + let definitions = HashMap::default(); + let mut this = Self { definitions, builder, shared_context, loops: Vec::new() }; this.add_parameters_to_scope(parameters); this } @@ -1053,6 +1066,24 @@ impl<'a> FunctionContext<'a> { self.builder.decrement_array_reference_count(parameter); } } + + 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 exit_loop(&mut self) { + self.loops.pop(); + } + + pub(crate) fn current_loop(&self) -> Loop { + // The frontend should ensure break/continue are never used outside a loop + *self.loops.last().expect("current_loop: not in a loop!") + } } /// True if the given operator cannot be encoded directly and needs diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 3d8ae0bb3eb..5acb266b4c1 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -152,6 +152,8 @@ impl<'a> FunctionContext<'a> { } Expression::Assign(assign) => self.codegen_assign(assign), Expression::Semi(semi) => self.codegen_semi(semi), + Expression::Break => Ok(self.codegen_break()), + Expression::Continue => Ok(self.codegen_continue()), } } @@ -477,6 +479,10 @@ impl<'a> FunctionContext<'a> { let index_type = Self::convert_non_tuple_type(&for_expr.index_type); let loop_index = self.builder.add_block_parameter(loop_entry, index_type); + // 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.builder.set_location(for_expr.start_range_location); let start_index = self.codegen_non_tuple_expression(&for_expr.start_range)?; @@ -507,6 +513,7 @@ impl<'a> FunctionContext<'a> { // Finish by switching back to the end of the loop self.builder.switch_to_block(loop_end); + self.exit_loop(); Ok(Self::unit_value()) } @@ -736,4 +743,19 @@ impl<'a> FunctionContext<'a> { self.codegen_expression(expr)?; Ok(Self::unit_value()) } + + fn codegen_break(&mut self) -> Values { + let loop_end = self.current_loop().loop_end; + self.builder.terminate_with_jmp(loop_end, Vec::new()); + Self::unit_value() + } + + fn codegen_continue(&mut self) -> Values { + 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]); + Self::unit_value() + } } diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 387840b57c4..fb7f520ee71 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -35,6 +35,8 @@ pub enum StatementKind { Expression(Expression), Assign(AssignStatement), For(ForLoopStatement), + Break, + Continue, // This is an expression with a trailing semi-colon Semi(Expression), // This statement is the result of a recovered parse error. @@ -59,6 +61,8 @@ impl Statement { | StatementKind::Constrain(_) | StatementKind::Assign(_) | StatementKind::Semi(_) + | StatementKind::Break + | StatementKind::Continue | StatementKind::Error => { // To match rust, statements always require a semicolon, even at the end of a block if semi.is_none() { @@ -637,6 +641,8 @@ impl Display for StatementKind { StatementKind::Expression(expression) => expression.fmt(f), StatementKind::Assign(assign) => assign.fmt(f), StatementKind::For(for_loop) => for_loop.fmt(f), + StatementKind::Break => write!(f, "break"), + StatementKind::Continue => write!(f, "continue"), StatementKind::Semi(semi) => write!(f, "{semi};"), StatementKind::Error => write!(f, "Error"), } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index c33b83257b0..5ea6d1052fd 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1214,6 +1214,8 @@ impl<'a> Resolver<'a> { } } } + StatementKind::Break => HirStatement::Break, + StatementKind::Continue => HirStatement::Continue, StatementKind::Error => HirStatement::Error, } } diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs index 358bea86922..e90da555803 100644 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -51,7 +51,7 @@ impl<'interner> TypeChecker<'interner> { HirStatement::Constrain(constrain_stmt) => self.check_constrain_stmt(constrain_stmt), HirStatement::Assign(assign_stmt) => self.check_assign_stmt(assign_stmt, stmt_id), HirStatement::For(for_loop) => self.check_for_loop(for_loop), - HirStatement::Error => (), + HirStatement::Break | HirStatement::Continue | HirStatement::Error => (), } Type::Unit } diff --git a/compiler/noirc_frontend/src/hir_def/stmt.rs b/compiler/noirc_frontend/src/hir_def/stmt.rs index b910be1fdda..4e5f718cf47 100644 --- a/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -14,6 +14,8 @@ pub enum HirStatement { Constrain(HirConstrainStatement), Assign(HirAssignStatement), For(HirForStatement), + Break, + Continue, Expression(ExprId), Semi(ExprId), Error, diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 7fcf8e87792..21b77127360 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -38,6 +38,8 @@ pub enum Expression { Constrain(Box, Location, Option>), Assign(Assign), Semi(Box), + Break, + Continue, } /// A definition is either a local (variable), function, or is a built-in diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 4938d33aff9..a99a4e61d4d 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -594,6 +594,8 @@ impl<'interner> Monomorphizer<'interner> { HirStatement::Semi(expr) => { self.expr(expr).map(|expr| ast::Expression::Semi(Box::new(expr))) } + HirStatement::Break => Ok(ast::Expression::Break), + HirStatement::Continue => Ok(ast::Expression::Continue), HirStatement::Error => unreachable!(), } } diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index 7aec2193494..c3e34890ce0 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -73,6 +73,8 @@ impl AstPrinter { self.print_expr(expr, f)?; write!(f, ";") } + Expression::Break => write!(f, "break"), + Expression::Continue => write!(f, "continue"), } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index bca5501bb42..b2d9d7e6802 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -46,7 +46,6 @@ use crate::{ }; use chumsky::prelude::*; -use chumsky::text::keyword; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; @@ -435,11 +434,11 @@ fn fresh_statement() -> impl NoirParser { } fn break_statement() -> impl NoirParser { - keyword(Keyword::Break) + keyword(Keyword::Break).to(StatementKind::Break) } fn continue_statement() -> impl NoirParser { - keyword(Keyword::Continue) + keyword(Keyword::Continue).to(StatementKind::Continue) } fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a From db5137b9204593a9546145586625aaebd6553789 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 15 Mar 2024 15:27:29 -0500 Subject: [PATCH 03/13] Get break/continue working; needed to fix terminator & successor logic --- .../src/ssa/function_builder/mod.rs | 19 +++-- compiler/noirc_evaluator/src/ssa/ir/cfg.rs | 4 - .../noirc_evaluator/src/ssa/opt/inlining.rs | 13 +-- noir_stdlib/src/collections/map.nr | 18 ++--- tooling/nargo_fmt/src/visitor/stmt.rs | 2 + .../nargo_fmt/tests/expected/tmp/Nargo.toml | 7 ++ .../nargo_fmt/tests/expected/tmp/src/main.nr | 79 +++++++++++++++++++ 7 files changed, 116 insertions(+), 26 deletions(-) create mode 100644 tooling/nargo_fmt/tests/expected/tmp/Nargo.toml create mode 100644 tooling/nargo_fmt/tests/expected/tmp/src/main.nr diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 2c39c83b342..6efc4922bdb 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -32,7 +32,7 @@ use super::{ /// functions as needed, although it is limited to one function at a time. pub(crate) struct FunctionBuilder { pub(super) current_function: Function, - current_block: BasicBlockId, + current_block: Option, finished_functions: Vec, call_stack: CallStack, } @@ -49,11 +49,10 @@ impl FunctionBuilder { ) -> Self { let mut new_function = Function::new(function_name, function_id); new_function.set_runtime(runtime); - let current_block = new_function.entry_block(); Self { + current_block: Some(new_function.entry_block()), current_function: new_function, - current_block, finished_functions: Vec::new(), call_stack: CallStack::new(), } @@ -72,7 +71,7 @@ impl FunctionBuilder { ) { let mut new_function = Function::new(name, function_id); new_function.set_runtime(runtime_type); - self.current_block = new_function.entry_block(); + self.current_block = Some(new_function.entry_block()); let old_function = std::mem::replace(&mut self.current_function, new_function); self.finished_functions.push(old_function); @@ -153,9 +152,10 @@ impl FunctionBuilder { instruction: Instruction, ctrl_typevars: Option>, ) -> InsertInstructionResult { + let block = self.current_block(); self.current_function.dfg.insert_instruction_and_results( instruction, - self.current_block, + block, ctrl_typevars, self.call_stack.clone(), ) @@ -165,12 +165,12 @@ impl FunctionBuilder { /// Expects the given block to be within the same function. If you want to insert /// instructions into a new function, call new_function instead. pub(crate) fn switch_to_block(&mut self, block: BasicBlockId) { - self.current_block = block; + self.current_block = Some(block); } /// Returns the block currently being inserted into pub(crate) fn current_block(&mut self) -> BasicBlockId { - self.current_block + self.current_block.expect("Tried to grab the current block after it was terminated") } /// Insert an allocate instruction at the end of the current block, allocating the @@ -311,7 +311,10 @@ impl FunctionBuilder { /// Terminates the current block with the given terminator instruction fn terminate_block_with(&mut self, terminator: TerminatorInstruction) { - self.current_function.dfg.set_block_terminator(self.current_block, terminator); + if let Some(block) = self.current_block.take() { + self.current_function.dfg.set_block_terminator(block, terminator); + } + self.current_block = None; } /// Terminate the current block with a jmp instruction to jmp to the given diff --git a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs index ebfbf003ec4..5a3f07cd673 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs @@ -95,10 +95,6 @@ impl ControlFlowGraph { ); predecessor_node.successors.insert(to); let successor_node = self.data.entry(to).or_default(); - assert!( - successor_node.predecessors.len() < 2, - "ICE: A cfg node cannot have more than two predecessors" - ); successor_node.predecessors.insert(from); } diff --git a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index 776f22b2877..aff06af9921 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -487,6 +487,13 @@ impl<'function> PerFunctionContext<'function> { } TerminatorInstruction::Return { return_values, call_stack } => { let return_values = vecmap(return_values, |value| self.translate_value(*value)); + + // Note that `translate_block` would take us back to the point at which the + // inlining of this source block began. Since additional blocks may have been + // inlined since, we are interested in the block representing the current program + // point, obtained via `current_block`. + let block_id = self.context.builder.current_block(); + if self.inlining_entry { let mut new_call_stack = self.context.call_stack.clone(); new_call_stack.append(call_stack.clone()); @@ -495,11 +502,7 @@ impl<'function> PerFunctionContext<'function> { .set_call_stack(new_call_stack) .terminate_with_return(return_values.clone()); } - // Note that `translate_block` would take us back to the point at which the - // inlining of this source block began. Since additional blocks may have been - // inlined since, we are interested in the block representing the current program - // point, obtained via `current_block`. - let block_id = self.context.builder.current_block(); + Some((block_id, return_values)) } } diff --git a/noir_stdlib/src/collections/map.nr b/noir_stdlib/src/collections/map.nr index 2d76acf1f3a..5f8cc6dab62 100644 --- a/noir_stdlib/src/collections/map.nr +++ b/noir_stdlib/src/collections/map.nr @@ -288,10 +288,10 @@ impl HashMap { let mut result = Option::none(); let hash = self.hash(key); - let mut break = false; + let mut should_break = false; for attempt in 0..N { - if !break { + if !should_break { let index = self.quadratic_probe(hash, attempt as u64); let slot = self._table[index]; @@ -300,7 +300,7 @@ impl HashMap { let (current_key, value) = slot.key_value_unchecked(); if current_key == key { result = Option::some(value); - break = true; + should_break = true; } } } @@ -324,10 +324,10 @@ impl HashMap { self.assert_load_factor(); let hash = self.hash(key); - let mut break = false; + let mut should_break = false; for attempt in 0..N { - if !break { + if !should_break { let index = self.quadratic_probe(hash, attempt as u64); let mut slot = self._table[index]; let mut insert = false; @@ -346,7 +346,7 @@ impl HashMap { if insert { slot.set(key, value); self._table[index] = slot; - break = true; + should_break = true; } } } @@ -364,10 +364,10 @@ impl HashMap { H: Hasher { // docs:end:remove let hash = self.hash(key); - let mut break = false; + let mut should_break = false; for attempt in 0..N { - if !break { + if !should_break { let index = self.quadratic_probe(hash, attempt as u64); let mut slot = self._table[index]; @@ -378,7 +378,7 @@ impl HashMap { slot.mark_deleted(); self._table[index] = slot; self._len -= 1; - break = true; + should_break = true; } } } diff --git a/tooling/nargo_fmt/src/visitor/stmt.rs b/tooling/nargo_fmt/src/visitor/stmt.rs index 44c5dad6b5d..8cf6e02d944 100644 --- a/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/tooling/nargo_fmt/src/visitor/stmt.rs @@ -95,6 +95,8 @@ impl super::FmtVisitor<'_> { self.push_rewrite(self.slice(span).to_string(), span); } StatementKind::Error => unreachable!(), + StatementKind::Break => self.push_rewrite("break".into(), span), + StatementKind::Continue => self.push_rewrite("continue".into(), span), } self.last_position = span.end(); diff --git a/tooling/nargo_fmt/tests/expected/tmp/Nargo.toml b/tooling/nargo_fmt/tests/expected/tmp/Nargo.toml new file mode 100644 index 00000000000..fa2f65983cb --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/tmp/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tmp" +type = "bin" +authors = [""] +compiler_version = ">=0.25.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_fmt/tests/expected/tmp/src/main.nr b/tooling/nargo_fmt/tests/expected/tmp/src/main.nr new file mode 100644 index 00000000000..c5b19a686d2 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/tmp/src/main.nr @@ -0,0 +1,79 @@ +// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +// Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +// Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. +// Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +contract Benchmarking { + use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; + + use dep::value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; + + use dep::aztec::{ + context::Context, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, + log::emit_unencrypted_log, state_vars::{Map, PublicMutable, PrivateSet}, + types::type_serialization::field_serialization::{FieldSerializationMethods, FIELD_SERIALIZED_LEN}, + types::address::{AztecAddress} + }; + + struct Storage { + notes: Map>, + balances: Map>, + } + + impl Storage { + fn init(context: Context) -> pub Self { + Storage { + notes: Map::new( + context, + 1, + |context, slot| { PrivateSet::new(context, slot, ValueNoteMethods) } + ), + balances: Map::new( + context, + 2, + |context, slot| { PublicMutable::new(context, slot, FieldSerializationMethods) } + ) + } + } + } + + #[aztec(private)] + fn constructor() {} + + // Nec tincidunt praesent semper feugiat nibh sed pulvinar. Nibh nisl condimentum id venenatis a. + #[aztec(private)] + fn create_note(owner: Field, value: Field) { + increment(storage.notes.at(owner), value, owner); + } + + // Diam quam nulla porttitor massa id. Elit ullamcorper dignissim cras tincidunt lobortis feugiat. + #[aztec(private)] + fn recreate_note(owner: Field, index: u32) { + let owner_notes = storage.notes.at(owner); + let getter_options = NoteGetterOptions::new().set_limit(1).set_offset(index); + let notes = owner_notes.get_notes(getter_options); + let note = notes[0].unwrap_unchecked(); + owner_notes.remove(note); + increment(owner_notes, note.value, owner); + } + + // Ultrices in iaculis nunc sed augue lacus. + #[aztec(public)] + fn increment_balance(owner: Field, value: Field) { + let current = storage.balances.at(owner).read(); + storage.balances.at(owner).write(current + value); + let _callStackItem1 = context.call_public_function( + context.this_address(), + FunctionSelector::from_signature("broadcast(Field)"), + [owner] + ); + } + + // Est ultricies integer quis auctor elit sed. In nibh mauris cursus mattis molestie a iaculis. + #[aztec(public)] + fn broadcast(owner: Field) { + emit_unencrypted_log(&mut context, storage.balances.at(owner).read()); + } +} + +// Uses the token bridge contract, which tells which input token we need to talk to and handles the exit funds to L1 +contract Uniswap {} From a3d1650c7f672be0eb4e61dc6aed8f75dea278d7 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 15 Mar 2024 16:00:26 -0500 Subject: [PATCH 04/13] Add frontend check & tests --- .../src/hir/resolution/errors.rs | 20 ++++++++ .../src/hir/resolution/resolver.rs | 49 ++++++++++++++++--- compiler/noirc_frontend/src/tests.rs | 28 +++++++++++ 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 30a1ba2ee34..3c6c0582292 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -84,6 +84,10 @@ pub enum ResolverError { LowLevelFunctionOutsideOfStdlib { ident: Ident }, #[error("Dependency cycle found, '{item}' recursively depends on itself: {cycle} ")] DependencyCycle { span: Span, item: String, cycle: String }, + #[error("break/continue are only allowed in unconstrained functions")] + JumpInConstrainedFn { is_break: bool, span: Span }, + #[error("break/continue are only allowed within loops")] + JumpOutsideLoop { is_break: bool, span: Span }, } impl ResolverError { @@ -322,6 +326,22 @@ impl From for Diagnostic { span, ) }, + ResolverError::JumpInConstrainedFn { is_break, span } => { + let item = if is_break { "break" } else { "continue" }; + Diagnostic::simple_error( + format!("{item} is only allowed in unconstrained functions"), + "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( + format!("{item} is only allowed within loops"), + "".into(), + span, + ) + }, } } } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 5ea6d1052fd..092159d6ea7 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -41,7 +41,7 @@ use crate::{ Generics, ItemVisibility, LValue, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, Shared, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind, UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, Visibility, ERROR_IDENT, + UnresolvedTypeExpression, Visibility, ERROR_IDENT, Statement, }; use fm::FileId; use iter_extended::vecmap; @@ -115,6 +115,13 @@ pub struct Resolver<'a> { /// that are captured. We do this in order to create the hidden environment /// parameter for the lambda function. lambda_stack: Vec, + + /// True if we're currently resolving an unconstrained function + in_unconstrained_fn: bool, + + /// How many loops we're currently within. + /// This increases by 1 at the start of a loop, and decreases by 1 when it ends. + nested_loops: u32, } /// ResolverMetas are tagged onto each definition to track how many times they are used @@ -155,6 +162,8 @@ impl<'a> Resolver<'a> { current_item: None, file, in_contract, + in_unconstrained_fn: false, + nested_loops: 0, } } @@ -416,6 +425,11 @@ impl<'a> Resolver<'a> { fn intern_function(&mut self, func: NoirFunction, id: FuncId) -> (HirFunction, FuncMeta) { let func_meta = self.extract_meta(&func, id); + + if func.def.is_unconstrained { + self.in_unconstrained_fn = true; + } + let hir_func = match func.kind { FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { HirFunction::empty() @@ -1148,7 +1162,7 @@ impl<'a> Resolver<'a> { }) } - pub fn resolve_stmt(&mut self, stmt: StatementKind) -> HirStatement { + pub fn resolve_stmt(&mut self, stmt: StatementKind, span: Span) -> HirStatement { match stmt { StatementKind::Let(let_stmt) => { let expression = self.resolve_expression(let_stmt.expression); @@ -1188,6 +1202,8 @@ impl<'a> Resolver<'a> { let end_range = self.resolve_expression(end_range); let (identifier, block) = (for_loop.identifier, for_loop.block); + self.nested_loops += 1; + // TODO: For loop variables are currently mutable by default since we haven't // yet implemented syntax for them to be optionally mutable. let (identifier, block) = self.in_new_scope(|this| { @@ -1200,6 +1216,8 @@ impl<'a> Resolver<'a> { (decl, this.resolve_expression(block)) }); + self.nested_loops -= 1; + HirStatement::For(HirForStatement { start_range, end_range, @@ -1210,12 +1228,18 @@ impl<'a> Resolver<'a> { range @ ForRange::Array(_) => { let for_stmt = range.into_for(for_loop.identifier, for_loop.block, for_loop.span); - self.resolve_stmt(for_stmt) + self.resolve_stmt(for_stmt, for_loop.span) } } } - StatementKind::Break => HirStatement::Break, - StatementKind::Continue => HirStatement::Continue, + StatementKind::Break => { + self.check_break_continue(true, span); + HirStatement::Break + } + StatementKind::Continue => { + self.check_break_continue(false, span); + HirStatement::Continue + } StatementKind::Error => HirStatement::Error, } } @@ -1262,8 +1286,8 @@ impl<'a> Resolver<'a> { Some(self.resolve_expression(assert_msg_call_expr)) } - pub fn intern_stmt(&mut self, stmt: StatementKind) -> StmtId { - let hir_stmt = self.resolve_stmt(stmt); + pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { + let hir_stmt = self.resolve_stmt(stmt.kind, stmt.span); self.interner.push_stmt(hir_stmt) } @@ -1911,7 +1935,7 @@ impl<'a> Resolver<'a> { fn resolve_block(&mut self, block_expr: BlockExpression) -> HirExpression { let statements = - self.in_new_scope(|this| vecmap(block_expr.0, |stmt| this.intern_stmt(stmt.kind))); + self.in_new_scope(|this| vecmap(block_expr.0, |stmt| this.intern_stmt(stmt))); HirExpression::Block(HirBlockExpression(statements)) } @@ -2038,6 +2062,15 @@ impl<'a> Resolver<'a> { } HirLiteral::FmtStr(str, fmt_str_idents) } + + fn check_break_continue(&mut self, is_break: bool, span: Span) { + if !self.in_unconstrained_fn { + self.push_err(ResolverError::JumpInConstrainedFn { is_break, span }); + } + if self.nested_loops == 0 { + self.push_err(ResolverError::JumpOutsideLoop { is_break, span }); + } + } } /// Gives an error if a user tries to create a mutable reference diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index b8ed6fb73d2..0bd8c2e5956 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -1226,6 +1226,34 @@ fn lambda$f1(mut env$l1: (Field)) -> Field { assert_eq!(get_program_errors(src).len(), 0); } + #[test] + fn break_and_continue_in_constrained_fn() { + let src = r#" + fn main() { + for i in 0 .. 10 { + if i == 2 { + continue; + } + if i == 5 { + break; + } + } + } + "#; + assert_eq!(get_program_errors(src).len(), 2); + } + + #[test] + fn break_and_continue_outside_loop() { + let src = r#" + unconstrained fn main() { + continue; + break; + } + "#; + assert_eq!(get_program_errors(src).len(), 2); + } + // Regression for #4545 #[test] fn type_aliases_in_main() { From d77f8d8d57473a8baaddb06f13f7861dede9784f Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 11:47:43 -0500 Subject: [PATCH 05/13] Remove new panic --- .../src/ssa/function_builder/mod.rs | 16 ++++++++-------- .../src/hir/resolution/resolver.rs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 6efc4922bdb..951be6c8429 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -32,7 +32,7 @@ use super::{ /// functions as needed, although it is limited to one function at a time. pub(crate) struct FunctionBuilder { pub(super) current_function: Function, - current_block: Option, + current_block: BasicBlockId, finished_functions: Vec, call_stack: CallStack, } @@ -51,7 +51,7 @@ impl FunctionBuilder { new_function.set_runtime(runtime); Self { - current_block: Some(new_function.entry_block()), + current_block: new_function.entry_block(), current_function: new_function, finished_functions: Vec::new(), call_stack: CallStack::new(), @@ -71,7 +71,7 @@ impl FunctionBuilder { ) { let mut new_function = Function::new(name, function_id); new_function.set_runtime(runtime_type); - self.current_block = Some(new_function.entry_block()); + self.current_block = new_function.entry_block(); let old_function = std::mem::replace(&mut self.current_function, new_function); self.finished_functions.push(old_function); @@ -165,12 +165,12 @@ impl FunctionBuilder { /// Expects the given block to be within the same function. If you want to insert /// instructions into a new function, call new_function instead. pub(crate) fn switch_to_block(&mut self, block: BasicBlockId) { - self.current_block = Some(block); + self.current_block = block; } /// Returns the block currently being inserted into pub(crate) fn current_block(&mut self) -> BasicBlockId { - self.current_block.expect("Tried to grab the current block after it was terminated") + self.current_block } /// Insert an allocate instruction at the end of the current block, allocating the @@ -310,11 +310,11 @@ impl FunctionBuilder { } /// Terminates the current block with the given terminator instruction + /// if the current block does not already have a terminator instruction. fn terminate_block_with(&mut self, terminator: TerminatorInstruction) { - if let Some(block) = self.current_block.take() { - self.current_function.dfg.set_block_terminator(block, terminator); + if self.current_function.dfg[self.current_block].terminator().is_none() { + self.current_function.dfg.set_block_terminator(self.current_block, terminator); } - self.current_block = None; } /// Terminate the current block with a jmp instruction to jmp to the given diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 092159d6ea7..90716bb958d 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -39,9 +39,9 @@ use crate::{ use crate::{ ArrayLiteral, BinaryOpKind, Distinctness, ForRange, FunctionDefinition, FunctionReturnType, Generics, ItemVisibility, LValue, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, - Shared, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind, UnaryOp, + Shared, Statement, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind, UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, Visibility, ERROR_IDENT, Statement, + UnresolvedTypeExpression, Visibility, ERROR_IDENT, }; use fm::FileId; use iter_extended::vecmap; From ee34e801fa796d9ea6c7f469bbb0f831273f858f Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 11:52:46 -0500 Subject: [PATCH 06/13] Fix frontend tests --- compiler/noirc_frontend/src/tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 0bd8c2e5956..98dbc42adcd 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -778,6 +778,8 @@ mod test { HirStatement::Semi(semi_expr) => semi_expr, HirStatement::For(for_loop) => for_loop.block, HirStatement::Error => panic!("Invalid HirStatement!"), + HirStatement::Break => panic!("Unexpected break"), + HirStatement::Continue => panic!("Unexpected continue"), }; let expr = interner.expression(&expr_id); From 33eb95fd5e9fd69f8142f279cca0e9e417ba75df Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 12:44:28 -0500 Subject: [PATCH 07/13] Add docs --- docs/docs/noir/concepts/control_flow.md | 50 +++++++++++++++++-------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/docs/docs/noir/concepts/control_flow.md b/docs/docs/noir/concepts/control_flow.md index 4ce65236db3..74997b711b2 100644 --- a/docs/docs/noir/concepts/control_flow.md +++ b/docs/docs/noir/concepts/control_flow.md @@ -7,21 +7,6 @@ keywords: [Noir programming language, loops, for loop, if-else statements, Rust sidebar_position: 2 --- -## Loops - -Noir has one kind of loop: the `for` loop. `for` loops allow you to repeat a block of code multiple -times. - -The following block of code between the braces is run 10 times. - -```rust -for i in 0..10 { - // do something -}; -``` - -The index for loops is of type `u64`. - ## If Expressions Noir supports `if-else` statements. The syntax is most similar to Rust's where it is not required @@ -43,3 +28,38 @@ if a == 0 { } assert(x == 2); ``` + +## Loops + +Noir has one kind of loop: the `for` loop. `for` loops allow you to repeat a block of code multiple +times. + +The following block of code between the braces is run 10 times. + +```rust +for i in 0..10 { + // do something +} +``` + +The index for loops is of type `u64`. + +### Break and Continue + +In unconstrained code, `break` and `continue` are also allowed in `for` 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: + +```rust +for i in 0 .. 10 { + if i == 2 { + continue; + } + if i == 5 { + break; + } + println(i); +} +``` + +`break` and `continue` cannot currently be used to jump out of more than a single loop at a time. From c4dea8ed603aecaadf0c8ed98913dd5431c66574 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 14:54:48 -0500 Subject: [PATCH 08/13] Removing tmp folder --- .../nargo_fmt/tests/expected/tmp/Nargo.toml | 7 -- .../nargo_fmt/tests/expected/tmp/src/main.nr | 79 ------------------- 2 files changed, 86 deletions(-) delete mode 100644 tooling/nargo_fmt/tests/expected/tmp/Nargo.toml delete mode 100644 tooling/nargo_fmt/tests/expected/tmp/src/main.nr diff --git a/tooling/nargo_fmt/tests/expected/tmp/Nargo.toml b/tooling/nargo_fmt/tests/expected/tmp/Nargo.toml deleted file mode 100644 index fa2f65983cb..00000000000 --- a/tooling/nargo_fmt/tests/expected/tmp/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "tmp" -type = "bin" -authors = [""] -compiler_version = ">=0.25.0" - -[dependencies] \ No newline at end of file diff --git a/tooling/nargo_fmt/tests/expected/tmp/src/main.nr b/tooling/nargo_fmt/tests/expected/tmp/src/main.nr deleted file mode 100644 index c5b19a686d2..00000000000 --- a/tooling/nargo_fmt/tests/expected/tmp/src/main.nr +++ /dev/null @@ -1,79 +0,0 @@ -// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. -// Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -// Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -// Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -contract Benchmarking { - use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; - - use dep::value_note::{utils::{increment, decrement}, value_note::{VALUE_NOTE_LEN, ValueNote, ValueNoteMethods}}; - - use dep::aztec::{ - context::Context, note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, - log::emit_unencrypted_log, state_vars::{Map, PublicMutable, PrivateSet}, - types::type_serialization::field_serialization::{FieldSerializationMethods, FIELD_SERIALIZED_LEN}, - types::address::{AztecAddress} - }; - - struct Storage { - notes: Map>, - balances: Map>, - } - - impl Storage { - fn init(context: Context) -> pub Self { - Storage { - notes: Map::new( - context, - 1, - |context, slot| { PrivateSet::new(context, slot, ValueNoteMethods) } - ), - balances: Map::new( - context, - 2, - |context, slot| { PublicMutable::new(context, slot, FieldSerializationMethods) } - ) - } - } - } - - #[aztec(private)] - fn constructor() {} - - // Nec tincidunt praesent semper feugiat nibh sed pulvinar. Nibh nisl condimentum id venenatis a. - #[aztec(private)] - fn create_note(owner: Field, value: Field) { - increment(storage.notes.at(owner), value, owner); - } - - // Diam quam nulla porttitor massa id. Elit ullamcorper dignissim cras tincidunt lobortis feugiat. - #[aztec(private)] - fn recreate_note(owner: Field, index: u32) { - let owner_notes = storage.notes.at(owner); - let getter_options = NoteGetterOptions::new().set_limit(1).set_offset(index); - let notes = owner_notes.get_notes(getter_options); - let note = notes[0].unwrap_unchecked(); - owner_notes.remove(note); - increment(owner_notes, note.value, owner); - } - - // Ultrices in iaculis nunc sed augue lacus. - #[aztec(public)] - fn increment_balance(owner: Field, value: Field) { - let current = storage.balances.at(owner).read(); - storage.balances.at(owner).write(current + value); - let _callStackItem1 = context.call_public_function( - context.this_address(), - FunctionSelector::from_signature("broadcast(Field)"), - [owner] - ); - } - - // Est ultricies integer quis auctor elit sed. In nibh mauris cursus mattis molestie a iaculis. - #[aztec(public)] - fn broadcast(owner: Field) { - emit_unencrypted_log(&mut context, storage.balances.at(owner).read()); - } -} - -// Uses the token bridge contract, which tells which input token we need to talk to and handles the exit funds to L1 -contract Uniswap {} From 161917f99943f7c719a8c30019817edf06ec3a96 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 14:58:56 -0500 Subject: [PATCH 09/13] Specify more on break and continue --- docs/docs/noir/concepts/control_flow.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/docs/noir/concepts/control_flow.md b/docs/docs/noir/concepts/control_flow.md index 74997b711b2..045d3c3a5f5 100644 --- a/docs/docs/noir/concepts/control_flow.md +++ b/docs/docs/noir/concepts/control_flow.md @@ -52,14 +52,26 @@ a loop may have. `break` and `continue` can be used like so: ```rust for i in 0 .. 10 { + println("Iteration start") + if i == 2 { continue; } + if i == 5 { break; } + println(i); } +println("Loop end") ``` +When used, `break` will end the current loop early and jump to the statement after the for loop. In the example +above, the `break` will stop the loop and jump to the `println("Loop end")`. + +`continue` will stop the current iteration of the loop, and jump to the start of the next iteration. In the example +above, `continue` will jump to `println("Iteration start")` when used. Note that the loop continues as normal after this. +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. From a440f490c57ac21e3f0bc8eebac4d0a0a5933aa0 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 15:01:49 -0500 Subject: [PATCH 10/13] Add break and continue note in unconstrained section as well --- docs/docs/noir/concepts/unconstrained.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/noir/concepts/unconstrained.md b/docs/docs/noir/concepts/unconstrained.md index 89d12c1c971..6b312ac0bac 100644 --- a/docs/docs/noir/concepts/unconstrained.md +++ b/docs/docs/noir/concepts/unconstrained.md @@ -93,3 +93,7 @@ Backend circuit size: 2902 This ends up taking off another ~250 gates from our circuit! We've ended up with more ACIR opcodes than before but they're easier for the backend to prove (resulting in fewer gates). Generally we want to use brillig whenever there's something that's easy to verify but hard to compute within the circuit. For example, if you wanted to calculate a square root of a number it'll be a much better idea to calculate this in brillig and then assert that if you square the result you get back your number. + +## Break and Continue + +In addition to loops over runtime bounds, `break` and `continue` are also available in unconstrained code. See [break and continue](../concepts/control-flow/#break-and-continue) From 53f1e6b346fe012db8495216d273745d68fc7c18 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 18 Mar 2024 15:25:03 -0500 Subject: [PATCH 11/13] Use underscore in link --- docs/docs/noir/concepts/unconstrained.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/noir/concepts/unconstrained.md b/docs/docs/noir/concepts/unconstrained.md index 6b312ac0bac..b8e71fe65f0 100644 --- a/docs/docs/noir/concepts/unconstrained.md +++ b/docs/docs/noir/concepts/unconstrained.md @@ -96,4 +96,4 @@ Generally we want to use brillig whenever there's something that's easy to verif ## Break and Continue -In addition to loops over runtime bounds, `break` and `continue` are also available in unconstrained code. See [break and continue](../concepts/control-flow/#break-and-continue) +In addition to loops over runtime bounds, `break` and `continue` are also available in unconstrained code. See [break and continue](../concepts/control_flow/#break-and-continue) From 60663eee1d158aaf909b2f9af4ce0cfe289ad078 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 19 Mar 2024 12:20:23 -0500 Subject: [PATCH 12/13] Add break and continue test --- .../break_and_continue/Nargo.toml | 7 +++++++ .../break_and_continue/src/main.nr | 15 +++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 test_programs/execution_success/break_and_continue/Nargo.toml create mode 100644 test_programs/execution_success/break_and_continue/src/main.nr diff --git a/test_programs/execution_success/break_and_continue/Nargo.toml b/test_programs/execution_success/break_and_continue/Nargo.toml new file mode 100644 index 00000000000..483602478ba --- /dev/null +++ b/test_programs/execution_success/break_and_continue/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "break_and_continue" +type = "bin" +authors = [""] +compiler_version = ">=0.24.0" + +[dependencies] diff --git a/test_programs/execution_success/break_and_continue/src/main.nr b/test_programs/execution_success/break_and_continue/src/main.nr new file mode 100644 index 00000000000..308c93c29e8 --- /dev/null +++ b/test_programs/execution_success/break_and_continue/src/main.nr @@ -0,0 +1,15 @@ +unconstrained fn main() { + let mut count = 0; + + for i in 0 .. 10 { + if i == 2 { + continue; + } + if i == 5 { + break; + } + count += 1; + } + + assert(count == 4); +} From b41cc8281ad5e1ff0528070ffea6da034d03982e Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 19 Mar 2024 12:28:10 -0500 Subject: [PATCH 13/13] Add semicolons to nargo fmt for break & continue --- .../execution_success/break_and_continue/src/main.nr | 2 +- tooling/nargo_fmt/src/visitor/stmt.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test_programs/execution_success/break_and_continue/src/main.nr b/test_programs/execution_success/break_and_continue/src/main.nr index 308c93c29e8..67dce03ac64 100644 --- a/test_programs/execution_success/break_and_continue/src/main.nr +++ b/test_programs/execution_success/break_and_continue/src/main.nr @@ -1,7 +1,7 @@ unconstrained fn main() { let mut count = 0; - for i in 0 .. 10 { + for i in 0..10 { if i == 2 { continue; } diff --git a/tooling/nargo_fmt/src/visitor/stmt.rs b/tooling/nargo_fmt/src/visitor/stmt.rs index 8cf6e02d944..ee8cc990e0e 100644 --- a/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/tooling/nargo_fmt/src/visitor/stmt.rs @@ -95,8 +95,8 @@ impl super::FmtVisitor<'_> { self.push_rewrite(self.slice(span).to_string(), span); } StatementKind::Error => unreachable!(), - StatementKind::Break => self.push_rewrite("break".into(), span), - StatementKind::Continue => self.push_rewrite("continue".into(), span), + StatementKind::Break => self.push_rewrite("break;".into(), span), + StatementKind::Continue => self.push_rewrite("continue;".into(), span), } self.last_position = span.end();