compiler: Check recursive cells and conditional ownership

55199dd625f01384eae1694dba6d6c286292a12c466e40b968e8326bdf78f33e
Alexis Sellier committed ago 1 parent c8d7b890
lib/std/lang/lower.rad +15 -6
4126 4126
    if letElse.pattern.guard == nil {
4127 4127
        set successBlock = try createBlock(self, "success");
4128 4128
    }
4129 4129
    // The else branch executes when the pattern fails to match.
4130 4130
    let elseBlock = try createBlock(self, "else");
4131 +
    let matchBlock = currentBlock(self);
4132 +
    let mut fallback: ?il::Val = nil;
4133 +
    // The fallback uses the enclosing scope before pattern bindings exist.
4134 +
    switchToBlock(self, elseBlock);
4135 +
    if try typeOf(self, letElse.elseBranch) == resolver::Type::Never {
4136 +
        try lowerNode(self, letElse.elseBranch);
4137 +
    } else {
4138 +
        set fallback = try lowerExpr(self, letElse.elseBranch);
4139 +
    }
4140 +
    let fallbackBlock = currentBlock(self);
4141 +
    switchToBlock(self, matchBlock);
4131 4142
4132 4143
    // Evaluate the pattern and jump to @success or @else.
4133 4144
    try lowerPatternMatch(
4134 4145
        self,
4135 4146
        &subject,
4143 4154
        set bindingVar = lookupLocalVar(&self.vars, letElse.pattern.pattern);
4144 4155
    }
4145 4156
    let mergeBlock = try createBlock(self, "merge");
4146 4157
    try emitJmp(self, mergeBlock);
4147 4158
4148 -
    try switchToAndSeal(self, elseBlock);
4149 -
    if try typeOf(self, letElse.elseBranch) == resolver::Type::Never {
4150 -
        try lowerNode(self, letElse.elseBranch);
4151 -
    } else {
4152 -
        let fallback = try lowerExpr(self, letElse.elseBranch);
4159 +
    try sealBlock(self, elseBlock);
4160 +
    if let value = fallback {
4161 +
        switchToBlock(self, fallbackBlock);
4153 4162
        if let variable = bindingVar {
4154 -
            defVar(self, variable, fallback);
4163 +
            defVar(self, variable, value);
4155 4164
        }
4156 4165
        try emitJmp(self, mergeBlock);
4157 4166
    }
4158 4167
4159 4168
    // Continue at @merge after a successful match or value-producing fallback.
lib/std/lang/resolver.rad +69 -41
1091 1091
    applications: ?*unsafe NominalApplication,
1092 1092
    /// First entry in the fully resolved suffix of the application list.
1093 1093
    completedApplications: ?*unsafe NominalApplication,
1094 1094
    /// Exact application lookup chains, backed by the resolver arena.
1095 1095
    applicationBuckets: *unsafe mut [?*unsafe NominalApplication],
1096 +
    /// Cell payload checks that require complete nominal layouts.
1097 +
    cellChecks: *mut [CellCheck],
1096 1098
    /// Current scope.
1097 1099
    scope: *unsafe mut Scope,
1098 1100
    /// Package scope containing package roots and top-level symbols.
1099 1101
    pkgScope: *unsafe mut Scope,
1100 1102
    /// Stack of loop contexts for nested loops.
1131 1133
    methods: [MethodEntry; MAX_METHODS],
1132 1134
    /// Number of registered standalone methods.
1133 1135
    methodsLen: u32,
1134 1136
}
1135 1137
1138 +
/// Cell signature whose payload needs a complete nominal layout.
1139 +
record CellCheck: Copy {
1140 +
    /// Source signature used for diagnostics.
1141 +
    node: *ast::Node,
1142 +
    /// Payload whose layout and ownership must be checked.
1143 +
    payload: Type,
1144 +
    /// Region environment at the source signature.
1145 +
    regions: ?*RegionScope,
1146 +
    /// Module that owns the source signature.
1147 +
    moduleId: u16,
1148 +
}
1149 +
1136 1150
/// Internal error sentinel thrown when analysis cannot proceed.
1137 1151
export union ResolveError: Copy {
1138 1152
    Failure,
1139 1153
}
1140 1154
1535 1549
            try ensureNominalResolved(self, applied.view, site);
