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.
Alexis Sellier committed ago 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 +
}