From 83e7e82ff8a21aca3899ac09c2e1e9374f888379 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 5 Nov 2025 19:02:50 +0000 Subject: [PATCH 1/3] more tests to try and break loop semantics --- .../src/elaborator/expressions.rs | 7 +- .../src/elaborator/statements.rs | 4 +- .../noirc_frontend/src/tests/control_flow.rs | 142 ++++++++++++++++++ 3 files changed, 146 insertions(+), 7 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index adac10f548e..594247a6aab 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -225,10 +225,8 @@ impl Elaborator<'_> { } } else if is_break_or_continue { break_or_continue_location = Some(location); - } - - if i + 1 == statements.len() { - block_type = if is_break_or_continue { Type::Unit } else { stmt_type }; + } else if i + 1 == statements.len() { + block_type = stmt_type; } } @@ -1222,6 +1220,7 @@ impl Elaborator<'_> { let (alternative, else_type, error_location) = if let Some(alternative) = if_expr.alternative { + dbg!(&alternative); let alternative_location = alternative.type_location(); let (else_, else_type) = self.elaborate_expression_with_target_type(alternative, target_type); diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 550f71fd115..271fba8400f 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -210,8 +210,6 @@ impl Elaborator<'_> { self.current_loop = Some(Loop { is_for: true, has_break: false }); self.push_scope(); - // TODO: For loop variables are currently mutable by default since we haven't - // yet implemented syntax for them to be optionally mutable. let kind = DefinitionKind::Local(None); let identifier = self.add_variable_decl( identifier, false, // mutable @@ -346,7 +344,7 @@ impl Elaborator<'_> { } let expr = if is_break { HirStatement::Break } else { HirStatement::Continue }; - (expr, self.interner.next_type_variable()) + (expr, Type::Unit) } fn get_lvalue_error_info(&self, lvalue: &HirLValue) -> (DefinitionId, String, Location) { diff --git a/compiler/noirc_frontend/src/tests/control_flow.rs b/compiler/noirc_frontend/src/tests/control_flow.rs index 00642d38fae..6ab629000d3 100644 --- a/compiler/noirc_frontend/src/tests/control_flow.rs +++ b/compiler/noirc_frontend/src/tests/control_flow.rs @@ -24,6 +24,19 @@ fn resolve_for_expr_incl() { assert_no_errors(src); } +#[test] +fn for_loop_mutate_induction_var() { + let src = r#" + fn main() { + for i in 0..10 { + i = 5; + ^ Variable `i` must be mutable to be assigned to + } + } + "#; + check_errors(src); +} + #[test] fn break_and_continue_outside_loop() { let src = r#" @@ -138,6 +151,56 @@ fn errors_on_loop_without_break_with_nested_loop() { check_errors(src); } +#[test] +fn break_in_nested_and_outer_loops() { + let src = r#" + unconstrained fn main() { + let mut x = 1; + loop { + x += 1; + loop { + x += 2; + break; // Breaks from nested loop only + } + if x > 2 { + break; // Breaks from outer loop + } + } + } + "#; + assert_no_errors(src); +} + +#[test] +fn continue_in_loop() { + let src = r#" + unconstrained fn main() { + let mut x = 0; + loop { + x += 1; + if x < 5 { + continue; + } + break; + } + + for i in 0..10 { + if i == 5 { + continue; + } + } + + while x > 0 { + x -= 1; + if x == 3 { + continue; + } + } + } + "#; + assert_no_errors(src); +} + #[test] fn errors_if_for_body_type_is_not_unit() { let src = r#" @@ -190,3 +253,82 @@ fn overflowing_int_in_for_loop() { "#; check_errors(src); } + +#[test] +fn break_type_mismatch() { + let src = r#" + unconstrained fn main() { + loop { + if true { + break; + } else { + 5 + ^ Expected type (), found type Field + }; + } + } + "#; + check_errors(src); +} + +#[test] +fn continue_type_mismatch() { + let src = r#" + unconstrained fn main() { + for _ in 0..1 { + if true { + continue; + } else { + 5 + ^ Expected type (), found type Field + } + } + } + "#; + check_errors(src); +} + +// Empty and Edge Case Range Tests + +#[test] +fn for_loop_empty_range() { + let src = r#" + fn main() { + let mut x = 0; + for _i in 0..0 { + x = 1; + } + assert(x == 0); + } + "#; + assert_no_errors(src); +} + +#[test] +fn for_loop_backwards_range() { + let src = r#" + fn main() { + let mut x = 0; + for _i in 10..5 { + x = 1; + } + assert(x == 0); + } + "#; + assert_no_errors(src); +} + +#[test] +fn for_loop_single_elem_inclusive_max_value() { + let src = r#" + fn main() { + let mut count = 0; + for i in 4294967295..=4294967295 { + count += 1; + let _x: u32 = i; + } + assert(count == 1); + } + "#; + assert_no_errors(src); +} From 07bddac7cce7e16225d847219cb1fc76a5f85d11 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 5 Nov 2025 19:07:13 +0000 Subject: [PATCH 2/3] cleanup --- .../src/elaborator/expressions.rs | 4 +- .../noirc_frontend/src/tests/control_flow.rs | 86 +++++++++---------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 594247a6aab..f2fe926280a 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -225,7 +225,9 @@ impl Elaborator<'_> { } } else if is_break_or_continue { break_or_continue_location = Some(location); - } else if i + 1 == statements.len() { + } + + if i + 1 == statements.len() { block_type = stmt_type; } } diff --git a/compiler/noirc_frontend/src/tests/control_flow.rs b/compiler/noirc_frontend/src/tests/control_flow.rs index 6ab629000d3..d76233edaad 100644 --- a/compiler/noirc_frontend/src/tests/control_flow.rs +++ b/compiler/noirc_frontend/src/tests/control_flow.rs @@ -24,6 +24,49 @@ fn resolve_for_expr_incl() { assert_no_errors(src); } +#[test] +fn for_loop_empty_range() { + let src = r#" + fn main() { + let mut x = 0; + for _i in 0..0 { + x = 1; + } + assert(x == 0); + } + "#; + assert_no_errors(src); +} + +#[test] +fn for_loop_backwards_range() { + let src = r#" + fn main() { + let mut x = 0; + for _i in 10..5 { + x = 1; + } + assert(x == 0); + } + "#; + assert_no_errors(src); +} + +#[test] +fn for_loop_single_elem_inclusive_max_value() { + let src = r#" + fn main() { + let mut count = 0; + for i in 4294967295..=4294967295 { + count += 1; + let _x: u32 = i; + } + assert(count == 1); + } + "#; + assert_no_errors(src); +} + #[test] fn for_loop_mutate_induction_var() { let src = r#" @@ -288,47 +331,4 @@ fn continue_type_mismatch() { check_errors(src); } -// Empty and Edge Case Range Tests -#[test] -fn for_loop_empty_range() { - let src = r#" - fn main() { - let mut x = 0; - for _i in 0..0 { - x = 1; - } - assert(x == 0); - } - "#; - assert_no_errors(src); -} - -#[test] -fn for_loop_backwards_range() { - let src = r#" - fn main() { - let mut x = 0; - for _i in 10..5 { - x = 1; - } - assert(x == 0); - } - "#; - assert_no_errors(src); -} - -#[test] -fn for_loop_single_elem_inclusive_max_value() { - let src = r#" - fn main() { - let mut count = 0; - for i in 4294967295..=4294967295 { - count += 1; - let _x: u32 = i; - } - assert(count == 1); - } - "#; - assert_no_errors(src); -} From ba990c3fdee50853f1c6a467384774cfe981ae09 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 5 Nov 2025 19:20:34 +0000 Subject: [PATCH 3/3] cleanup --- compiler/noirc_frontend/src/elaborator/expressions.rs | 5 ++--- compiler/noirc_frontend/src/tests/control_flow.rs | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index f2fe926280a..dc9955e0465 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -225,8 +225,8 @@ impl Elaborator<'_> { } } else if is_break_or_continue { break_or_continue_location = Some(location); - } - + } + if i + 1 == statements.len() { block_type = stmt_type; } @@ -1222,7 +1222,6 @@ impl Elaborator<'_> { let (alternative, else_type, error_location) = if let Some(alternative) = if_expr.alternative { - dbg!(&alternative); let alternative_location = alternative.type_location(); let (else_, else_type) = self.elaborate_expression_with_target_type(alternative, target_type); diff --git a/compiler/noirc_frontend/src/tests/control_flow.rs b/compiler/noirc_frontend/src/tests/control_flow.rs index d76233edaad..803352f5d34 100644 --- a/compiler/noirc_frontend/src/tests/control_flow.rs +++ b/compiler/noirc_frontend/src/tests/control_flow.rs @@ -330,5 +330,3 @@ fn continue_type_mismatch() { "#; check_errors(src); } - -