1536 1550
            set cursor = applied.next;
1537 1551
        }
1538 1552
        if self.applications == first {
1539 1553
            set self.completedApplications = first;
1540 -
            return;
1554 +
            break;
1541 1555
        }
1542 1556
        set end = first;
1543 1557
    }
1558 +
    let previousRegions = self.regionScope;
1559 +
    let previousModule = self.currentMod;
1560 +
    let mut index: u32 = 0;
1561 +
    while index < self.cellChecks.len {
1562 +
        let check = self.cellChecks[index];
1563 +
        set self.regionScope = check.regions;
1564 +
        set self.currentMod = check.moduleId;
1565 +
        try validateCellPayload(self, check.node, check.payload) catch error {
1566 +
            set self.regionScope = previousRegions;
1567 +
            set self.currentMod = previousModule;
1568 +
            throw error;
1569 +
        };
1570 +
        set index += 1;
1571 +
    }
1572 +
    set self.cellChecks.len = 0;
1573 +
    set self.regionScope = previousRegions;
1574 +
    set self.currentMod = previousModule;
1544 1575
}
1545 1576
1546 1577
1547 1578
/// Allocate a function type descriptor and return a pointer to it.
1548 1579
unsafe fn allocFnType 'arena (self: &mut Resolver 'arena, info: FnType) -> *FnType {
1642 1673
        symbolCount: 0,
1643 1674
        regionScope: nil,
1644 1675
        applications: nil,
1645 1676
        completedApplications: nil,
1646 1677
        applicationBuckets,
1678 +
        cellChecks: &mut [],
1647 1679
        scope: pkgScope,
1648 1680
        pkgScope: pkgScope,
1649 1681
        loopStack: [LoopCtx { hasBreak: false }; MAX_LOOP_DEPTH],
1650 1682
        loopDepth: 0,
1651 1683
        currentFn: nil,
2407 2439
2408 2440
/// Ensure all nested nominal types in a type are resolved.
2409 2441
unsafe fn ensureTypeResolved 'arena (self: &mut Resolver 'arena, ty: Type, site: *ast::Node) throws (ResolveError) {
2410 2442
    match ty {
2411 2443
        case Type::Nominal(info) => try ensureNominalResolved(self, info, site),
2412 -
        // Pointer and slice layouts do not depend on their element layout.
2413 -
        case Type::Pointer { .. }, Type::Slice { .. } => {
2444 +
        // Pointer, slice, and cell layouts do not depend on their element layout.
2445 +
        case Type::Pointer { .. }, Type::Slice { .. }, Type::Cell { .. } => {
2414 2446
        },
2415 -
        case Type::Cell { payload, .. } => try validateCellPayload(self, site, *payload),
2416 2447
        case Type::Array(arr) => try ensureTypeResolved(self, *arr.item, site),
2417 2448
        case Type::Optional(inner) => try ensureTypeResolved(self, *inner, site),
2418 2449
        else => {},
2419 2450
    }
2420 2451
}
3286 3317
    }
3287 3318
    let name = try nodeName(self, ident);
3288 3319
    let data = SymbolData::Value { mutable, alignment, type, addressTaken: false };
3289 3320
    let scope = self.scope;
3290 3321
    let sym = try bindIdent(self, name, owner, data, attrs, scope);
3322 +
    if ident <> owner {
3323 +
        setNodeSymbol(self, ident, sym);
3324 +
    }
3291 3325
    setNodeType(self, owner, type);
3292 3326
    setNodeType(self, ident, type);
3293 3327
3294 3328
    // Track number of local bindings for lowering stage.
