diff --git a/third_party/move/move-compiler-v2/src/bytecode_generator.rs b/third_party/move/move-compiler-v2/src/bytecode_generator.rs index 16248e056d369..035daf7f1c6ba 100644 --- a/third_party/move/move-compiler-v2/src/bytecode_generator.rs +++ b/third_party/move/move-compiler-v2/src/bytecode_generator.rs @@ -447,11 +447,11 @@ impl<'env> Generator<'env> { self.emit_with(*id, |attr| Bytecode::Jump(attr, continue_label)); self.emit_with(*id, |attr| Bytecode::Label(attr, break_label)); }, - ExpData::LoopCont(id, 0, do_continue) => { + ExpData::LoopCont(id, nest, do_continue) => { if let Some(LoopContext { continue_label, break_label, - }) = self.loops.last() + }) = self.loops.iter().rev().nth(*nest) { let target = if *do_continue { *continue_label @@ -463,9 +463,6 @@ impl<'env> Generator<'env> { self.error(*id, "missing enclosing loop statement") } }, - ExpData::LoopCont(_, _, _) => { - unimplemented!("continue/break with nesting") - }, ExpData::SpecBlock(id, spec) => { // Map locals in spec to assigned temporaries. let mut replacer = |id, target| { diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/loop_labels.exp b/third_party/move/move-compiler-v2/tests/bytecode-generator/loop_labels.exp new file mode 100644 index 0000000000000..247f1e144dfdb --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/loop_labels.exp @@ -0,0 +1,78 @@ +// -- Model dump before bytecode pipeline +module 0x815::test { + private fun f1() { + loop { + loop { + loop { + if true { + loop { + if false { + continue[3] + } else { + break[1] + }; + break + } + } else { + continue[2] + } + } + }; + break + } + } +} // end 0x815::test + +// -- Sourcified model before bytecode pipeline +module 0x815::test { + fun f1() { + 'l0: loop { + loop 'l1: loop if (true) loop { + if (false) continue 'l0 else break 'l1; + break + } else continue 'l0; + break + } + } +} + +============ initial bytecode ================ + +[variant baseline] +fun test::f1() { + var $t0: bool + var $t1: bool + 0: label L0 + 1: label L2 + 2: label L4 + 3: $t0 := true + 4: if ($t0) goto 5 else goto 19 + 5: label L6 + 6: label L9 + 7: $t1 := false + 8: if ($t1) goto 9 else goto 12 + 9: label L11 + 10: goto 0 + 11: goto 14 + 12: label L12 + 13: goto 23 + 14: label L13 + 15: goto 17 + 16: goto 6 + 17: label L10 + 18: goto 21 + 19: label L7 + 20: goto 0 + 21: label L8 + 22: goto 2 + 23: label L5 + 24: goto 1 + 25: label L3 + 26: goto 28 + 27: goto 0 + 28: label L1 + 29: return () +} + + +============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/tests/bytecode-generator/loop_labels.move b/third_party/move/move-compiler-v2/tests/bytecode-generator/loop_labels.move new file mode 100644 index 0000000000000..e91763e82ae00 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/bytecode-generator/loop_labels.move @@ -0,0 +1,14 @@ +module 0x815::test { + fun f1() { + 'outer: loop { + // unlabeled loop, but counts in nesting in AST + loop { + 'inner: loop if (true) loop { + if (false) continue 'outer else break 'inner; + break + } else continue 'outer + }; + break + } + } +} diff --git a/third_party/move/move-compiler-v2/tests/checking-lang-v1/loop_labels.exp b/third_party/move/move-compiler-v2/tests/checking-lang-v1/loop_labels.exp new file mode 100644 index 0000000000000..e7af6bf3d09a0 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking-lang-v1/loop_labels.exp @@ -0,0 +1,31 @@ + +Diagnostics: +error: unsupported language construct + ┌─ tests/checking-lang-v1/loop_labels.move:3:9 + │ +3 │ 'outer: loop { + │ ^^^^^^ Move language construct `'outer` is not enabled until version 2.1 + +error: unsupported language construct + ┌─ tests/checking-lang-v1/loop_labels.move:6:17 + │ +6 │ 'inner: loop if (true) loop { + │ ^^^^^^ Move language construct `'inner` is not enabled until version 2.1 + +error: unsupported language construct + ┌─ tests/checking-lang-v1/loop_labels.move:7:41 + │ +7 │ if (false) continue 'outer else break 'inner; + │ ^^^^^^ Move language construct `'outer` is not enabled until version 2.1 + +error: unsupported language construct + ┌─ tests/checking-lang-v1/loop_labels.move:7:59 + │ +7 │ if (false) continue 'outer else break 'inner; + │ ^^^^^^ Move language construct `'inner` is not enabled until version 2.1 + +error: unsupported language construct + ┌─ tests/checking-lang-v1/loop_labels.move:9:33 + │ +9 │ } else continue 'outer + │ ^^^^^^ Move language construct `'outer` is not enabled until version 2.1 diff --git a/third_party/move/move-compiler-v2/tests/checking-lang-v1/loop_labels.move b/third_party/move/move-compiler-v2/tests/checking-lang-v1/loop_labels.move new file mode 100644 index 0000000000000..e91763e82ae00 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking-lang-v1/loop_labels.move @@ -0,0 +1,14 @@ +module 0x815::test { + fun f1() { + 'outer: loop { + // unlabeled loop, but counts in nesting in AST + loop { + 'inner: loop if (true) loop { + if (false) continue 'outer else break 'inner; + break + } else continue 'outer + }; + break + } + } +} diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_err.exp b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_err.exp new file mode 100644 index 0000000000000..fd0b9ddf3ef67 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_err.exp @@ -0,0 +1,21 @@ + +Diagnostics: +error: label `'outer` undefined + ┌─ tests/checking/control_flow/loop_labels_check_err.move:3:15 + │ +3 │ break 'outer; + │ ^^^^^^ + +error: label `'inner` undefined + ┌─ tests/checking/control_flow/loop_labels_check_err.move:5:19 + │ +5 │ break 'inner + │ ^^^^^^ + +error: label `'l1` already used by outer loop + ┌─ tests/checking/control_flow/loop_labels_check_err.move:11:19 + │ +11 │ 'l1: loop 'l1: loop {}; + │ --- ^^^ + │ │ + │ outer definition of label diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_err.move b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_err.move new file mode 100644 index 0000000000000..6a1a1f616934e --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_err.move @@ -0,0 +1,14 @@ +module 0x815::test { + fun undefined_label() { + break 'outer; + 'outer: loop { + break 'inner + } + } + + fun duplicate_label() { + 'l1: loop {}; + 'l1: loop 'l1: loop {}; + 'l1: loop {} + } +} diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_ok.exp b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_ok.exp new file mode 100644 index 0000000000000..3bf1a2816123f --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_ok.exp @@ -0,0 +1,37 @@ +// -- Model dump before bytecode pipeline +module 0x815::test { + private fun f1() { + loop { + loop { + loop { + if true { + loop { + if false { + continue[3] + } else { + break[1] + }; + break + } + } else { + continue[2] + } + } + }; + break + } + } +} // end 0x815::test + +// -- Sourcified model before bytecode pipeline +module 0x815::test { + fun f1() { + 'l0: loop { + loop 'l1: loop if (true) loop { + if (false) continue 'l0 else break 'l1; + break + } else continue 'l0; + break + } + } +} diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_ok.move b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_ok.move new file mode 100644 index 0000000000000..e91763e82ae00 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_check_ok.move @@ -0,0 +1,14 @@ +module 0x815::test { + fun f1() { + 'outer: loop { + // unlabeled loop, but counts in nesting in AST + loop { + 'inner: loop if (true) loop { + if (false) continue 'outer else break 'inner; + break + } else continue 'outer + }; + break + } + } +} diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err1.exp b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err1.exp new file mode 100644 index 0000000000000..7cb036dbc78d3 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err1.exp @@ -0,0 +1,10 @@ + +Diagnostics: +error: unexpected token + ┌─ tests/checking/control_flow/loop_labels_parse_err1.move:3:13 + │ +3 │ 'a: if (true) false else true + │ ^^ + │ │ + │ Unexpected 'if' + │ Expected one of: 'while' or 'loop' diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err1.move b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err1.move new file mode 100644 index 0000000000000..319268dd01537 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err1.move @@ -0,0 +1,5 @@ +module 0x815::test { + fun f1(): bool { + 'a: if (true) false else true + } +} diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err2.exp b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err2.exp new file mode 100644 index 0000000000000..fb01360a4250c --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err2.exp @@ -0,0 +1,10 @@ + +Diagnostics: +error: unexpected token + ┌─ tests/checking/control_flow/loop_labels_parse_err2.move:3:13 + │ +3 │ 'a: if (true) false else true + │ ^^ + │ │ + │ Unexpected 'if' + │ Expected one of: 'while' or 'loop' diff --git a/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err2.move b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err2.move new file mode 100644 index 0000000000000..319268dd01537 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/checking/control_flow/loop_labels_parse_err2.move @@ -0,0 +1,5 @@ +module 0x815::test { + fun f1(): bool { + 'a: if (true) false else true + } +} diff --git a/third_party/move/move-compiler-v2/tests/testsuite.rs b/third_party/move/move-compiler-v2/tests/testsuite.rs index ba34279b86a38..6c5c577cd16e8 100644 --- a/third_party/move/move-compiler-v2/tests/testsuite.rs +++ b/third_party/move/move-compiler-v2/tests/testsuite.rs @@ -106,7 +106,7 @@ const TEST_CONFIGS: Lazy> = Lazy::new(|| { // Turn optimization on by default. Some configs below may turn it off. .set_experiment(Experiment::OPTIMIZE, true) .set_experiment(Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS, true) - .set_language_version(LanguageVersion::V2_0); + .set_language_version(LanguageVersion::V2_1); opts.testing = true; let configs = vec![ // --- Tests for checking and ast processing @@ -723,7 +723,7 @@ const TEST_CONFIGS: Lazy> = Lazy::new(|| { include: vec!["/op-equal/"], exclude: vec![], exp_suffix: None, - options: opts.clone().set_language_version(LanguageVersion::V2_1), + options: opts.clone(), // Run the entire compiler pipeline to double-check the result stop_after: StopAfter::FileFormat, dump_ast: DumpLevel::EndStage, diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/loop_labels.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/loop_labels.exp new file mode 100644 index 0000000000000..6cd67db3f6472 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/loop_labels.exp @@ -0,0 +1 @@ +processed 1 task diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/loop_labels.move b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/loop_labels.move new file mode 100644 index 0000000000000..b1c0cf1f69c32 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/loop_labels.move @@ -0,0 +1,18 @@ +//# run +script { + fun main() { + let result = 0; + 'outer: while (result < 100) { + while (result < 50) { + 'inner: while (result < 30) { + result += 1; + continue 'outer + }; + result += 10; + continue 'outer + }; + result += 20 + }; + assert!(result == 110); + } +} diff --git a/third_party/move/move-compiler/src/expansion/ast.rs b/third_party/move/move-compiler/src/expansion/ast.rs index 880234d6fc715..47db86f2654ba 100644 --- a/third_party/move/move-compiler/src/expansion/ast.rs +++ b/third_party/move/move-compiler/src/expansion/ast.rs @@ -5,7 +5,7 @@ use crate::{ expansion::translate::is_valid_struct_constant_or_schema_name, parser::ast::{ - self as P, Ability, Ability_, BinOp, CallKind, ConstantName, Field, FunctionName, + self as P, Ability, Ability_, BinOp, CallKind, ConstantName, Field, FunctionName, Label, ModuleName, QuantKind, SpecApplyPattern, StructName, UnaryOp, UseDecl, Var, VariantName, ENTRY_MODIFIER, }, @@ -25,7 +25,6 @@ use std::{ fmt, hash::Hash, }; - //************************************************************************************************** // Program //************************************************************************************************** @@ -504,8 +503,8 @@ pub enum Exp_ { IfElse(Box, Box, Box), Match(Box, Vec, Exp)>>), - While(Box, Box), - Loop(Box), + While(Option