diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f23280badc56c6..0eb5fff545a1ab 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -105,7 +105,7 @@ mut: cur_orm_ts ast.TypeSymbol cur_anon_fn &ast.AnonFn = unsafe { nil } vmod_file_content string // needed for @VMOD_FILE, contents of the file, *NOT its path** - loop_label string // set when inside a labelled for loop + loop_labels []string // filled, when inside labelled for loops: `a_label: for x in 0..10 {` vweb_gen_types []ast.Type // vweb route checks timers &util.Timers = util.get_timers() comptime_info_stack []comptime.ComptimeInfo // stores the values from the above on each $for loop, to make nesting them easier @@ -183,7 +183,7 @@ fn (mut c Checker) reset_checker_state_at_start_of_new_file() { c.inside_sql = false c.cur_orm_ts = ast.TypeSymbol{} c.prevent_sum_type_unwrapping_once = false - c.loop_label = '' + c.loop_labels = [] c.using_new_err_struct = false c.inside_selector_expr = false c.inside_interface_deref = false @@ -2015,16 +2015,15 @@ fn (mut c Checker) check_enum_field_integer_literal(expr ast.IntegerLiteral, is_ } @[inline] -fn (mut c Checker) check_loop_label(label string, pos token.Pos) { +fn (mut c Checker) check_loop_labels(label string, pos token.Pos) { if label == '' { - // ignore return } - if c.loop_label.len != 0 { - c.error('nesting of labelled `for` loops is not supported', pos) + if label in c.loop_labels { + c.error('the loop label was already defined before', pos) return } - c.loop_label = label + c.loop_labels << label } fn (mut c Checker) stmt(mut node ast.Stmt) { @@ -2231,7 +2230,7 @@ fn (mut c Checker) branch_stmt(node ast.BranchStmt) { } } if node.label.len > 0 { - if node.label != c.loop_label { + if node.label !in c.loop_labels { c.error('invalid label name `${node.label}`', node.pos) } } diff --git a/vlib/v/checker/for.v b/vlib/v/checker/for.v index 9184e784b51043..aafb828ddec3a9 100644 --- a/vlib/v/checker/for.v +++ b/vlib/v/checker/for.v @@ -7,7 +7,7 @@ import v.token fn (mut c Checker) for_c_stmt(mut node ast.ForCStmt) { c.in_for_count++ - prev_loop_label := c.loop_label + prev_loop_labels := c.loop_labels if node.has_init { c.stmt(mut node.init) } @@ -22,15 +22,15 @@ fn (mut c Checker) for_c_stmt(mut node ast.ForCStmt) { } c.stmt(mut node.inc) } - c.check_loop_label(node.label, node.pos) + c.check_loop_labels(node.label, node.pos) c.stmts(mut node.stmts) - c.loop_label = prev_loop_label + c.loop_labels = prev_loop_labels c.in_for_count-- } fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { c.in_for_count++ - prev_loop_label := c.loop_label + prev_loop_labels := c.loop_labels mut typ := c.expr(mut node.cond) if node.key_var.len > 0 && node.key_var != '_' { c.check_valid_snake_case(node.key_var, 'variable name', node.pos) @@ -259,15 +259,15 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) { } } } - c.check_loop_label(node.label, node.pos) + c.check_loop_labels(node.label, node.pos) c.stmts(mut node.stmts) - c.loop_label = prev_loop_label + c.loop_labels = prev_loop_labels c.in_for_count-- } fn (mut c Checker) for_stmt(mut node ast.ForStmt) { c.in_for_count++ - prev_loop_label := c.loop_label + prev_loop_labels := c.loop_labels c.expected_type = ast.bool_type if node.cond !is ast.EmptyExpr { typ := c.expr(mut node.cond) @@ -288,9 +288,9 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) { } // TODO: update loop var type // how does this work currently? - c.check_loop_label(node.label, node.pos) + c.check_loop_labels(node.label, node.pos) c.stmts(mut node.stmts) - c.loop_label = prev_loop_label + c.loop_labels = prev_loop_labels c.in_for_count-- if c.smartcast_mut_pos != token.Pos{} { c.smartcast_mut_pos = token.Pos{} diff --git a/vlib/v/checker/tests/labelled_break_continue.out b/vlib/v/checker/tests/labelled_break_continue.out index caadcfc45063e4..489f447f89ce58 100644 --- a/vlib/v/checker/tests/labelled_break_continue.out +++ b/vlib/v/checker/tests/labelled_break_continue.out @@ -26,10 +26,3 @@ vlib/v/checker/tests/labelled_break_continue.vv:16:10: error: invalid label name | ~~~~~ 17 | } 18 | } -vlib/v/checker/tests/labelled_break_continue.vv:21:11: error: nesting of labelled `for` loops is not supported - 19 | // check nested loops (not supported ATM) - 20 | L3: for ;; i++ { - 21 | L4: for { - | ^ - 22 | if i < 17 {continue L3} - 23 | else {break L3} diff --git a/vlib/v/tests/nested_fors_with_labels_test.v b/vlib/v/tests/nested_fors_with_labels_test.v new file mode 100644 index 00000000000000..b3d523be53e99e --- /dev/null +++ b/vlib/v/tests/nested_fors_with_labels_test.v @@ -0,0 +1,91 @@ +const good = [ + '> x: 0 | y: 0 | z: 0', + '> x: 0 | y: 0 | z: 1', + '> x: 0 | y: 1 | z: 0', + '> x: 0 | y: 1 | z: 1', + '> x: 1 | y: 0 | z: 0', + '> x: 1 | y: 0 | z: 1', + '> x: 1 | y: 1 | z: 0', + '> x: 1 | y: 1 | z: 1', +] + +fn test_labeled_nested_loops_for_in() { + mut values := []string{} + abc: for x in 0 .. 2 { + def: for y in 0 .. 5 { + if y > 1 { + continue abc + } + xyz: for z in 0 .. 10 { + if z > 1 { + continue def + } + values << '> x: ${x} | y: ${y} | z: ${z}' + } + } + } + assert values == good +} + +fn test_labeled_nested_loops_for_c_style() { + mut values := []string{} + abc: for x := 0; x < 2; x++ { + def: for y := 0; y < 5; y++ { + if y > 1 { + continue abc + } + xyz: for z := 0; z < 10; z++ { + if z > 1 { + continue def + } + values << '> x: ${x} | y: ${y} | z: ${z}' + } + } + } + assert values == good +} + +fn test_labeled_nested_loops_for_in_array() { + mut values := []string{} + x_array := [0, 1] + y_array := [0, 1, 2, 3, 4] + z_array := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + abc: for x in x_array { + def: for y in y_array { + if y > 1 { + continue abc + } + xyz: for z in y_array { + if z > 1 { + continue def + } + values << '> x: ${x} | y: ${y} | z: ${z}' + } + } + } + assert values == good +} + +fn test_labeled_nested_loops_for_condition() { + mut values := []string{} + mut x := -1 + abc: for x < 1 { + x++ + mut y := -1 + def: for y < 5 { + y++ + if y > 1 { + continue abc + } + mut z := -1 + xyz: for z < 10 { + z++ + if z > 1 { + continue def + } + values << '> x: ${x} | y: ${y} | z: ${z}' + } + } + } + assert values == good +}