3295 3329
    if let owner = self.currentFnNode {
6855 6889
            // Simple binding requires an optional expression.
6856 6890
            let case Type::Optional(inner) = exprTy else {
6857 6891
                throw emitError(self, pat.scrutinee, ErrorKind::ExpectedOptional);
6858 6892
            };
6859 6893
            let payloadTy = *inner;
6860 -
            let _ = try bindValueIdent(self, pat.pattern, node, payloadTy, pat.mutable, 0, 0);
6861 6894
            // The `else` branch supplies the binding when the optional is nil.
6862 6895
            try checkAssignable(self, letElse.elseBranch, payloadTy);
6896 +
            let _ = try bindValueIdent(self, pat.pattern, node, payloadTy, pat.mutable, 0, 0);
6863 6897
6864 6898
            return setNodeType(self, node, Type::Void);
6865 6899
        }
6866 6900
        case ast::PatternKind::Case => {
6867 6901
            // Resolve the failure path before introducing success-only bindings.
7526 7560
        kind, item, traitInfo, methodIndex: runtime.index,
7527 7561
    });
7528 7562
    return setNodeType(self, node, result);
7529 7563
}
7530 7564
7531 -
/// Return whether a nominal declaration explicitly derives Copy.
7532 -
fn nominalDeclaresCopy(node: *ast::Node) -> bool {
7533 -
    let mut derives: *[*ast::Node] = &[];
7534 -
    match node.value {
7535 -
        case ast::NodeValue::RecordDecl(decl) => set derives = decl.derives,
7536 -
        case ast::NodeValue::UnionDecl(decl) => set derives = decl.derives,
7537 -
        else => return false,
7538 -
    }
7539 -
    for derive in derives {
7540 -
        if let case ast::NodeValue::Ident(name) = derive.value {
7541 -
            if mem::eq(name, "Copy") {
7542 -
                return true;
7543 -
            }
7544 -
        }
7545 -
    }
7546 -
    return false;
7547 -
}
7548 -
7549 7565
/// Require a complete payload that can be copied and discarded by value.
7550 7566
unsafe fn validateCellPayload 'arena (self: &mut Resolver 'arena, node: *ast::Node, payload: Type) throws (ResolveError) {
7551 7567
    try ensureStorableType(self, node, payload);
7552 -
    if let case Type::Nominal(info) = payload {
7553 -
        let mut source = info;
7554 -
        if let case NominalType::Application(applied) = *source {
7555 -
            set source = applied.base;
7556 -
        }
7557 -
        if let case NominalType::Resolving(decl) = *source {
7558 -
            if nominalDeclaresCopy(decl) {
7559 -
                return;
7560 -
            }
7561 -
            throw emitError(self, node, ErrorKind::InvalidCellPayload);
7562 -
        }
7563 -
    }
7564 7568
    try ensureTypeResolved(self, payload, node);
7565 7569
    if not isTypeInferrable(payload) or payload == Type::Void or payload == Type::Opaque
7566 7570
        or not isCopy(payload) or not isBulkDiscardable(payload)
7567 7571
    {
7568 7572
        throw emitError(self, node, ErrorKind::InvalidCellPayload);
9723 9727
    throws (ResolveError)
9724 9728
{
9725 9729
    match sig {
9726 9730
        case ast::TypeSig::Cell { class, payload } => {
9727 9731
            let inner = try infer(self, payload);
9728 -
            try validateCellPayload(self, node, inner);
9732 +
            try ensureStorableType(self, node, inner);
9733 +
            let allocator = alloc::arenaAllocator(self.arena);
9734 +
            self.cellChecks.append(CellCheck {
9735 +
                node, payload: inner, regions: self.regionScope, moduleId: self.currentMod,
9736 +
            }, allocator);
9729 9737
            return Type::Cell { class: resolvePointerClass(class), payload: allocType(self, inner) };
9730 9738
        }
9731 9739
        case ast::TypeSig::RegionRef { region, type } => {
9732 9740
            let identity = try resolveRegion(self, region);
9733 9741
            let base = try infer(self, type);
11131 11139
    let bindingsStart = thenEnv.len;
11132 11140
    let loanStart = checker.loanLen;
11133 11141
    if try addLinearPatternBindings(checker, &mut thenEnv, conditional.pattern.pattern) {
11134 11142
        try addPatternLoan(checker, conditional.pattern.scrutinee);
11135 11143
    }
11144 +
    expireAllocationLoans(checker, &mut thenEnv, conditional.pattern.pattern, conditional.pattern.scrutinee);
11136 11145
    if let guard = conditional.pattern.guard {
11137 11146
        try checkLinearNode(checker, &mut thenEnv, guard, LinearUse::Consume);
11138 11147
    }
11139 11148
    try checkLinearNode(checker, &mut thenEnv, conditional.thenBranch, LinearUse::Discard);
11140 11149
    try finishLinearScope(checker, &mut thenEnv, bindingsStart);
11221 11230
            let start = bodyEnv.len;
11222 11231
            let loanStart = checker.loanLen;
11223 11232
            if try addLinearPatternBindings(checker, &mut bodyEnv, whileStmt.pattern.pattern) {
11224 11233
                try addPatternLoan(checker, whileStmt.pattern.scrutinee);
11225 11234
            }
11235 +
            expireAllocationLoans(checker, &mut bodyEnv, whileStmt.pattern.pattern, whileStmt.pattern.scrutinee);
11226 11236
            if let guard = whileStmt.pattern.guard {
11227 11237
                try checkLinearNode(checker, &mut bodyEnv, guard, LinearUse::Consume);
11228 11238
                let mut guardExit = bodyEnv;
11229 11239
                try finishLinearScope(checker, &mut guardExit, start);
11230 11240
                let previous = conditionExit;
11333 11343
        }
11334 11344
        else => panic "cell address owner must belong to its place",
11335 11345
    }
11336 11346
}
11337 11347
11348 +
/// Fresh allocation storage has no loans from earlier loop iterations.
11349 +
unsafe fn expireAllocationLoans 'arena 'checking (
11350 +
    checker: &mut LinearChecker 'arena 'checking, env: &mut LinearEnv,
11351 +
    node: *ast::Node, value: *ast::Node
11352 +
) where 'arena: 'checking {
11353 +
    let case ast::NodeValue::Try(allocation) = value.value else return;
11354 +
    if allocation.catches.len <> 0 {
11355 +
        return;
11356 +
    }
11357 +
    let case NodeExtra::SessionAllocation(_) = checker.resolver.nodeData.entries[allocation.expr.id].extra
11358 +
        else return;
11359 +
    let symbol = checker.resolver.nodeData.entries[node.id].binding else return;
11360 +
    for i in 0..checker.regionalLen {
11361 +
        if regionalLoan(checker, i).place.root == symbol.symbol {
11362 +
            set env.regionalLoans &= ~((1 as u64) << (i as u64));
11363 +
        }
11364 +
    }
11365 +
}
11366 +
11338 11367
/// Check one expression or statement under an ownership-use context.
11339 11368
unsafe fn checkLinearNode 'arena 'checking (
11340 11369
    checker: &mut LinearChecker 'arena 'checking,
11341 11370
    env: &mut LinearEnv,
11342 11371
    node: *ast::Node,
11429 11458
            if env.terminated {
11430 11459
                return;
11431 11460
            }
11432 11461
            try addLinearBinding(checker, env, node);
11433 11462
            try addLocalLoan(checker, env, node, binding);
11463 +
            expireAllocationLoans(checker, env, node, binding.value);
11434 11464
            if isUndefined {
11435 11465
                markLinearBindingUnavailable(checker.resolver, env, node);
11436 11466
            }
11437 11467
        }
11438 11468
        case ast::NodeValue::Assign(assign) => {
11618 11648
                binding.pattern.scrutinee,
11619 11649
                patternSubjectUse(checker.resolver, binding.pattern.scrutinee),
11620 11650
            );
11621 11651
            let base = *env;
11622 11652
            let mut guardedEnv = base;
11653 +
            try addLinearPatternBindings(checker, &mut guardedEnv, binding.pattern.pattern);
11654 +
            expireAllocationLoans(checker, &mut guardedEnv, binding.pattern.pattern, binding.pattern.scrutinee);
11623 11655
            if let guard = binding.pattern.guard {
11624 11656
                try checkLinearNode(checker, &mut guardedEnv, guard, LinearUse::Consume);
11625 11657
            }
11626 11658
            let mut successEnv = guardedEnv;
11627 -
            try addLinearPatternBindings(
11628 -
                checker,
11629 -
                &mut successEnv,
11630 -
                binding.pattern.pattern,
11631 -
            );
11632 11659
            let mut fallbackEnv = base;
11633 11660
            try checkLinearNode(
11634 11661
                checker,
11635 11662
                &mut fallbackEnv,
11636 11663
                binding.elseBranch,
11642 11669
                    checker,
11643 11670
                    &mut guardFallbackEnv,
11644 11671
                    binding.elseBranch,
11645 11672
                    LinearUse::Consume,
11646 11673
                );
11674 +
                try finishLinearScope(checker, &mut guardFallbackEnv, base.len);
11647 11675
                let previous = fallbackEnv;
11648 11676
                try joinLinearBranches(
11649 11677
                    checker,
11650 11678
                    &mut fallbackEnv,
11651 11679
                    &previous,
lib/std/lang/resolver/tests/regions.rad +139 -0
1355 1355
            try super::expectErrorKind(&result, resolver::ErrorKind::InvalidAllocationLayout);
1356 1356
        }
1357 1357
    }
1358 1358
}
1359 1359
1360 +
/// Conditional bindings enforce moves through bodies and guards.
1361 +
@test unsafe fn testConditionalBindingMoves() throws (testing::TestError) {
1362 +
    for program in [
1363 +
        "record Token {} fn take(value: Token) {} fn f(value: ?Token) { if let item = value { take(item); take(item); } }",
1364 +
        "record Token {} fn take(value: Token) {} fn f(value: ?Token) { let item = value else return; take(item); take(item); }",
1365 +
        "record Token {} fn take(value: Token) {} fn next() -> ?Token { return Token {}; } fn f() { while let item = next() { take(item); take(item); } }",
1366 +
        "record Token {} fn take(value: Token) -> bool { return true; } fn f(value: ?Token) { if let item = value; take(item) { take(item); } }",
1367 +
        "record Token {} fn take(value: Token) {} unsafe fn f(value: ?Token) { if let item = value { take(item); take(item); } }",
1368 +
        "record Token {} record Box { item: Token } fn take(item: Token) -> bool { return true; } fn f(box: Box) { let case Box { item } = box if take(item) else return; take(item); }",
1369 +
        "record Token {} record Box { item: Token } fn take(item: Token) -> bool { return true; } unsafe fn f(box: Box) { let case Box { item } = box if take(item) else return; take(item); }",
1370 +
        "record Token {} union Box { Full(Token), Empty } fn take(item: Token) -> bool { return true; } fn f(box: Box) { let case Box::Full(item) = box if take(item) else return; take(item); }",
1371 +
    ] {
1372 +
        let mut storage = super::testArena();
1373 +
        let memory: 'test = &mut storage in {
1374 +
            let mut res = super::testResolver(memory);
1375 +
            let result = try super::resolveProgramStr(&mut res, program);
1376 +
            try super::expectErrorKind(&result, resolver::ErrorKind::AffineUseAfterMove("item"));
1377 +
        }
1378 +
    }
1379 +
}
1380 +
1381 +
/// Guard ownership distinguishes consumed fields, borrowed fields, and failure paths.
1382 +
@test unsafe fn testLetElseGuardOwnership() throws (testing::TestError) {
1383 +
    for program in [
1384 +
        "record Token {} record Box { item: Token } fn take(item: Token) -> bool { return true; } fn f(box: Box) { let case Box { item } = box if take(item) else return; }",
1385 +
        "record Token {} record Box { item: Token } fn read(item: &Token) -> bool { return true; } fn take(item: Token) {} fn f(box: Box) { let case Box { item } = box if read(&item) else return; take(item); }",
1386 +
        "union Box { Full(u32), Empty } fn f(box: Box) -> u32 { let case Box::Full(item) = box if item > 0 else return 0; return item; }",
1387 +
    ] {
1388 +
        let mut storage = super::testArena();
1389 +
        let memory: 'test = &mut storage in {
1390 +
            let mut res = super::testResolver(memory);
1391 +
            let result = try super::resolveProgramStr(&mut res, program);
1392 +
            try super::expectNoErrors(&result);
1393 +
        }
1394 +
    }
