compiler: Check loop context transitions and control statements
9637272a0326ff464608c53f061aff6168a424a52363c5c42758a69b158a9ea5
1 parent
0bf6dec3
lib/std/lang/resolver.rad
+32 -24
| 1634 | 1634 | applications: nil, |
|
| 1635 | 1635 | completedApplications: nil, |
|
| 1636 | 1636 | applicationBuckets, |
|
| 1637 | 1637 | scope: pkgScope, |
|
| 1638 | 1638 | pkgScope: pkgScope, |
|
| 1639 | - | loopStack: undefined, |
|
| 1639 | + | loopStack: [LoopCtx { hasBreak: false }; MAX_LOOP_DEPTH], |
|
| 1640 | 1640 | loopDepth: 0, |
|
| 1641 | 1641 | currentFn: nil, |
|
| 1642 | 1642 | currentFnNode: nil, |
|
| 1643 | 1643 | currentMod: 0, |
|
| 1644 | 1644 | inUnsafeContext: false, |
| 1775 | 1775 | return; |
|
| 1776 | 1776 | }; |
|
| 1777 | 1777 | set self.scope = parent; |
|
| 1778 | 1778 | } |
|
| 1779 | 1779 | ||
| 1780 | - | /// Visit the body of a loop while tracking nesting depth. |
|
| 1781 | - | unsafe fn visitLoop 'arena (self: &mut Resolver 'arena, body: *ast::Node) -> Type |
|
| 1782 | - | throws (ResolveError) |
|
| 1783 | - | { |
|
| 1784 | - | assert self.loopDepth < MAX_LOOP_DEPTH, "visitLoop: loop nesting depth exceeded"; |
|
| 1780 | + | /// Initialize a loop context before making it active. |
|
| 1781 | + | fn enterLoop 'arena (self: &mut Resolver 'arena) { |
|
| 1782 | + | assert self.loopDepth < MAX_LOOP_DEPTH, "enterLoop: loop nesting depth exceeded"; |
|
| 1785 | 1783 | set self.loopStack[self.loopDepth] = LoopCtx { hasBreak: false }; |
|
| 1786 | 1784 | set self.loopDepth += 1; |
|
| 1785 | + | } |
|
| 1787 | 1786 | ||
| 1788 | - | let ty = try infer(self, body) catch { |
|
| 1789 | - | assert self.loopDepth <> 0, "visitLoop: loop depth underflow"; |
|
| 1790 | - | set self.loopDepth -= 1; |
|
| 1791 | - | throw ResolveError::Failure; |
|
| 1792 | - | }; |
|
| 1787 | + | /// End the active loop context and return its control-flow type. |
|
| 1788 | + | fn exitLoop 'arena (self: &mut Resolver 'arena) -> Type { |
|
| 1789 | + | assert self.loopDepth > 0, "exitLoop: loop depth underflow"; |
|
| 1793 | 1790 | // Pop and check if break was encountered. |
|
| 1794 | 1791 | set self.loopDepth -= 1; |
|
| 1795 | - | ||
| 1796 | 1792 | if self.loopStack[self.loopDepth].hasBreak { |
|
| 1797 | 1793 | return Type::Void; |
|
| 1798 | 1794 | } |
|
| 1799 | 1795 | return Type::Never; |
|
| 1800 | 1796 | } |
|
| 1801 | 1797 | ||
| 1798 | + | /// Visit the body of a loop while tracking nesting depth. |
|
| 1799 | + | unsafe fn visitLoop 'arena (self: &mut Resolver 'arena, body: *ast::Node) -> Type |
|
| 1800 | + | throws (ResolveError) |
|
| 1801 | + | { |
|
| 1802 | + | enterLoop(self); |
|
| 1803 | + | try infer(self, body) catch { |
|
| 1804 | + | exitLoop(self); |
|
| 1805 | + | throw ResolveError::Failure; |
|
| 1806 | + | }; |
|
| 1807 | + | return exitLoop(self); |
|
| 1808 | + | } |
|
| 1809 | + | ||
| 1802 | 1810 | /// Require that loop control statements appear inside a loop. |
|
| 1803 | - | fn ensureInsideLoop 'arena (self: &mut Resolver 'arena, node: *ast::Node) throws (ResolveError) { |
|
| 1811 | + | /// Record breaks and assign the control statement's diverging type. |
|
| 1812 | + | fn resolveLoopControl 'arena (self: &mut Resolver 'arena, node: *ast::Node) -> Type throws (ResolveError) { |
|
| 1804 | 1813 | if self.loopDepth == 0 { |
|
| 1805 | 1814 | throw emitError(self, node, ErrorKind::InvalidLoopControl); |
|
| 1806 | 1815 | } |
|
| 1816 | + | match node.value { |
|
| 1817 | + | case ast::NodeValue::Break => { |
|
| 1818 | + | // Mark that the current loop has a reachable break. |
|
| 1819 | + | set self.loopStack[self.loopDepth - 1].hasBreak = true; |
|
| 1820 | + | } |
|
| 1821 | + | case ast::NodeValue::Continue => {} |
|
| 1822 | + | else => panic "resolveLoopControl: expected loop control statement", |
|
| 1823 | + | } |
|
| 1824 | + | return setNodeType(self, node, Type::Never); |
|
| 1807 | 1825 | } |
|
| 1808 | 1826 | ||
| 1809 | 1827 | /// Bind a loop pattern to the provided type. |
|
| 1810 | 1828 | unsafe fn bindForLoopPattern 'arena (self: &mut Resolver 'arena, pattern: *ast::Node, ty: Type, mutable: bool) |
|
| 1811 | 1829 | throws (ResolveError) |
| 3903 | 3921 | case ast::NodeValue::For(loopNode) => return try resolveFor(self, node, loopNode), |
|
| 3904 | 3922 | case ast::NodeValue::Loop { body } => { |
|
| 3905 | 3923 | let loopType = try visitLoop(self, body); |
|
| 3906 | 3924 | return setNodeType(self, node, loopType); |
|
| 3907 | 3925 | }, |
|
| 3908 | - | case ast::NodeValue::Break => { |
|
| 3909 | - | try ensureInsideLoop(self, node); |
|
| 3910 | - | // Mark that the current loop has a reachable break. |
|
| 3911 | - | set self.loopStack[self.loopDepth - 1].hasBreak = true; |
|
| 3912 | - | ||
| 3913 | - | return setNodeType(self, node, Type::Never); |
|
| 3914 | - | }, |
|
| 3915 | - | case ast::NodeValue::Continue => { |
|
| 3916 | - | try ensureInsideLoop(self, node); |
|
| 3917 | - | return setNodeType(self, node, Type::Never); |
|
| 3918 | - | }, |
|
| 3926 | + | case ast::NodeValue::Break, ast::NodeValue::Continue => return try resolveLoopControl(self, node), |
|
| 3919 | 3927 | case ast::NodeValue::Match(sw) => return try resolveMatch(self, node, sw), |
|
| 3920 | 3928 | case ast::NodeValue::MatchProng(_) => panic "visit: `MatchProng` not handled here", |
|
| 3921 | 3929 | case ast::NodeValue::LetElse(letElse) => return try resolveLetElse(self, node, letElse), |
|
| 3922 | 3930 | case ast::NodeValue::BuiltinCall { kind, args } => return try resolveBuiltinCall(self, node, kind, args), |
|
| 3923 | 3931 | case ast::NodeValue::Assign(assign) => return try resolveAssign(self, node, assign), |
lib/std/lang/resolver/tests.rad
+35 -36
| 73 | 73 | ); |
|
| 74 | 74 | try expectErrorKind(&result, super::ErrorKind::LinearBranchMismatch("second")); |
|
| 75 | 75 | } |
|
| 76 | 76 | } |
|
| 77 | 77 | ||
| 78 | + | /// Nested loops keep separate break state and restore the stack after errors. |
|
| 79 | + | @test unsafe fn testLoopContextState() throws (testing::TestError) { |
|
| 80 | + | for program, index in [ |
|
| 81 | + | "fn f() { loop { break; } loop { loop { break; } continue; } }", |
|
| 82 | + | "fn f() { loop { loop { missing; } } }", |
|
| 83 | + | "fn f() { loop { break; } break; }", |
|
| 84 | + | "fn f() { loop { break; } continue; }", |
|
| 85 | + | "break;", |
|
| 86 | + | "continue;", |
|
| 87 | + | "loop { break }", |
|
| 88 | + | "while true { continue }", |
|
| 89 | + | ] { |
|
| 90 | + | let mut arena = testArena(); |
|
| 91 | + | let storage: 'test = &mut arena in { |
|
| 92 | + | let mut res = testResolver(storage); |
|
| 93 | + | let result = try resolveProgramStr(&mut res, program); |
|
| 94 | + | assert res.loopDepth == 0; |
|
| 95 | + | if index == 0 { |
|
| 96 | + | try expectNoErrors(&result); |
|
| 97 | + | let body = try getFnBody(&res, result.root, "f"); |
|
| 98 | + | let first = super::typeFor(&res, body.statements[0]) else throw testing::TestError::Failed; |
|
| 99 | + | let second = super::typeFor(&res, body.statements[1]) else throw testing::TestError::Failed; |
|
| 100 | + | let case super::Type::Void = first else throw testing::TestError::Failed; |
|
| 101 | + | let case super::Type::Never = second else throw testing::TestError::Failed; |
|
| 102 | + | } else if index == 1 { |
|
| 103 | + | try expectErrorKind(&result, super::ErrorKind::UnresolvedSymbol("missing")); |
|
| 104 | + | } else if index < 6 { |
|
| 105 | + | try expectErrorKind(&result, super::ErrorKind::InvalidLoopControl); |
|
| 106 | + | } else { |
|
| 107 | + | try expectNoErrors(&result); |
|
| 108 | + | } |
|
| 109 | + | } |
|
| 110 | + | } |
|
| 111 | + | } |
|
| 112 | + | ||
| 78 | 113 | /// Loop bindings and lowering metadata retain the same item type and names. |
|
| 79 | 114 | @test unsafe fn testForLoopMetadata() throws (testing::TestError) { |
|
| 80 | 115 | let mut arena = testArena(); |
|
| 81 | 116 | let storage: 'test = &mut arena in { |
|
| 82 | 117 | let mut res = testResolver(storage); |
| 2076 | 2111 | try expectNoErrors(&result); |
|
| 2077 | 2112 | } |
|
| 2078 | 2113 | } |
|
| 2079 | 2114 | } |
|
| 2080 | 2115 | ||
| 2081 | - | @test unsafe fn testResolveBreakRequiresLoop() throws (testing::TestError) { |
|
| 2082 | - | { |
|
| 2083 | - | let mut testArena109 = testArena(); |
|
| 2084 | - | let testStorage109: 'test109 = &mut testArena109 in { |
|
| 2085 | - | let mut a = testResolver(testStorage109); |
|
| 2086 | - | let result = try resolveProgramStr(&mut a, "break;"); |
|
| 2087 | - | try expectErrorKind(&result, super::ErrorKind::InvalidLoopControl); |
|
| 2088 | - | } |
|
| 2089 | - | } { |
|
| 2090 | - | let mut testArena110 = testArena(); |
|
| 2091 | - | let testStorage110: 'test110 = &mut testArena110 in { |
|
| 2092 | - | let mut a = testResolver(testStorage110); |
|
| 2093 | - | let result = try resolveProgramStr(&mut a, "loop { break }"); |
|
| 2094 | - | try expectNoErrors(&result); |
|
| 2095 | - | } |
|
| 2096 | - | } |
|
| 2097 | - | } |
|
| 2098 | - | ||
| 2099 | - | @test unsafe fn testResolveContinueRequiresLoop() throws (testing::TestError) { |
|
| 2100 | - | { |
|
| 2101 | - | let mut testArena111 = testArena(); |
|
| 2102 | - | let testStorage111: 'test111 = &mut testArena111 in { |
|
| 2103 | - | let mut a = testResolver(testStorage111); |
|
| 2104 | - | let result = try resolveProgramStr(&mut a, "continue;"); |
|
| 2105 | - | try expectErrorKind(&result, super::ErrorKind::InvalidLoopControl); |
|
| 2106 | - | } |
|
| 2107 | - | } { |
|
| 2108 | - | let mut testArena112 = testArena(); |
|
| 2109 | - | let testStorage112: 'test112 = &mut testArena112 in { |
|
| 2110 | - | let mut a = testResolver(testStorage112); |
|
| 2111 | - | let result = try resolveProgramStr(&mut a, "while true { continue }"); |
|
| 2112 | - | try expectNoErrors(&result); |
|
| 2113 | - | } |
|
| 2114 | - | } |
|
| 2115 | - | } |
|
| 2116 | - | ||
| 2117 | 2116 | @test unsafe fn testResolveFnTypeVoidNoParams() throws (testing::TestError) { |
|
| 2118 | 2117 | let mut testArena113 = testArena(); |
|
| 2119 | 2118 | let testStorage113: 'test113 = &mut testArena113 in { |
|
| 2120 | 2119 | let mut a = testResolver(testStorage113); |
|
| 2121 | 2120 | let result = try resolveProgramStr(&mut a, "fn f() {} f();"); |
test/tests/loop.context.rad
added
+26 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Exercise the full loop stack and reuse its entries in successive iterations. |
|
| 4 | + | @default fn main() -> u32 { |
|
| 5 | + | let mut count: u32 = 0; |
|
| 6 | + | loop { |
|
| 7 | + | loop { loop { loop { loop { loop { loop { loop { |
|
| 8 | + | loop { loop { loop { loop { loop { loop { loop { loop { |
|
| 9 | + | set count += 1; |
|
| 10 | + | break; |
|
| 11 | + | } break; } break; } break; } break; } break; } break; } break; |
|
| 12 | + | } break; } break; } break; } break; } break; } break; } break; |
|
| 13 | + | } |
|
| 14 | + | if count < 3 { |
|
| 15 | + | continue; |
|
| 16 | + | } |
|
| 17 | + | break; |
|
| 18 | + | } |
|
| 19 | + | assert count == 3; |
|
| 20 | + | loop { |
|
| 21 | + | set count += 1; |
|
| 22 | + | break; |
|
| 23 | + | } |
|
| 24 | + | assert count == 4; |
|
| 25 | + | return 0; |
|
| 26 | + | } |