Merge ownership across short-circuit paths
d5fdaec0a07066dd66b3d4dc1bc5156354e34370f4239e91fcb0022a7284770f
Analyze the left operand of and/or on every path. Analyze the right operand in the path where it executes, then join that state with the skipped path. This rejects values consumed on only one reachable short-circuit path while keeping unconditionally consumed left operands valid.
1 parent
db989071
lib/std/lang/resolver.rad
+12 -1
| 8275 | 8275 | try checkLinearNode(checker, &mut branch, prong.body, LinearUse::Discard); |
|
| 8276 | 8276 | try finishLinearScope(checker, &mut branch, bindingsStart); |
|
| 8277 | 8277 | set checker.loanLen = loanStart; |
|
| 8278 | 8278 | if haveResult { |
|
| 8279 | 8279 | let previous = result; |
|
| 8280 | - | try joinLinearBranches(checker, &mut result, &previous, &branch, node); |
|
| 8280 | + | try joinLinearBranches(checker, &mut result, &previous, &branch, node); |
|
| 8281 | 8281 | } else { |
|
| 8282 | 8282 | set result = branch; |
|
| 8283 | 8283 | set haveResult = true; |
|
| 8284 | 8284 | } |
|
| 8285 | 8285 | } |
| 8610 | 8610 | } |
|
| 8611 | 8611 | try checkLinearNode(checker, env, repeat.item, LinearUse::Consume); |
|
| 8612 | 8612 | try checkLinearNode(checker, env, repeat.count, LinearUse::Consume); |
|
| 8613 | 8613 | } |
|
| 8614 | 8614 | case ast::NodeValue::BinOp(op) => { |
|
| 8615 | + | if op.op == ast::BinaryOp::And or op.op == ast::BinaryOp::Or { |
|
| 8616 | + | try checkLinearNode(checker, env, op.left, LinearUse::Consume); |
|
| 8617 | + | ||
| 8618 | + | let skipped = *env; |
|
| 8619 | + | let mut evaluated = skipped; |
|
| 8620 | + | ||
| 8621 | + | try checkLinearNode(checker, &mut evaluated, op.right, LinearUse::Consume); |
|
| 8622 | + | try joinLinearBranches(checker, env, &skipped, &evaluated, node); |
|
| 8623 | + | ||
| 8624 | + | return; |
|
| 8625 | + | } |
|
| 8615 | 8626 | let mut operandUse = LinearUse::Consume; |
|
| 8616 | 8627 | match op.op { |
|
| 8617 | 8628 | case ast::BinaryOp::Eq, ast::BinaryOp::Ne, |
|
| 8618 | 8629 | ast::BinaryOp::Lt, ast::BinaryOp::Gt, |
|
| 8619 | 8630 | ast::BinaryOp::Lte, ast::BinaryOp::Gte => |
lib/std/lang/resolver/tests.rad
+28 -0
| 6417 | 6417 | let mut res = testResolver(); |
|
| 6418 | 6418 | let result = try resolveProgramStr(&mut res, program); |
|
| 6419 | 6419 | try expectNoErrors(&result); |
|
| 6420 | 6420 | } |
|
| 6421 | 6421 | } |
|
| 6422 | + | ||
| 6423 | + | /// Short-circuit paths must agree on exact-use ownership. |
|
| 6424 | + | @test unsafe fn testShortCircuitOwnership() throws (testing::TestError) { |
|
| 6425 | + | for program in [ |
|
| 6426 | + | "union Ticket: Once { Value(u32) } fn take(t: Ticket) -> bool { match t { case Ticket::Value(n) => return n == 1, } } fn f(t: Ticket) { let result = false and take(t); }", |
|
| 6427 | + | "union Ticket: Once { Value(u32) } fn take(t: Ticket) -> bool { match t { case Ticket::Value(n) => return n == 1, } } fn f(t: Ticket) { let result = true or take(t); }", |
|
| 6428 | + | "union Ticket: Once { Value(u32) } fn take(t: Ticket) -> bool { match t { case Ticket::Value(n) => return n == 1, } } fn f(t: Ticket, flag: bool) { let result = flag and take(t); }", |
|
| 6429 | + | "union Ticket: Once { Value(u32) } fn take(t: Ticket) -> bool { match t { case Ticket::Value(n) => return n == 1, } } unsafe fn f(t: Ticket, flag: bool) { let result = flag or take(t); }", |
|
| 6430 | + | ] { |
|
| 6431 | + | let mut res = testResolver(); |
|
| 6432 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6433 | + | let error = try expectError(&result); |
|
| 6434 | + | let case super::ErrorKind::LinearBranchMismatch(_) = error.kind |
|
| 6435 | + | else throw testing::TestError::Failed; |
|
| 6436 | + | } |
|
| 6437 | + | } |
|
| 6438 | + | ||
| 6439 | + | /// The left operand executes on every short-circuit path. |
|
| 6440 | + | @test unsafe fn testShortCircuitLeftConsumption() throws (testing::TestError) { |
|
| 6441 | + | for program in [ |
|
| 6442 | + | "union Ticket: Once { Value(u32) } fn take(t: Ticket) -> bool { match t { case Ticket::Value(n) => return n == 1, } } fn f(t: Ticket) { let result = take(t) and false; }", |
|
| 6443 | + | "union Ticket: Once { Value(u32) } fn take(t: Ticket) -> bool { match t { case Ticket::Value(n) => return n == 1, } } fn f(t: Ticket) { let result = take(t) or true; }", |
|
| 6444 | + | ] { |
|
| 6445 | + | let mut res = testResolver(); |
|
| 6446 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 6447 | + | try expectNoErrors(&result); |
|
| 6448 | + | } |
|
| 6449 | + | } |