1395 +
    let mut storage = super::testArena();
1396 +
    let memory: 'test = &mut storage in {
1397 +
        let mut res = super::testResolver(memory);
1398 +
        let result = try super::resolveProgramStr(&mut res,
1399 +
            "record Token {} record Box { item: u32 } fn take(outer: Token) -> bool { return false; } fn f(box: Box, outer: Token) { let case Box { item } = box if take(outer) else { take(outer); return; }; }");
1400 +
        try super::expectErrorKind(&result, resolver::ErrorKind::AffineUseAfterMove("outer"));
1401 +
    }
1402 +
}
1403 +
1404 +
/// Fallback expressions cannot access their new binding before initialization.
1405 +
@test unsafe fn testLetElseFallbackBinding() throws (testing::TestError) {
1406 +
    for program in [
1407 +
        "fn f(input: ?u32) -> u32 { let value = input else value; return value; }",
1408 +
        "unsafe fn f(input: ?u32) -> u32 { let value = input else value; return value; }",
1409 +
        "fn f(input: ?u32) -> u32 { let mut value = input else { value }; return value; }",
1410 +
    ] {
1411 +
        let mut storage = super::testArena();
1412 +
        let memory: 'test = &mut storage in {
1413 +
            let mut res = super::testResolver(memory);
1414 +
            let result = try super::resolveProgramStr(&mut res, program);
1415 +
            try super::expectErrorKind(&result, resolver::ErrorKind::UnresolvedSymbol("value"));
1416 +
        }
1417 +
    }
