compiler: Check recursive cells and conditional ownership
55199dd625f01384eae1694dba6d6c286292a12c466e40b968e8326bdf78f33e
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 | + | } |