diff --git a/src/protocol/tests/parser_validation.rs b/src/protocol/tests/parser_validation.rs index 0462c7e1e7f81070d022ab9fa36ad21f66d6203c..f6cb386849c640aa79f36a3f3d96477e9161917b 100644 --- a/src/protocol/tests/parser_validation.rs +++ b/src/protocol/tests/parser_validation.rs @@ -4,6 +4,8 @@ use super::*; + + #[test] fn test_correct_struct_instance() { Tester::new_single_source_expect_ok( @@ -528,4 +530,254 @@ fn test_polymorph_array_types() { ).for_struct("Bar", |s| { s .for_field("inputs", |f| { f.assert_parser_type("in[]"); }); }); +} + +#[test] +fn test_correct_modifying_operators() { + // Not testing the types, just that it parses + Tester::new_single_source_expect_ok( + "valid uses", + " + func f() -> u32 { + auto a = 5; + a += 2; a -= 2; a *= 2; a /= 2; a %= 2; + a <<= 2; a >>= 2; + a |= 2; a &= 2; a ^= 2; + return a; + } + " + ); +} + +#[test] +fn test_incorrect_modifying_operators() { + Tester::new_single_source_expect_err( + "wrong declaration", + "func f() -> u8 { auto a += 2; return a; }" + ).error(|e| { e.assert_msg_has(0, "expected '='"); }); + + Tester::new_single_source_expect_err( + "inside function", + "func f(u32 a) -> u32 { auto b = 0; auto c = f(a += 2); }" + ).error(|e| { e.assert_msg_has(0, "assignments are statements"); }); + + Tester::new_single_source_expect_err( + "inside tuple", + "func f(u32 a) -> u32 { auto b = (a += 2, a /= 2); return 0; }" + ).error(|e| { e.assert_msg_has(0, "assignments are statements"); }); +} + +#[test] +fn test_variable_introduction_in_scope() { + Tester::new_single_source_expect_err( + "variable use before declaration", + "func f() -> u8 { return thing; auto thing = 5; }" + ).error(|e| { e.assert_msg_has(0, "unresolved variable"); }); + + Tester::new_single_source_expect_err( + "variable use in declaration", + "func f() -> u8 { auto thing = 5 + thing; return thing; }" + ).error(|e| { e.assert_msg_has(0, "unresolved variable"); }); + + Tester::new_single_source_expect_ok( + "variable use after declaration", + "func f() -> u8 { auto thing = 5; return thing; }" + ); + + Tester::new_single_source_expect_err( + "variable use of closed scope", + "func f() -> u8 { { auto thing = 5; } return thing; }" + ).error(|e| { e.assert_msg_has(0, "unresolved variable"); }); +} + +#[test] +fn test_correct_select_statement() { + + Tester::new_single_source_expect_ok( + "guard variable decl", + " + primitive f() { + channel unused -> input; + + u32 outer_value = 0; + sync select { + auto in_same_guard = get(input) -> {} // decl A1 + auto in_same_gaurd = get(input) -> {} // decl A2 + auto in_guard_and_block = get(input) -> {} // decl B1 + outer_value = get(input) -> { auto in_guard_and_block = outer_value; } // decl B2 + } + } + " + ); + + Tester::new_single_source_expect_ok( + "empty select", + "primitive f() { sync select {} }" + ); + + Tester::new_single_source_expect_ok( + "mixed uses", " + primitive f() { + channel unused_output -> input; + u32 outer_value = 0; + sync select { + outer_value = get(input) -> outer_value = 0; + auto new_value = get(input) -> { + outer_value = new_value; + } + get(input) + get(input) -> + outer_value = 8; + get(input) -> + {} + outer_value %= get(input) -> { + outer_value *= outer_value; + auto new_value = get(input); + outer_value += new_value; + } + } + } + " + ); +} + +#[test] +fn test_incorrect_select_statement() { + Tester::new_single_source_expect_err( + "outside sync", + "primitive f() { select {} }" + ).error(|e| { e + .assert_num(1) + .assert_occurs_at(0, "select") + .assert_msg_has(0, "inside sync blocks"); + }); + + Tester::new_single_source_expect_err( + "variable in previous block", + "primitive f() { + channel tx -> rx; + u32 a = 0; // this one will be shadowed + sync select { auto a = get(rx) -> {} } + }" + ).error(|e| { e + .assert_num(2) + .assert_occurs_at(0, "a = get").assert_msg_has(0, "variable name conflicts") + .assert_occurs_at(1, "a = 0").assert_msg_has(1, "Previous variable"); + }); + + Tester::new_single_source_expect_err( + "put inside arm", + "primitive f() { + channel a -> b; + sync select { put(a) -> {} } + }" + ).error(|e| { e + .assert_occurs_at(0, "put") + .assert_msg_has(0, "may not occur"); + }); +} + +#[test] +fn test_incorrect_goto_statement() { + Tester::new_single_source_expect_err( + "goto missing var in same scope", + "func f() -> u32 { + goto exit; + auto v = 5; + exit: return 0; + }" + ).error(|e| { e + .assert_num(3) + .assert_occurs_at(0, "exit;").assert_msg_has(0, "skips over a variable") + .assert_occurs_at(1, "exit:").assert_msg_has(1, "jumps to this label") + .assert_occurs_at(2, "v = 5").assert_msg_has(2, "skips over this variable"); + }); + + Tester::new_single_source_expect_err( + "goto missing var in outer scope", + "func f() -> u32 { + if (true) { + goto exit; + } + auto v = 0; + exit: return 1; + }" + ).error(|e| { e + .assert_num(3) + .assert_occurs_at(0, "exit;").assert_msg_has(0, "skips over a variable") + .assert_occurs_at(1, "exit:").assert_msg_has(1, "jumps to this label") + .assert_occurs_at(2, "v = 0").assert_msg_has(2, "skips over this variable"); + }); + + Tester::new_single_source_expect_err( + "goto jumping into scope", + "func f() -> u32 { + goto nested; + { + nested: return 0; + } + return 1; + }" + ).error(|e| { e + .assert_num(1) + .assert_occurs_at(0, "nested;") + .assert_msg_has(0, "could not find this label"); + }); + + Tester::new_single_source_expect_err( + "goto jumping outside sync", + "primitive f() { + sync { goto exit; } + exit: u32 v = 0; + }" + ).error(|e| { e + .assert_num(3) + .assert_occurs_at(0, "goto exit;").assert_msg_has(0, "not escape the surrounding sync") + .assert_occurs_at(1, "exit: u32 v").assert_msg_has(1, "target of the goto") + .assert_occurs_at(2, "sync {").assert_msg_has(2, "jump past this"); + }) +} + +#[test] +fn test_incorrect_while_statement() { + // Just testing the error cases caught at compile-time. Other ones need + // evaluation testing + Tester::new_single_source_expect_err( + "break wrong earlier loop", + "func f() -> u32 { + target: while (true) {} + while (true) { break target; } + return 0; + }" + ).error(|e| { e + .assert_num(2) + .assert_occurs_at(0, "target; }").assert_msg_has(0, "not nested under the target") + .assert_occurs_at(1, "target: while").assert_msg_has(1, "is found here"); + }); + + Tester::new_single_source_expect_err( + "break wrong later loop", + "func f() -> u32 { + while (true) { break target; } + target: while (true) {} + return 0; + }" + ).error(|e| { e + .assert_num(2) + .assert_occurs_at(0, "target; }").assert_msg_has(0, "not nested under the target") + .assert_occurs_at(1, "target: while").assert_msg_has(1, "is found here"); + }); + + Tester::new_single_source_expect_err( + "break outside of sync", + "primitive f() { + outer: while (true) { //mark + sync while(true) { break outer; } + } + }" + ).error(|e| { e + .assert_num(3) + .assert_occurs_at(0, "break outer;").assert_msg_has(0, "may not escape the surrounding") + .assert_occurs_at(1, "while (true) { //mark").assert_msg_has(1, "escapes out of this loop") + .assert_occurs_at(2, "sync while").assert_msg_has(2, "escape this synchronous block"); + }); } \ No newline at end of file