1418 +
}
1419 +
1420 +
/// Each successful session allocation has distinct storage across loop iterations.
1421 +
@test unsafe fn testFreshAllocationLoopLoan() throws (testing::TestError) {
1422 +
    for program in [
1423 +
        "fn f 'r (s: &Session 'r) { for i in 0..3 { let p = try! s.fill(0 as u32, 2); set p[0] = i; let view: &'r [u32] = &p[..]; } }",
1424 +
        "fn f 'r (s: &Session 'r) { let mut i: u32 = 0; while i < 3 { let p = try! s.new(0 as u32); set *p = i; let view: &'r u32 = &*p; set i += 1; } }",
1425 +
        "fn f 'r (s: &Session 'r) { for i in 0..3 { if let p = try? s.fill(0 as u32, 2); p.len == 2 { set p[0] = i; let view: &'r [u32] = &p[..]; } } }",
1426 +
        "fn f 'r (s: &Session 'r) { while let p = try? s.new(0 as u32) { set *p = 1; let view: &'r u32 = &*p; } }",
1427 +
        "fn f 'r (s: &Session 'r) { loop { let p = try? s.new(0 as u32) else break; set *p = 1; let view: &'r u32 = &*p; } }",
1428 +
        "fn f 'r (s: &Session 'r) { for i in 0..3 { if let _ = try? s.new(0 as u32) {} let _ = try! s.new(0 as u32); } }",
1429 +
    ] {
1430 +
        let mut storage = super::testArena();
1431 +
        let memory: 'test = &mut storage in {
1432 +
            let mut res = super::testResolver(memory);
1433 +
            let result = try super::resolveSessionProgramStr(&mut res, program);
1434 +
            try super::expectNoErrors(&result);
1435 +
        }
1436 +
    }
