Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub enum StatementKind {
Expression(Expression),
Assign(AssignStatement),
For(ForLoopStatement),
Loop(Expression),
Break,
Continue,
/// This statement should be executed at compile-time
Expand Down Expand Up @@ -106,6 +107,9 @@ impl StatementKind {
// A semicolon on a for loop is optional and does nothing
StatementKind::For(_) => self,

// A semicolon on a loop is optional and does nothing
StatementKind::Loop(..) => self,

// No semicolon needed for a resolved statement
StatementKind::Interned(_) => self,

Expand Down Expand Up @@ -961,6 +965,7 @@ 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::Loop(block) => write!(f, "loop {}", block),
StatementKind::Break => write!(f, "break"),
StatementKind::Continue => write!(f, "continue"),
StatementKind::Comptime(statement) => write!(f, "comptime {}", statement.kind),
Expand Down
9 changes: 9 additions & 0 deletions compiler/noirc_frontend/src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ pub trait Visitor {
true
}

fn visit_loop_statement(&mut self, _: &Expression) -> bool {
true
}

fn visit_comptime_statement(&mut self, _: &Statement) -> bool {
true
}
Expand Down Expand Up @@ -1104,6 +1108,11 @@ impl Statement {
StatementKind::For(for_loop_statement) => {
for_loop_statement.accept(visitor);
}
StatementKind::Loop(block) => {
if visitor.visit_loop_statement(block) {
block.accept(visitor);
}
}
StatementKind::Comptime(statement) => {
if visitor.visit_comptime_statement(statement) {
statement.accept(visitor);
Expand Down
10 changes: 10 additions & 0 deletions compiler/noirc_frontend/src/elaborator/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl<'context> Elaborator<'context> {
StatementKind::Constrain(constrain) => self.elaborate_constrain(constrain),
StatementKind::Assign(assign) => self.elaborate_assign(assign),
StatementKind::For(for_stmt) => self.elaborate_for(for_stmt),
StatementKind::Loop(block) => self.elaborate_loop(block, statement.span),
StatementKind::Break => self.elaborate_jump(true, statement.span),
StatementKind::Continue => self.elaborate_jump(false, statement.span),
StatementKind::Comptime(statement) => self.elaborate_comptime_statement(*statement),
Expand Down Expand Up @@ -268,6 +269,15 @@ impl<'context> Elaborator<'context> {
(statement, Type::Unit)
}

pub(super) fn elaborate_loop(
&mut self,
_block: Expression,
span: noirc_errors::Span,
) -> (HirStatement, Type) {
self.push_err(ResolverError::LoopNotYetSupported { span });
(HirStatement::Error, Type::Unit)
}

fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) {
let in_constrained_function = self.in_constrained_function();

Expand Down
3 changes: 3 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,9 @@ fn remove_interned_in_statement_kind(
block: remove_interned_in_expression(interner, for_loop.block),
..for_loop
}),
StatementKind::Loop(block) => {
StatementKind::Loop(remove_interned_in_expression(interner, block))
}
StatementKind::Comptime(statement) => {
StatementKind::Comptime(Box::new(remove_interned_in_statement(interner, *statement)))
}
Expand Down
9 changes: 9 additions & 0 deletions compiler/noirc_frontend/src/hir/resolution/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ pub enum ResolverError {
span: Span,
missing_trait_location: Location,
},
#[error("`loop` statements are not yet implemented")]
LoopNotYetSupported { span: Span },
}

impl ResolverError {
Expand Down Expand Up @@ -642,6 +644,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
diagnostic.add_secondary_with_file(format!("required by this bound in `{impl_trait}"), missing_trait_location.span, missing_trait_location.file);
diagnostic
},
ResolverError::LoopNotYetSupported { span } => {
Diagnostic::simple_error(
"`loop` statements are not yet implemented".to_string(),
String::new(),
*span)

}
}
}
}
3 changes: 3 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ pub enum Keyword {
Impl,
In,
Let,
Loop,
Match,
Mod,
Module,
Expand Down Expand Up @@ -1090,6 +1091,7 @@ impl fmt::Display for Keyword {
Keyword::Impl => write!(f, "impl"),
Keyword::In => write!(f, "in"),
Keyword::Let => write!(f, "let"),
Keyword::Loop => write!(f, "loop"),
Keyword::Match => write!(f, "match"),
Keyword::Mod => write!(f, "mod"),
Keyword::Module => write!(f, "Module"),
Expand Down Expand Up @@ -1152,6 +1154,7 @@ impl Keyword {
"impl" => Keyword::Impl,
"in" => Keyword::In,
"let" => Keyword::Let,
"loop" => Keyword::Loop,
"match" => Keyword::Match,
"mod" => Keyword::Mod,
"Module" => Keyword::Module,
Expand Down
51 changes: 51 additions & 0 deletions compiler/noirc_frontend/src/parser/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ impl<'a> Parser<'a> {
/// | ConstrainStatement
/// | ComptimeStatement
/// | ForStatement
/// | LoopStatement
/// | IfStatement
/// | BlockStatement
/// | AssignStatement
Expand Down Expand Up @@ -156,6 +157,10 @@ impl<'a> Parser<'a> {
return Some(StatementKind::For(for_loop));
}

if let Some(block) = self.parse_loop() {
return Some(StatementKind::Loop(block));
}

if let Some(kind) = self.parse_if_expr() {
return Some(StatementKind::Expression(Expression {
kind,
Expand Down Expand Up @@ -287,6 +292,26 @@ impl<'a> Parser<'a> {
Some(ForLoopStatement { identifier, range, block, span: self.span_since(start_span) })
}

/// LoopStatement = 'loop' Block
fn parse_loop(&mut self) -> Option<Expression> {
if !self.eat_keyword(Keyword::Loop) {
return None;
}

let block_start_span = self.current_token_span;
let block = if let Some(block) = self.parse_block() {
Expression {
kind: ExpressionKind::Block(block),
span: self.span_since(block_start_span),
}
} else {
self.expected_token(Token::LeftBrace);
Expression { kind: ExpressionKind::Error, span: self.span_since(block_start_span) }
};

Some(block)
}

/// ForRange
/// = ExpressionExceptConstructor
/// | ExpressionExceptConstructor '..' ExpressionExceptConstructor
Expand Down Expand Up @@ -790,4 +815,30 @@ mod tests {
assert!(statement.is_none());
assert_eq!(parser.errors.len(), 2);
}

#[test]
fn parses_empty_loop() {
let src = "loop { }";
let statement = parse_statement_no_errors(src);
let StatementKind::Loop(block) = statement.kind else {
panic!("Expected loop");
};
let ExpressionKind::Block(block) = block.kind else {
panic!("Expected block");
};
assert!(block.statements.is_empty());
}

#[test]
fn parses_loop_with_statements() {
let src = "loop { 1; 2 }";
let statement = parse_statement_no_errors(src);
let StatementKind::Loop(block) = statement.kind else {
panic!("Expected loop");
};
let ExpressionKind::Block(block) = block.kind else {
panic!("Expected block");
};
assert_eq!(block.statements.len(), 2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ comptime fn foo<let N: Field>(x: Field) {
break;
}

let loop = quote { for _ in 0..0 { break; } };
unquote!(loop);
let loop_ = quote { for _ in 0..0 { break; } };
unquote!(loop_);
}

mod submodule {
Expand Down
8 changes: 4 additions & 4 deletions test_programs/execution_success/hint_black_box/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::hint::black_box;

fn main(a: u32, b: u32) {
// This version unrolls into a number of additions
assert_eq(loop(5, a), b);
assert_eq(loop_(5, a), b);
// This version simplifies into a single `constraint 50 == b`
assert_eq(loop(5, 10), b);
assert_eq(loop_(5, 10), b);
// This version should not simplify down to a single constraint,
// it should treat 10 as opaque:
assert_eq(loop(5, black_box(10)), b);
assert_eq(loop_(5, black_box(10)), b);

// Check array handling.
let arr = [a, a, a, a, a];
Expand Down Expand Up @@ -54,7 +54,7 @@ fn main(a: u32, b: u32) {
//assert_eq(c, b);
}

fn loop(n: u32, k: u32) -> u32 {
fn loop_(n: u32, k: u32) -> u32 {
let mut sum = 0;
for _ in 0..n {
sum = sum + k;
Expand Down
4 changes: 2 additions & 2 deletions test_programs/execution_success/loop/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
//
// The features being tested is basic looping.
fn main(six_as_u32: u32) {
assert_eq(loop(4), six_as_u32);
assert_eq(loop_excl(4), six_as_u32);
assert_eq(loop_incl(3), six_as_u32);
assert(plain_loop() == six_as_u32);
}

fn loop(x: u32) -> u32 {
fn loop_excl(x: u32) -> u32 {
let mut sum = 0;
for i in 0..x {
sum = sum + i;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Tests a simple loop where we expect loop invariant instructions
// to be hoisted to the loop's pre-header block.
fn main(x: u32, y: u32) {
loop(4, x, y);
loop_(4, x, y);
array_read_loop(4, x);
}

fn loop(upper_bound: u32, x: u32, y: u32) {
fn loop_(upper_bound: u32, x: u32, y: u32) {
for _ in 0..upper_bound {
let mut z = x * y;
z = z * x;
Expand Down
2 changes: 2 additions & 0 deletions tooling/lsp/src/requests/completion/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> {
| Keyword::Impl
| Keyword::In
| Keyword::Let
| Keyword::Loop
| Keyword::Match
| Keyword::Mod
| Keyword::Mut
Expand Down Expand Up @@ -257,6 +258,7 @@ pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option<BuiltInFunct
| Keyword::Impl
| Keyword::In
| Keyword::Let
| Keyword::Loop
| Keyword::Match
| Keyword::Mod
| Keyword::Module
Expand Down
54 changes: 54 additions & 0 deletions tooling/nargo_fmt/src/formatter/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ impl<'a, 'b> ChunkFormatter<'a, 'b> {
StatementKind::For(for_loop_statement) => {
group.group(self.format_for_loop(for_loop_statement));
}
StatementKind::Loop(block) => {
group.group(self.format_loop(block));
}
StatementKind::Break => {
group.text(self.chunk(|formatter| {
formatter.write_keyword(Keyword::Break);
Expand Down Expand Up @@ -277,6 +280,34 @@ impl<'a, 'b> ChunkFormatter<'a, 'b> {
group
}

fn format_loop(&mut self, block: Expression) -> ChunkGroup {
let mut group = ChunkGroup::new();

group.text(self.chunk(|formatter| {
formatter.write_keyword(Keyword::Loop);
}));

group.space(self);

let ExpressionKind::Block(block) = block.kind else {
panic!("Expected a block expression for loop body");
};

group.group(self.format_block_expression(
block, true, // force multiple lines
));

// If there's a trailing semicolon, remove it
group.text(self.chunk(|formatter| {
formatter.skip_whitespace_if_it_is_not_a_newline();
if formatter.is_at(Token::Semicolon) {
formatter.bump();
}
}));

group
}

fn format_comptime_statement(&mut self, statement: Statement) -> ChunkGroup {
let mut group = ChunkGroup::new();

Expand Down Expand Up @@ -749,4 +780,27 @@ mod tests {
let expected = src;
assert_format_with_max_width(src, expected, " let x = 123456;".len());
}

#[test]
fn format_empty_loop() {
let src = " fn foo() { loop { } } ";
let expected = "fn foo() {
loop {}
}
";
assert_format(src, expected);
}

#[test]
fn format_non_empty_loop() {
let src = " fn foo() { loop { 1 ; 2 } } ";
let expected = "fn foo() {
loop {
1;
2
}
}
";
assert_format(src, expected);
}
}