compiler: Check loop context transitions and control statements

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