1437 +
}
1438 +
1439 +
/// Fresh allocations preserve loans on aliases and existing storage.
1440 +
@test unsafe fn testFreshAllocationRetainsOtherLoans() throws (testing::TestError) {
1441 +
    for program in [
1442 +
        "fn f 'r (s: &Session 'r) { for i in 0..3 { let p = try! s.fill(0 as u32, 2); let view: &'r [u32] = &p[..]; set p[0] = i; } }",
1443 +
        "fn f 'r (s: &Session 'r) { let p = try! s.fill(0 as u32, 2); for i in 0..3 { set p[0] = i; let view: &'r [u32] = &p[..]; } }",
1444 +
        "fn f 'r (s: &Session 'r, p: &'r mut u32) { for i in 0..3 { let view: &'r u32 = &*p; let fresh = try! s.new(0 as u32); set *p = i; } }",
1445 +
        "fn f 'r (s: &Session 'r, p: &'r mut u32) { for i in 0..3 { let fresh = try! s.new(&*p); set *p = i; } }",
1446 +
        "fn f 'r (s: &Session 'r) { while let p = try? s.new(0 as u32) { let view: &'r u32 = &*p; set *p = 1; } }",
1447 +
        "fn f 'r (s: &Session 'r) { for i in 0..3 { if let p = try? s.new(0 as u32) { let view: &'r u32 = &*p; set *p = i; } } }",
1448 +
        "fn f 'r (s: &Session 'r) { loop { let p = try? s.new(0 as u32) else break; let view: &'r u32 = &*p; set *p = 1; } }",
1449 +
        "fn check 'r (p: &'r u32) -> bool { return *p == 0; } fn f 'r (s: &Session 'r) { while let p = try? s.new(0 as u32); check(&*p) { set *p = 1; } }",
1450 +
    ] {
1451 +
        let mut storage = super::testArena();
1452 +
        let memory: 'test = &mut storage in {
1453 +
            let mut res = super::testResolver(memory);
1454 +
            let result = try super::resolveSessionProgramStr(&mut res, program);
1455 +
            try super::expectErrorKind(&result, resolver::ErrorKind::BorrowConflict("p"));
1456 +
        }
1457 +
    }
