Support never-returning function signatures

db5ee0c49a6ab67dbb0bdcfe1a75cae4d759d50a3e9dc968283864ed631c4245
Alexis Sellier committed ago 1 parent ca4687c0
lib/std/lang/ast.rad +2 -0
157 157
158 158
/// Type signature node.
159 159
export union TypeSig: Copy {
160 160
    /// Absence of type.
161 161
    Void,
162 +
    /// A function return type with no possible value.
163 +
    Never,
162 164
    /// Opaque type.
163 165
    Opaque,
164 166
    /// Boolean type.
165 167
    Bool,
166 168
    /// Integer type.
lib/std/lang/ast/printer.rad +1 -0
85 85
86 86
/// Convert a type signature to an S-expression.
87 87
unsafe fn typeSigToExpr(a: &mut alloc::Arena, sig: super::TypeSig) -> sexpr::Expr {
88 88
    match sig {
89 89
        case super::TypeSig::Void => return sexpr::sym("void"),
90 +
        case super::TypeSig::Never => return sexpr::sym("!"),
90 91
        case super::TypeSig::Opaque => return sexpr::sym("opaque"),
91 92
        case super::TypeSig::Bool => return sexpr::sym("bool"),
92 93
        case super::TypeSig::Integer { width, sign } => return sexpr::sym(intTypeName(width, sign)),
93 94
        case super::TypeSig::Array { itemType, length } =>
94 95
            return sexpr::list(a, "array", &[toExpr(a, itemType), toExpr(a, length)]),
lib/std/lang/lower.rad +18 -5
3546 3546
    /// Lower function body.
3547 3547
    try lowerBlock(self, body);
3548 3548
3549 3549
    // Add implicit return if body doesn't diverge.
3550 3550
    if not blockHasTerminator(self) {
3551 -
        if self.fnType.throwList.len > 0 {
3551 +
        if *self.fnType.returnType == resolver::Type::Never {
3552 +
            emit(self, il::Instr::Unreachable);
3553 +
        } else if self.fnType.throwList.len > 0 {
3552 3554
            if *self.fnType.returnType == resolver::Type::Void {
3553 3555
                // Implicit `void` return in throwing function: wrap in result success.
3554 3556
                let val = try buildResult(self, 0, nil, resolver::Type::Void);
3555 3557
                try emitRetVal(self, val);
3556 3558
            } else {
5208 5210
5209 5211
/// Lower a let binding.
5210 5212
unsafe fn lowerLet(self: &mut FnLowerer, node: *ast::Node, l: ast::Let) throws (LowerError) {
5211 5213
    // Evaluate value.
5212 5214
    let val = try lowerExpr(self, l.value);
5215 +
    if blockHasTerminator(self) {
5216 +
        return;
5217 +
    }
5213 5218
    // Handle placeholder pattern: `let _ = expr;`
5214 5219
    if let case ast::NodeValue::Placeholder = l.ident.value {
5215 5220
        return;
5216 5221
    }
5217 5222
    let case ast::NodeValue::Ident(name) = l.ident.value else {
5772 5777
unsafe fn lowerReturnStmt(self: &mut FnLowerer, node: *ast::Node, value: ?*ast::Node) throws (LowerError) {
5773 5778
    let mut val = il::Val::Undef;
5774 5779
    if let expr = value {
5775 5780
        set val = try lowerExpr(self, expr);
5776 5781
    }
5782 +
    if blockHasTerminator(self) {
5783 +
        return;
5784 +
    }
5777 5785
    set val = try applyCoercion(self, node, val);
5778 5786
    try emitRetVal(self, val);
5779 5787
}
5780 5788
5781 5789
/// Lower a throw statement.
6279 6287
    let mut mergeBlock: ?BlockId = nil;
6280 6288
    let mut resultSlot: ?il::Reg = nil; // `try` result value will be stored here.
6281 6289
6282 6290
    // Check if the `try` returns a success value or not. If so, reserve
6283 6291
    // space for it.
6284 -
    let isVoid = tryExprTy == resolver::Type::Void;
6292 +
    let isVoid = tryExprTy == resolver::Type::Void or tryExprTy == resolver::Type::Never;
6285 6293
    if not isVoid {
6286 6294
        set resultSlot = try emitReserve(self, tryExprTy);
6287 6295
    }
6288 6296
    // Branch on tag: zero means ok, non-zero means error.
6289 6297
    try emitBr(self, tagReg, errBlock, okBlock);
6294 6302
6295 6303
    // Success path: extract the successful value from the result and store it
6296 6304
    // in the result slot for later use after the merge point.
6297 6305
    switchToBlock(self, okBlock);
6298 6306
6299 -
    if let slot = resultSlot {
6307 +
    if okValueTy == resolver::Type::Never {
6308 +
        emit(self, il::Instr::Unreachable);
6309 +
    } else if let slot = resultSlot {
6300 6310
        // Extract the success payload. If the result type differs from the payload
6301 6311
        // type (e.g. `try?` wrapping `T` into `?T`), wrap the value.
6302 6312
        let payloadVal = tvalPayloadVal(self, base, okValueTy, RESULT_VAL_OFFSET);
6303 6313
        let mut okVal = payloadVal;
6304 6314
6810 6820
            args,
6811 6821
        });
6812 6822
        return il::Val::Reg(dst);
6813 6823
    }
6814 6824
    let mut dst: ?il::Reg = nil;
6815 -
    if retTy <> resolver::Type::Void {
6825 +
    if retTy <> resolver::Type::Void and retTy <> resolver::Type::Never {
6816 6826
        set dst = nextReg(self);
6817 6827
    }
6818 6828
    emit(self, il::Instr::Call {
6819 6829
        retTy: ilType(self.low, retTy),
6820 6830
        dst,
6821 6831
        func: callee,
6822 6832
        args,
6823 6833
    });
6824 6834
6835 +
    if retTy == resolver::Type::Never {
6836 +
        emit(self, il::Instr::Unreachable);
6837 +
    }
6825 6838
    if let d = dst {
6826 6839
        if isSmallAggregate(retTy) {
6827 6840
            let slot = emitReserveLayout(self, resolver::Layout {
6828 6841
                size: resolver::PTR_SIZE,
6829 6842
                alignment: resolver::PTR_SIZE,
7345 7358
            if resolver::isVoidUnion(typ) {
7346 7359
                return il::Type::W8;
7347 7360
            }
7348 7361
            return il::Type::W64;
7349 7362
        }
7350 -
        case resolver::Type::Void => return il::Type::W64,
7363 +
        case resolver::Type::Void, resolver::Type::Never => return il::Type::W64,
7351 7364
        // [`Type::Int`] is the type of unsuffixed integer literals and their
7352 7365
        // compound expressions (e.g. `1 + 2`). It defaults to W64 (i64) here,
7353 7366
        // matching the native word size on RV64. It cannot be resolved earlier
7354 7367
        // because the resolver uses [`Type::Int`] to distinguish unsuffixed
7355 7368
        // expressions from explicitly typed ones, which affects coercion
lib/std/lang/parser.rad +10 -2
1810 1810
        parseType
1811 1811
    );
1812 1812
    let mut returnType: ?*ast::Node = nil;
1813 1813
1814 1814
    if consume(p, scanner::TokenKind::Arrow) {
1815 -
        set returnType = try parseType(p);
1815 +
        set returnType = try parseReturnType(p);
1816 1816
    }
1817 1817
    let throwList = try parseThrowList(p);
1818 1818
    let sig = ast::FnSig { params, returnType, throwList };
1819 1819
    return node(p, ast::NodeValue::TypeSig(
1820 1820
        ast::TypeSig::Fn { sig, isUnsafe }
1838 1838
    }
1839 1839
    try expect(p, scanner::TokenKind::RParen, "expected `)` after function parameters");
1840 1840
1841 1841
    let mut returnType: ?*ast::Node = nil;
1842 1842
    if consume(p, scanner::TokenKind::Arrow) {
1843 -
        set returnType = try parseType(p);
1843 +
        set returnType = try parseReturnType(p);
1844 1844
    }
1845 1845
    let throwList = try parseThrowList(p);
1846 1846
1847 1847
    return ast::FnSig { params, returnType, throwList };
1848 1848
}
1849 1849
1850 +
/// Parse a function return type, including the uninhabited type.
1851 +
unsafe fn parseReturnType(p: &mut Parser) -> *ast::Node throws (ParseError) {
1852 +
    if consume(p, scanner::TokenKind::Bang) {
1853 +
        return node(p, ast::NodeValue::TypeSig(ast::TypeSig::Never));
1854 +
    }
1855 +
    return try parseType(p);
1856 +
}
1857 +
1850 1858
/// Parse a function declaration.
1851 1859
unsafe fn parseFnDecl(p: &mut Parser, attrs: ?ast::Attributes) -> *ast::Node
1852 1860
    throws (ParseError)
1853 1861
{
1854 1862
    try expect(p, scanner::TokenKind::Fn, "expected `fn`");
lib/std/lang/parser/tests.rad +14 -0
2961 2961
    let throwsNode = try! parseStmtStr("fn handle() throws (Error, Other,) {}");
2962 2962
    let case ast::NodeValue::FnDecl(throwsDecl) = throwsNode.value else throw testing::TestError::Failed;
2963 2963
    try testing::expect(throwsDecl.sig.throwList.len == 2);
2964 2964
}
2965 2965
2966 +
/// Function declarations and function types accept never return annotations.
2967 +
@test unsafe fn testParseNeverReturn() throws (testing::TestError) {
2968 +
    let declNode = try! parseStmtStr("fn stop() -> ! { panic; }");
2969 +
    let case ast::NodeValue::FnDecl(decl) = declNode.value else throw testing::TestError::Failed;
2970 +
    let ret = decl.sig.returnType else throw testing::TestError::Failed;
2971 +
    assert ret.value == ast::NodeValue::TypeSig(ast::TypeSig::Never);
2972 +
    let fnNode = try! parseTypeStr("unsafe fn(u64) -> ! throws (Error)");
2973 +
    let case ast::NodeValue::TypeSig(ast::TypeSig::Fn { sig, isUnsafe }) = fnNode.value
2974 +
        else throw testing::TestError::Failed;
2975 +
    let fnRet = sig.returnType else throw testing::TestError::Failed;
2976 +
    assert isUnsafe and sig.params.len == 1 and sig.throwList.len == 1;
2977 +
    assert fnRet.value == ast::NodeValue::TypeSig(ast::TypeSig::Never);
2978 +
}
2979 +
2966 2980
/// Unsafe function types preserve their call requirement and signature.
2967 2981
@test unsafe fn testParseUnsafeFunctionType() throws (testing::TestError) {
2968 2982
    let node = try! parseTypeStr("unsafe fn(&u32) -> u32 throws (Error)");
2969 2983
    let case ast::NodeValue::TypeSig(ast::TypeSig::Fn { sig, isUnsafe }) = node.value
2970 2984
        else throw testing::TestError::Failed;
lib/std/lang/resolver.rad +32 -8
1827 1827
unsafe fn isAssignable(self: &mut Resolver, to: Type, from: Type, rval: *ast::Node) -> ?Coercion {
1828 1828
    if to == Type::Unknown or from == Type::Unknown {
1829 1829
        return nil;
1830 1830
    }
1831 1831
    if from == Type::Undefined {
1832 +
        if to == Type::Never {
1833 +
            return nil;
1834 +
        }
1832 1835
        // TODO: Don't let `undefined` be used in place of functions and other
1833 1836
        // non-data types.
1834 1837
        return Coercion::Identity;
1835 1838
    }
1836 1839
    // The "never" type can always be assigned, since the code path is never
2987 2990
        },
2988 2991
        case ast::NodeValue::UnOp(unop) => return try resolveUnOp(self, node, unop),
2989 2992
        case ast::NodeValue::ExprStmt(expr) => {
2990 2993
            // Pass `Void` as expected type to indicate value is discarded.
2991 2994
            let exprTy = try visit(self, expr, Type::Void);
2992 -
            return setNodeType(self, node, unifyBranches(exprTy, Type::Void));
2995 +
            return setNodeType(self, node, Type::Never if exprTy == Type::Never else Type::Void);
2993 2996
        },
2994 2997
        case ast::NodeValue::TypeSig(sig) => return try inferTypeSig(self, node, sig),
2995 2998
        case ast::NodeValue::Super => {
2996 2999
            // `super` by itself is invalid, must be used in scope access.
2997 3000
            throw emitError(self, node, ErrorKind::InvalidModulePath);
3118 3121
unsafe fn resolveLet(self: &mut Resolver, node: *ast::Node, decl: ast::Let) -> Type
3119 3122
    throws (ResolveError)
3120 3123
{
3121 3124
    let mut alignment: u32 = 0; // Zero is default.
3122 3125
    let mut bindingTy = Type::Unknown;
3126 +
    let mut valueTy = Type::Unknown;
3123 3127
3124 3128
    // Check type.
3125 3129
    if let declTy = try visitOptional(self, decl.type, Type::Unknown) {
3126 -
        let _coercion = try checkAssignable(self, decl.value, declTy);
3130 +
        set valueTy = try checkAssignable(self, decl.value, declTy);
3127 3131
        set bindingTy = declTy;
3128 3132
    } else {
3129 3133
        set bindingTy = try infer(self, decl.value);
3134 +
        set valueTy = bindingTy;
3130 3135
3131 3136
        if not isTypeInferrable(bindingTy) {
3132 3137
            throw emitError(self, decl.value, ErrorKind::CannotInferType);
3133 3138
        }
3134 3139
    }
3156 3161
        throw emitError(self, decl.value, ErrorKind::InvalidAlignmentValue(alignment));
3157 3162
    }
3158 3163
    let _ = try bindValueIdent(self, decl.ident, node, bindingTy, decl.mutable, alignment, 0);
3159 3164
    setNodeType(self, decl.value, bindingTy);
3160 3165
3161 -
    return Type::Void;
3166 +
    return Type::Never if valueTy == Type::Never else Type::Void;
3162 3167
}
3163 3168
3164 3169
/// Check whether a node is an integer literal, optionally under unary negation.
3165 3170
fn isIntegerLiteralExpr(node: *ast::Node) -> bool {
3166 3171
    match node.value {
4630 4635
/// Analyze a traditional `while` loop.
4631 4636
unsafe fn resolveWhile(self: &mut Resolver, node: *ast::Node, loopNode: ast::While) -> Type
4632 4637
    throws (ResolveError)
4633 4638
{
4634 4639
    try checkBoolean(self, loopNode.condition);
4635 -
    try visitLoop(self, loopNode.body);
4640 +
    let loopTy = try visitLoop(self, loopNode.body);
4636 4641
    try visitOptional(self, loopNode.elseBranch, Type::Void);
4637 4642
4643 +
    if loopNode.condition.value == ast::NodeValue::Bool(true) {
4644 +
        return setNodeType(self, node, loopTy);
4645 +
    }
4638 4646
    return setNodeType(self, node, Type::Void);
4639 4647
}
4640 4648
4641 4649
/// Analyze a `while let` loop with pattern binding.
4642 4650
unsafe fn resolveWhileLet(self: &mut Resolver, node: *ast::Node, loopNode: ast::WhileLet) -> Type
6887 6895
        enterScope(self, node);
6888 6896
6889 6897
        let errTy = *calleeInfo.throwList[0];
6890 6898
        try bindValueIdent(self, binding, binding, errTy, false, 0, 0);
6891 6899
    }
6892 -
    try visit(self, first.body, resultTy);
6900 +
    let bodyTy = try visit(self, first.body, resultTy);
6893 6901
6894 6902
    if let _ = first.binding {
6895 6903
        exitScope(self);
6896 6904
    }
6897 6905
    try checkCatchBody(self, first.body, resultTy, hint);
6898 6906
6899 -
    return resultTy;
6907 +
    return bodyTy if resultTy == Type::Never else resultTy;
6900 6908
}
6901 6909
6902 6910
/// Resolve typed catch clauses (`catch e as T {..} catch e as S {..}`).
6903 6911
///
6904 6912
/// Validates that each type annotation is in the callee's throw list, that
6912 6920
    hint: Type
6913 6921
) -> Type throws (ResolveError) {
6914 6922
    // Track which of the callee's throw types have been covered.
6915 6923
    let mut covered: [bool; MAX_FN_THROWS] = [false; MAX_FN_THROWS];
6916 6924
    let mut hasCatchAll = false;
6925 +
    let mut catchTy = Type::Never;
6917 6926
6918 6927
    for clauseNode in catches {
6919 6928
        let case ast::NodeValue::CatchClause(clause) = clauseNode.value else
6920 6929
            throw emitError(self, node, ErrorKind::UnexpectedNode(clauseNode));
6921 6930
6946 6955
        } else {
6947 6956
            // Catch-all clause with no type annotation or binding.
6948 6957
            set hasCatchAll = true;
6949 6958
        }
6950 6959
        // Resolve the catch body and check assignability.
6951 -
        try visit(self, clause.body, resultTy);
6960 +
        let bodyTy = try visit(self, clause.body, resultTy);
6961 +
        if bodyTy <> Type::Never { set catchTy = Type::Void; }
6952 6962
        // Only typed clauses can have bindings.
6953 6963
        if let _ = clause.binding {
6954 6964
            exitScope(self);
6955 6965
        }
6956 6966
        try checkCatchBody(self, clause.body, resultTy, hint);
6962 6972
            if not covered[i] {
6963 6973
                throw emitError(self, node, ErrorKind::TryCatchNonExhaustive);
6964 6974
            }
6965 6975
        }
6966 6976
    }
6967 -
    return resultTy;
6977 +
    return catchTy if resultTy == Type::Never else resultTy;
6968 6978
}
6969 6979
6970 6980
/// Analyze a `throw` statement.
6971 6981
unsafe fn resolveThrow(self: &mut Resolver, node: *ast::Node, expr: *ast::Node) -> Type
6972 6982
    throws (ResolveError)
7335 7345
{
7336 7346
    match sig {
7337 7347
        case ast::TypeSig::Void => {
7338 7348
            return Type::Void;
7339 7349
        }
7350 +
        case ast::TypeSig::Never => {
7351 +
            return Type::Never;
7352 +
        }
7340 7353
        case ast::TypeSig::Opaque => {
7341 7354
            return Type::Opaque;
7342 7355
        }
7343 7356
        case ast::TypeSig::Bool => {
7344 7357
            return Type::Bool;
7764 7777
/// Require all available exact-use bindings to be consumed at a function exit.
7765 7778
unsafe fn finishLinearExit(
7766 7779
    checker: &mut LinearChecker,
7767 7780
    env: &mut LinearEnv,
7768 7781
) throws (ResolveError) {
7782 +
    if env.terminated {
7783 +
        return;
7784 +
    }
7769 7785
    for i in 0..env.len {
7770 7786
        if linearBindingAvailable(env, i) {
7771 7787
            let sym = env.symbols[i];
7772 7788
            let case SymbolData::Value { type: ty, .. } = sym.data
7773 7789
                else panic "finishLinearExit: expected value symbol";
8192 8208
            try checkLinearNode(checker, env, arg, LinearUse::Borrow);
8193 8209
        } else {
8194 8210
            try checkLinearNode(checker, env, arg, LinearUse::Consume);
8195 8211
        }
8196 8212
    }
8213 +
    if *info.returnType == Type::Never and info.throwList.len == 0 {
8214 +
        set env.terminated = true;
8215 +
    }
8197 8216
}
8198 8217
8199 8218
/// Check a pattern conditional. Linear scrutinees require an exhaustive match.
8200 8219
unsafe fn checkLinearIfLet(
8201 8220
    checker: &mut LinearChecker,
8491 8510
            try checkLinearMatch(checker, env, node, matchExpr);
8492 8511
        }
8493 8512
        case ast::NodeValue::Try(tryExpr) => {
8494 8513
            try checkLinearNode(checker, env, tryExpr.expr, usage);
8495 8514
            let success = *env;
8515 +
            if let resultTy = typeFor(checker.resolver, tryExpr.expr); resultTy == Type::Never {
8516 +
                if not tryExpr.returnsOptional and (tryExpr.catches.len > 0 or tryExpr.shouldPanic) {
8517 +
                    set env.terminated = true;
8518 +
                }
8519 +
            }
8496 8520
            for catchNode in tryExpr.catches {
8497 8521
                let case ast::NodeValue::CatchClause(catchClause) = catchNode.value
8498 8522
                    else panic "checkLinearNode: expected catch";
8499 8523
                let mut branch = success;
8500 8524
                let start = branch.len;
lib/std/lang/resolver/tests.rad +65 -0
1743 1743
        let result = try resolveProgramStr(&mut a, program);
1744 1744
        try expectErrorKind(&result, super::ErrorKind::FnMissingReturn);
1745 1745
    }
1746 1746
}
1747 1747
1748 +
/// Never-returning functions require divergence on every path.
1749 +
@test unsafe fn testResolveNeverReturn() throws (testing::TestError) {
1750 +
    for program in &[
1751 +
        "fn stop() -> ! { panic; } fn f() -> i32 { stop(); }",
1752 +
        "fn stop() -> ! { panic; } fn f() -> ! { return stop(); }",
1753 +
        "fn f(callback: fn() -> !) -> ! { callback(); }",
1754 +
        "fn stop() -> ! { panic; } fn f() -> ! { let _ = stop(); }",
1755 +
        "fn stop() -> ! { panic; } fn f() -> i32 { let value: i32 = stop(); }",
1756 +
        "union Token: Once { Held(u32) } fn stop() -> ! { panic; } fn f(token: Token) -> ! { stop(); }",
1757 +
        "union Token: Once { Held(u32) } fn stop() -> ! { panic; } fn f(token: Token) -> ! { return stop(); }",
1758 +
        "union Token: Once { Held(u32) } fn stop() -> ! throws (i32) { throw 1; } fn f(token: Token) -> ! { try! stop(); }",
1759 +
        "fn f() -> ! { while true {} }",
1760 +
        "fn f(flag: bool) -> ! { if flag { panic; } else { while true {} } }",
1761 +
        "fn f() -> ! throws (i32) { throw 1; } fn g() -> ! throws (i32) { try f(); }",
1762 +
        "fn f() -> ! throws (i32) { throw 1; } fn g() -> i32 { try f() catch { return 2; }; }",
1763 +
    ] {
1764 +
        let mut a = testResolver();
1765 +
        let result = try resolveProgramStr(&mut a, program);
1766 +
        try expectNoErrors(&result);
1767 +
    }
1768 +
}
1769 +
1770 +
/// A never-returning signature rejects any normal completion path.
1771 +
@test unsafe fn testResolveNeverFallthrough() throws (testing::TestError) {
1772 +
    for program in &[
1773 +
        "fn f() -> ! {}",
1774 +
        "fn f(flag: bool) -> ! { if flag { panic; } }",
1775 +
        "fn f() -> ! { while true { break; } }",
1776 +
        "fn f() -> ! throws (i32) { throw 1; } fn g() -> ! { try f() catch {}; }",
1777 +
        "fn f() -> ! throws (i32) { throw 1; } fn g() -> ! { try f() catch e as i32 {}; }",
1778 +
    ] {
1779 +
        let mut a = testResolver();
1780 +
        let result = try resolveProgramStr(&mut a, program);
1781 +
        try expectErrorKind(&result, super::ErrorKind::FnMissingReturn);
1782 +
    }
1783 +
}
1784 +
1785 +
/// No value, including undefined, can construct a never return value.
1786 +
@test unsafe fn testResolveNeverValue() throws (testing::TestError) {
1787 +
    for program in &[
1788 +
        "fn f() -> ! { return; }",
1789 +
        "fn f() -> ! { return 1; }",
1790 +
        "unsafe fn f() -> ! { return undefined; }",
1791 +
        "fn ordinary() {} fn f() { let callback: fn() -> ! = ordinary; }",
1792 +
    ] {
1793 +
        let mut a = testResolver();
1794 +
        let result = try resolveProgramStr(&mut a, program);
1795 +
        let error = try expectError(&result);
1796 +
    }
1797 +
}
1798 +
1799 +
/// Caught errors preserve the caller's exact-use ownership obligations.
1800 +
@test unsafe fn testResolveNeverCaughtOwnership() throws (testing::TestError) {
1801 +
    for program in &[
1802 +
        "union Token: Once { Held(u32) } fn fail() -> ! throws (i32) { throw 1; } fn f(token: Token) { try fail() catch {}; }",
1803 +
        "union Token: Once { Held(u32) } fn fail() -> ! throws (i32) { throw 1; } fn f(token: Token) { let absent = try? fail(); }",
1804 +
    ] {
1805 +
        let mut a = testResolver();
1806 +
        let result = try resolveProgramStr(&mut a, program);
1807 +
        let error = try expectError(&result);
1808 +
        let case super::ErrorKind::LinearNotConsumed(_) = error.kind
1809 +
            else throw testing::TestError::Failed;
1810 +
    }
1811 +
}
1812 +
1748 1813
@test unsafe fn testResolveFnAllPathsReturn() throws (testing::TestError) {
1749 1814
    let mut a = testResolver();
1750 1815
    let program = "fn h(flag: bool) -> i32 { if flag { return 1; } else { return 2; } }";
1751 1816
    let result = try resolveProgramStr(&mut a, program);
1752 1817
    try expectNoErrors(&result);
test/tests/return.never.catch.rad added +30 -0
1 +
//! returns: 0
2 +
3 +
/// Error returned instead of a success value.
4 +
union Error: Copy { Failure }
5 +
6 +
/// Complete only through an error.
7 +
fn fail() -> ! throws (Error) { throw Error::Failure; }
8 +
9 +
/// Propagate an error without a normal return.
10 +
fn forward() -> ! throws (Error) { try fail(); }
11 +
12 +
/// Recover from the only possible completion of a throwing call.
13 +
fn recover() -> u64 {
14 +
    try forward() catch error {
15 +
        return 42;
16 +
    };
17 +
}
18 +
19 +
@default fn main() -> u64 {
20 +
    assert recover() == 42;
21 +
    let mut caught = false;
22 +
    try fail() catch { set caught = true; };
23 +
    assert caught;
24 +
    set caught = false;
25 +
    try fail() catch error as Error { set caught = true; };
26 +
    assert caught;
27 +
    let absent = try? fail();
28 +
    assert absent == nil;
29 +
    return 0;
30 +
}
test/tests/return.never.flow.rad added +22 -0
1 +
/// Terminate execution.
2 +
fn stop() -> ! { panic; }
3 +
4 +
/// Forward a diverging expression from a value-returning function.
5 +
fn value() -> u64 {
6 +
    return stop();
7 +
}
8 +
9 +
/// Preserve the returning branch when the other branch diverges.
10 +
fn select(flag: bool) -> u64 {
11 +
    if flag { stop(); }
12 +
    return 42;
13 +
}
14 +
15 +
/// A loop without a break cannot complete normally.
16 +
fn spin() -> ! { while true {} }
17 +
18 +
/// Indirect calls preserve the callee's never-returning contract.
19 +
fn indirect(callback: fn() -> !) -> ! { callback(); }
20 +
21 +
/// A diverging initializer prevents normal completion.
22 +
fn initialized() -> u64 { let value: u64 = stop(); }
test/tests/return.never.flow.ril added +44 -0
1 +
fn w64 $stop() {
2 +
  @entry0
3 +
    unreachable;
4 +
}
5 +
6 +
fn w64 $value() {
7 +
  @entry0
8 +
    call w64 $stop();
9 +
    unreachable;
10 +
}
11 +
12 +
fn w64 $select(w8 %0) {
13 +
  @entry0
14 +
    br.ne w32 %0 0 @then1 @merge2;
15 +
  @then1
16 +
    call w64 $stop();
17 +
    unreachable;
18 +
  @merge2
19 +
    ret 42;
20 +
}
21 +
22 +
fn w64 $spin() {
23 +
  @entry0
24 +
    jmp @while1;
25 +
  @while1
26 +
    copy %0 1;
27 +
    br.ne w32 %0 0 @body2 @merge3;
28 +
  @body2
29 +
    jmp @while1;
30 +
  @merge3
31 +
    unreachable;
32 +
}
33 +
34 +
fn w64 $indirect(w64 %0) {
35 +
  @entry0
36 +
    call w64 %0();
37 +
    unreachable;
38 +
}
39 +
40 +
fn w64 $initialized() {
41 +
  @entry0
42 +
    call w64 $stop();
43 +
    unreachable;
44 +
}
test/tests/return.never.rad added +14 -0
1 +
//! returns: 23
2 +
3 +
/// Exit the process with the requested status.
4 +
fn exit(code: u64) -> !;
5 +
6 +
/// Terminate execution without a return value.
7 +
fn stop() -> ! { exit(23); }
8 +
9 +
/// Call a function that cannot return through a function pointer.
10 +
fn invoke(callback: fn() -> !) -> ! { callback(); }
11 +
12 +
@default fn main() -> u64 {
13 +
    invoke(stop);
14 +
}
test/tests/return.never.ras added +6 -0
1 +
.text;
2 +
// Terminate the hosted process with the code in a0.
3 +
.export @"return.never::exit";
4 +
@"return.never::exit"
5 +
    li %a7 93;
6 +
    ecall;