Support never-returning function signatures
db5ee0c49a6ab67dbb0bdcfe1a75cae4d759d50a3e9dc968283864ed631c4245
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; |