1458 +
}
1459 +
1460 +
/// Cell references give indirect cycles finite layouts in each declaration order.
1461 +
@test unsafe fn testRecursiveCellLayout() throws (testing::TestError) {
1462 +
    for program in [
1463 +
        "record V: 'r + Copy { n: N 'r } record N: 'r + Copy { s: &'r cell S 'r } union S: 'r + Copy { Ready(V 'r), Pending }",
1464 +
        "union S: 'r + Copy { Ready(V 'r), Pending } record V: 'r + Copy { n: N 'r } record N: 'r + Copy { s: &'r cell S 'r }",
1465 +
        "record N: 'r + Copy { s: &'r cell S 'r } union S: 'r + Copy { Ready(V 'r), Pending } record V: 'r + Copy { n: N 'r }",
1466 +
        "record V: Copy { n: N } record N: Copy { s: *cell S } union S: Copy { Ready(V), Pending }",
1467 +
    ] {
1468 +
        let mut storage = super::testArena();
1469 +
        let memory: 'test = &mut storage in {
1470 +
            let mut res = super::testResolver(memory);
1471 +
            let result = try super::resolveProgramStr(&mut res, program);
1472 +
            try super::expectNoErrors(&result);
1473 +
        }
1474 +
    }
1475 +
}
1476 +
1477 +
/// Deferred cell checks reject invalid payloads without a payload access.
1478 +
@test unsafe fn testDeferredCellPayload() throws (testing::TestError) {
1479 +
    for program in [
1480 +
        "record N: 'r + Copy { s: &'r cell S 'r } union S: 'r { Ready(N 'r), Pending }",
1481 +
        "record N: Copy { s: *cell S } record S { value: u32 }",
1482 +
    ] {
1483 +
        let mut storage = super::testArena();
1484 +
        let memory: 'test = &mut storage in {
1485 +
            let mut res = super::testResolver(memory);
1486 +
            let result = try super::resolveProgramStr(&mut res, program);
1487 +
            try super::expectErrorKind(&result, resolver::ErrorKind::InvalidCellPayload);
1488 +
        }
1489 +
    }
1490 +
    let mut storage = super::testArena();
1491 +
    let memory: 'test = &mut storage in {
1492 +
        let mut res = super::testResolver(memory);
1493 +
        let result = try super::resolveProgramStr(&mut res,
1494 +
            "record N: Copy { s: *cell S } record S: Copy { bytes: [u64; 536870913] }");
1495 +
        try super::expectErrorKind(&result, resolver::ErrorKind::InvalidAllocationLayout);
1496 +
    }
1497 +
}
1498 +
1360 1499
/// Cell payloads must permit plain value copies.
1361 1500
@test unsafe fn testCellPointerPayload() throws (testing::TestError) {
1362 1501
    for program in [
1363 1502
        "union State: 'r { Next(&'r cell State 'r) }",
1364 1503
        "union State: 'r { Ready(&'r u32) } fn f 'r (state: &'r cell State 'r) {}",
test/tests/cond.letelse.fallback.scope.rad added +34 -0
1 +
//! returns: 0
2 +
//! Fallback expressions use the enclosing scope before binding their result.
3 +
4 +
/// Return the optional payload or the outer fallback value.
5 +
fn choose(input: ?u32, value: u32) -> u32 {
6 +
    {
7 +
        let value = input else value;
8 +
        return value;
9 +
    }
10 +
}
11 +
12 +
/// Evaluate a fallback with its own local binding and conditional result.
13 +
fn chooseBlock(input: ?u32, value: u32) -> u32 {
14 +
    {
15 +
        let value = input else {
16 +
            let mut offset = value;
17 +
            if value > 10 {
18 +
                set offset += 1;
19 +
            }
20 +
            return offset;
21 +
        };
22 +
        return value;
23 +
    }
24 +
}
25 +
26 +
/// Check both paths through a fallback that shares the new binding's name.
27 +
@default fn main() -> u32 {
28 +
    assert choose(nil, 17) == 17;
29 +
    assert choose(23, 17) == 23;
30 +
    assert chooseBlock(nil, 17) == 18;
31 +
    assert chooseBlock(nil, 3) == 3;
32 +
    assert chooseBlock(23, 17) == 23;
33 +
    return 0;
34 +
}
test/tests/regions.session.loop.allocate.rad added +42 -0
1 +
//! returns: 0
2 +
//! session-support
3 +
//! Recoverable allocation publishes separate storage on every loop iteration.
4 +
5 +
use std::lang::alloc;
6 +
7 +
/// Exercise guarded bindings, loop bindings, and early-exit allocation failure.
8 +
@default fn main() -> u32 {
9 +
    static DATA: [u8; 256] = [0; 256];
10 +
    let mut arena = alloc::new(&mut DATA[..]);
11 +
    use arena as storage in {
12 +
        for i in 0..3 {
13 +
            if let values = try? storage.fill(0 as u32, 2); values.len == 2 {
14 +
                set values[0] = i;
15 +
                let frozen: &'storage [u32] = &values[..];
16 +
                assert frozen[0] == i;
17 +
            } else {
18 +
                panic "main: allocation failed";
19 +
            }
20 +
        }
21 +
        let mut count: u32 = 0;
22 +
        while let value = try? storage.new(0 as u32); count < 3 {
23 +
            set *value = count;
24 +
            let frozen: &'storage u32 = &*value;
25 +
            assert *frozen == count;
26 +
            set count += 1;
27 +
        }
28 +
        assert count == 3;
29 +
        set count = 0;
30 +
        loop {
31 +
            let value = try? storage.new(0 as u32) else break;
32 +
            set *value = count;
33 +
            let frozen: &'storage u32 = &*value;
34 +
            assert *frozen == count;
35 +
            set count += 1;
36 +
        }
37 +
        assert count > 0;
38 +
    }
39 +
    alloc::reset(&mut arena);
40 +
    assert alloc::used(&arena) == 0;
41 +
    return 0;
42 +
}