Merge pull request #4460 from roc-lang/crash

Crash
This commit is contained in:
Richard Feldman 2022-11-25 17:18:21 -05:00 committed by GitHub
commit 58fad36f9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 1247 additions and 455 deletions

View file

@ -640,10 +640,13 @@ fn stmt_spec<'a>(
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
RuntimeError(_) => {
let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
Crash(msg, _) => {
// Model this as a foreign call rather than TERMINATE because
// we want ownership of the message.
let result_type =
layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
builder.add_terminate(block, type_id)
builder.add_unknown_with(block, &[env.symbols[msg]], result_type)
}
}
}

View file

@ -7,7 +7,7 @@ const math = std.math;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const RocStr = str.RocStr;
const WithOverflow = utils.WithOverflow;
const roc_panic = utils.panic;
const roc_panic = @import("panic.zig").panic_help;
const U256 = num_.U256;
const mul_u128 = num_.mul_u128;
@ -233,7 +233,7 @@ pub const RocDec = extern struct {
const answer = RocDec.addWithOverflow(self, other);
if (answer.has_overflowed) {
roc_panic("Decimal addition overflowed!", 1);
roc_panic("Decimal addition overflowed!", 0);
unreachable;
} else {
return answer.value;
@ -265,7 +265,7 @@ pub const RocDec = extern struct {
const answer = RocDec.subWithOverflow(self, other);
if (answer.has_overflowed) {
roc_panic("Decimal subtraction overflowed!", 1);
roc_panic("Decimal subtraction overflowed!", 0);
unreachable;
} else {
return answer.value;
@ -329,7 +329,7 @@ pub const RocDec = extern struct {
const answer = RocDec.mulWithOverflow(self, other);
if (answer.has_overflowed) {
roc_panic("Decimal multiplication overflowed!", 1);
roc_panic("Decimal multiplication overflowed!", 0);
unreachable;
} else {
return answer.value;

View file

@ -3,6 +3,7 @@ const builtin = @import("builtin");
const math = std.math;
const utils = @import("utils.zig");
const expect = @import("expect.zig");
const panic_utils = @import("panic.zig");
const ROC_BUILTINS = "roc_builtins";
const NUM = "num";
@ -166,7 +167,7 @@ comptime {
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
@export(panic_utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
if (builtin.target.cpu.arch != .wasm32) {
exportUtilsFn(expect.expectFailedStartSharedBuffer, "expect_failed_start_shared_buffer");

View file

@ -4,7 +4,7 @@ const math = std.math;
const RocList = @import("list.zig").RocList;
const RocStr = @import("str.zig").RocStr;
const WithOverflow = @import("utils.zig").WithOverflow;
const roc_panic = @import("utils.zig").panic;
const roc_panic = @import("panic.zig").panic_help;
pub fn NumParseResult(comptime T: type) type {
// on the roc side we sort by alignment; putting the errorcode last
@ -284,7 +284,7 @@ pub fn exportAddOrPanic(comptime T: type, comptime name: []const u8) void {
fn func(self: T, other: T) callconv(.C) T {
const result = addWithOverflow(T, self, other);
if (result.has_overflowed) {
roc_panic("integer addition overflowed!", 1);
roc_panic("integer addition overflowed!", 0);
unreachable;
} else {
return result.value;
@ -343,7 +343,7 @@ pub fn exportSubOrPanic(comptime T: type, comptime name: []const u8) void {
fn func(self: T, other: T) callconv(.C) T {
const result = subWithOverflow(T, self, other);
if (result.has_overflowed) {
roc_panic("integer subtraction overflowed!", 1);
roc_panic("integer subtraction overflowed!", 0);
unreachable;
} else {
return result.value;
@ -451,7 +451,7 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
fn func(self: T, other: T) callconv(.C) T {
const result = @call(.{ .modifier = always_inline }, mulWithOverflow, .{ T, W, self, other });
if (result.has_overflowed) {
roc_panic("integer multiplication overflowed!", 1);
roc_panic("integer multiplication overflowed!", 0);
unreachable;
} else {
return result.value;

View file

@ -0,0 +1,16 @@
const std = @import("std");
const RocStr = @import("str.zig").RocStr;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
// Signals to the host that the program has panicked
extern fn roc_panic(msg: *const RocStr, tag_id: u32) callconv(.C) void;
pub fn panic_help(msg: []const u8, tag_id: u32) void {
var str = RocStr.init(msg.ptr, msg.len);
roc_panic(&str, tag_id);
}
// must export this explicitly because right now it is not used from zig code
pub fn panic(msg: *const RocStr, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ msg, alignment });
}

View file

@ -16,9 +16,6 @@ extern fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, align
// This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void;
// Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *const anyopaque, tag_id: u32) callconv(.C) void;
// should work just like libc memcpy (we can't assume libc is present)
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
@ -108,11 +105,6 @@ pub fn dealloc(c_ptr: [*]u8, alignment: u32) void {
return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment });
}
// must export this explicitly because right now it is not used from zig code
pub fn panic(c_ptr: *const anyopaque, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
}
pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
@call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size });
}

View file

@ -376,6 +376,10 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
*called_via,
)
}
Crash { msg, ret_var } => Crash {
msg: Box::new(msg.map(|m| go_help!(m))),
ret_var: sub!(*ret_var),
},
RunLowLevel { op, args, ret_var } => RunLowLevel {
op: *op,
args: args

View file

@ -166,6 +166,12 @@ pub enum Expr {
/// Empty record constant
EmptyRecord,
/// The "crash" keyword
Crash {
msg: Box<Loc<Expr>>,
ret_var: Variable,
},
/// Look up exactly one field on a record, e.g. (expr).foo.
Access {
record_var: Variable,
@ -309,6 +315,7 @@ impl Expr {
}
Self::Expect { .. } => Category::Expect,
Self::ExpectFx { .. } => Category::Expect,
Self::Crash { .. } => Category::Crash,
Self::Dbg { .. } => Category::Expect,
@ -784,6 +791,47 @@ pub fn canonicalize_expr<'a>(
}
}
}
} else if let ast::Expr::Crash = loc_fn.value {
// We treat crash specially, since crashing must be applied with one argument.
debug_assert!(!args.is_empty());
let mut args = Vec::new();
let mut output = Output::default();
for loc_arg in loc_args.iter() {
let (arg_expr, arg_out) =
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
args.push(arg_expr);
output.references.union_mut(&arg_out.references);
}
let crash = if args.len() > 1 {
let args_region = Region::span_across(
&loc_args.first().unwrap().region,
&loc_args.last().unwrap().region,
);
env.problem(Problem::OverAppliedCrash {
region: args_region,
});
// Still crash, just with our own message, and drop the references.
Crash {
msg: Box::new(Loc::at(
region,
Expr::Str(String::from("hit a crash!").into_boxed_str()),
)),
ret_var: var_store.fresh(),
}
} else {
let msg = args.pop().unwrap();
Crash {
msg: Box::new(msg),
ret_var: var_store.fresh(),
}
};
(crash, output)
} else {
// Canonicalize the function expression and its arguments
let (fn_expr, fn_expr_output) =
@ -874,6 +922,22 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default())
}
ast::Expr::Crash => {
// Naked crashes aren't allowed; we'll admit this with our own message, but yield an
// error.
env.problem(Problem::UnappliedCrash { region });
(
Crash {
msg: Box::new(Loc::at(
region,
Expr::Str(String::from("hit a crash!").into_boxed_str()),
)),
ret_var: var_store.fresh(),
},
Output::default(),
)
}
ast::Expr::Defs(loc_defs, loc_ret) => {
// The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| {
@ -1728,7 +1792,8 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ RunLowLevel { .. }
| other @ TypedHole { .. }
| other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_) => other,
| other @ OpaqueWrapFunction(_)
| other @ Crash { .. } => other,
List {
elem_var,
@ -2826,6 +2891,7 @@ fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
// Intentionally ignore the lookups in the nested `expect` condition itself,
// because they couldn't possibly influence the outcome of this `expect`!
}
Expr::Crash { msg, .. } => stack.push(&msg.value),
Expr::Num(_, _, _, _)
| Expr::Float(_, _, _, _, _)
| Expr::Int(_, _, _, _, _)

View file

@ -981,6 +981,14 @@ fn fix_values_captured_in_closure_expr(
);
}
Crash { msg, ret_var: _ } => {
fix_values_captured_in_closure_expr(
&mut msg.value,
no_capture_symbols,
closure_captures,
);
}
Closure(ClosureData {
captured_symbols,
name,

View file

@ -138,7 +138,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc
| MalformedClosure
| PrecedenceConflict { .. }
| Tag(_)
| OpaqueRef(_) => loc_expr,
| OpaqueRef(_)
| Crash => loc_expr,
TupleAccess(_sub_expr, _paths) => todo!("Handle TupleAccess"),
RecordAccess(sub_expr, paths) => {

View file

@ -203,6 +203,9 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
let (fn_var, loc_fn, _closure_var, _ret_var) = &**f;
walk_call(visitor, *fn_var, loc_fn, args);
}
Expr::Crash { msg, .. } => {
visitor.visit_expr(&msg.value, msg.region, Variable::STR);
}
Expr::RunLowLevel {
op: _,
args,

View file

@ -482,6 +482,28 @@ pub fn constrain_expr(
let and_constraint = constraints.and_constraint(and_cons);
constraints.exists(vars, and_constraint)
}
Expr::Crash { msg, ret_var } => {
let str_index = constraints.push_type(types, Types::STR);
let expected_msg = constraints.push_expected_type(Expected::ForReason(
Reason::CrashArg,
str_index,
msg.region,
));
let msg_is_str = constrain_expr(
types,
constraints,
env,
msg.region,
&msg.value,
expected_msg,
);
let magic = constraints.equal_types_var(*ret_var, expected, Category::Crash, region);
let and = constraints.and_constraint([msg_is_str, magic]);
constraints.exists([*ret_var], and)
}
Var(symbol, variable) => {
// Save the expectation in the variable, then lookup the symbol's type in the environment
let expected_type = *constraints[expected].get_type_ref();

View file

@ -43,7 +43,8 @@ impl<'a> Formattable for Expr<'a> {
| MalformedIdent(_, _)
| MalformedClosure
| Tag(_)
| OpaqueRef(_) => false,
| OpaqueRef(_)
| Crash => false,
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
@ -191,6 +192,10 @@ impl<'a> Formattable for Expr<'a> {
buf.push('_');
buf.push_str(name);
}
Crash => {
buf.indent(indent);
buf.push_str("crash");
}
Apply(loc_expr, loc_args, _) => {
buf.indent(indent);
if apply_needs_parens && !loc_args.is_empty() {

View file

@ -666,6 +666,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Crash => Expr::Crash,
Expr::Defs(a, b) => {
let mut defs = a.clone();
defs.space_before = vec![Default::default(); defs.len()];

View file

@ -5829,6 +5829,42 @@ mod test_fmt {
);
}
#[test]
fn format_crash() {
expr_formats_same(indoc!(
r#"
_ = crash
_ = crash ""
crash "" ""
"#
));
expr_formats_to(
indoc!(
r#"
_ = crash
_ = crash ""
_ = crash "" ""
try
foo
(\_ -> crash "")
"#
),
indoc!(
r#"
_ = crash
_ = crash ""
_ = crash "" ""
try
foo
(\_ -> crash "")
"#
),
);
}
// this is a parse error atm
// #[test]
// fn multiline_apply() {

View file

@ -1110,7 +1110,7 @@ trait Backend<'a> {
Stmt::Expect { .. } => todo!("expect is not implemented in the dev backend"),
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the dev backend"),
Stmt::RuntimeError(_) => {}
Stmt::Crash(..) => todo!("crash is not implemented in the dev backend"),
}
}

View file

@ -39,8 +39,8 @@ use roc_debug_flags::dbg_do;
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{
BranchInfo, CallType, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc, OptLevel,
ProcLayout,
BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc,
OptLevel, ProcLayout,
};
use roc_mono::layout::{
Builtin, CapturesNiche, LambdaName, LambdaSet, Layout, LayoutIds, RawFunctionLayout,
@ -183,22 +183,6 @@ pub struct Env<'a, 'ctx, 'env> {
pub exposed_to_host: MutSet<Symbol>,
}
#[repr(u32)]
pub enum PanicTagId {
NullTerminatedString = 0,
}
impl std::convert::TryFrom<u32> for PanicTagId {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(PanicTagId::NullTerminatedString),
_ => Err(()),
}
}
}
impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
/// The integer type representing a pointer
///
@ -344,16 +328,33 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
)
}
pub fn call_panic(&self, message: PointerValue<'ctx>, tag_id: PanicTagId) {
pub fn call_panic(
&self,
env: &Env<'a, 'ctx, 'env>,
message: BasicValueEnum<'ctx>,
tag: CrashTag,
) {
let function = self.module.get_function("roc_panic").unwrap();
let tag_id = self
.context
.i32_type()
.const_int(tag_id as u32 as u64, false);
let tag_id = self.context.i32_type().const_int(tag as u32 as u64, false);
let msg = match env.target_info.ptr_width() {
PtrWidth::Bytes4 => {
// we need to pass the message by reference, but we currently hold the value.
let alloca = env
.builder
.build_alloca(message.get_type(), "alloca_panic_msg");
env.builder.build_store(alloca, message);
alloca.into()
}
PtrWidth::Bytes8 => {
// string is already held by reference
message
}
};
let call = self
.builder
.build_call(function, &[message.into(), tag_id.into()], "roc_panic");
.build_call(function, &[msg.into(), tag_id.into()], "roc_panic");
call.set_call_convention(C_CALL_CONV);
}
@ -750,7 +751,15 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
}
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(),
Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => {
Str(str_literal) => build_string_literal(env, parent, str_literal),
}
}
fn build_string_literal<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
str_literal: &str,
) -> BasicValueEnum<'ctx> {
if str_literal.len() < env.small_str_bytes() as usize {
match env.small_str_bytes() {
24 => small_str_ptr_width_8(env, parent, str_literal).into(),
@ -761,16 +770,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
let ptr = define_global_str_literal_ptr(env, str_literal);
let number_of_elements = env.ptr_int().const_int(str_literal.len() as u64, false);
let alloca =
const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
let alloca = const_str_alloca_ptr(env, parent, ptr, number_of_elements, number_of_elements);
match env.target_info.ptr_width() {
PtrWidth::Bytes4 => env.builder.build_load(alloca, "load_const_str"),
PtrWidth::Bytes8 => alloca.into(),
}
}
}
}
}
fn const_str_alloca_ptr<'a, 'ctx, 'env>(
@ -2621,7 +2627,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
}
roc_target::PtrWidth::Bytes4 => {
// temporary WASM implementation
throw_exception(env, "An expectation failed!");
throw_internal_exception(env, parent, "An expectation failed!");
}
}
} else {
@ -2683,7 +2689,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
}
roc_target::PtrWidth::Bytes4 => {
// temporary WASM implementation
throw_exception(env, "An expectation failed!");
throw_internal_exception(env, parent, "An expectation failed!");
}
}
} else {
@ -2703,8 +2709,8 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
)
}
RuntimeError(error_msg) => {
throw_exception(env, error_msg);
Crash(sym, tag) => {
throw_exception(env, scope, sym, *tag);
// unused value (must return a BasicValue)
let zero = env.context.i64_type().const_zero();
@ -3336,7 +3342,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
builder.position_at_end(entry);
let wrapped_layout = roc_result_layout(env.arena, return_layout, env.target_info);
let wrapped_layout = roc_call_result_layout(env.arena, return_layout, env.target_info);
call_roc_function(env, roc_function, &wrapped_layout, arguments_for_call)
} else {
call_roc_function(env, roc_function, &return_layout, arguments_for_call)
@ -3366,7 +3372,8 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
// a tagged union to indicate to the test loader that a panic occurred.
// especially when running 32-bit binaries on a 64-bit machine, there
// does not seem to be a smarter solution
let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout));
let wrapper_return_type =
roc_call_result_type(env, basic_type_from_layout(env, &return_layout));
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
for layout in arguments {
@ -3755,7 +3762,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let return_type = match env.mode {
LlvmBackendMode::GenTest | LlvmBackendMode::WasmGenTest | LlvmBackendMode::CliTest => {
roc_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
@ -3862,14 +3869,29 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
}
}
/// Pointer to pointer of the panic message.
/// Pointer to RocStr which is the panic message.
pub fn get_panic_msg_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let ptr_to_u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let str_typ = zig_str_type(env);
let global_name = "roc_panic_msg_ptr";
let global_name = "roc_panic_msg_str";
let global = env.module.get_global(global_name).unwrap_or_else(|| {
let global = env.module.add_global(ptr_to_u8_ptr, None, global_name);
global.set_initializer(&ptr_to_u8_ptr.const_zero());
let global = env.module.add_global(str_typ, None, global_name);
global.set_initializer(&str_typ.const_zero());
global
});
global.as_pointer_value()
}
/// Pointer to the panic tag.
/// Only non-zero values must be written into here.
pub fn get_panic_tag_ptr<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
let i64_typ = env.context.i64_type();
let global_name = "roc_panic_msg_tag";
let global = env.module.get_global(global_name).unwrap_or_else(|| {
let global = env.module.add_global(i64_typ, None, global_name);
global.set_initializer(&i64_typ.const_zero());
global
});
@ -3887,7 +3909,7 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
let builder = env.builder;
let return_type = basic_type_from_layout(env, &return_layout);
let call_result_type = roc_result_type(env, return_type.as_basic_type_enum());
let call_result_type = roc_call_result_type(env, return_type.as_basic_type_enum());
let result_alloca = builder.build_alloca(call_result_type, "result");
let then_block = context.append_basic_block(parent, "then_block");
@ -3922,26 +3944,21 @@ fn set_jump_and_catch_long_jump<'a, 'ctx, 'env>(
{
builder.position_at_end(catch_block);
let error_msg = {
// u8**
let ptr_int_ptr = get_panic_msg_ptr(env);
// u8* again
builder.build_load(ptr_int_ptr, "ptr_int")
};
// RocStr* global
let error_msg_ptr = get_panic_msg_ptr(env);
// i64* global
let error_tag_ptr = get_panic_tag_ptr(env);
let return_value = {
let v1 = call_result_type.const_zero();
// flag is non-zero, indicating failure
let flag = context.i64_type().const_int(1, false);
// tag must be non-zero, indicating failure
let tag = builder.build_load(error_tag_ptr, "load_panic_tag");
let v2 = builder
.build_insert_value(v1, flag, 0, "set_error")
.unwrap();
let v2 = builder.build_insert_value(v1, tag, 0, "set_error").unwrap();
let v3 = builder
.build_insert_value(v2, error_msg, 1, "set_exception")
.build_insert_value(v2, error_msg_ptr, 1, "set_exception")
.unwrap();
v3
};
@ -3971,7 +3988,7 @@ fn make_exception_catcher<'a, 'ctx, 'env>(
function_value
}
fn roc_result_layout<'a>(
fn roc_call_result_layout<'a>(
arena: &'a Bump,
return_layout: Layout<'a>,
target_info: TargetInfo,
@ -3981,14 +3998,14 @@ fn roc_result_layout<'a>(
Layout::struct_no_name_order(arena.alloc(elements))
}
fn roc_result_type<'a, 'ctx, 'env>(
fn roc_call_result_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
return_type: BasicTypeEnum<'ctx>,
) -> StructType<'ctx> {
env.context.struct_type(
&[
env.context.i64_type().into(),
env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
zig_str_type(env).ptr_type(AddressSpace::Generic).into(),
return_type,
],
false,
@ -4003,7 +4020,7 @@ fn make_good_roc_result<'a, 'ctx, 'env>(
let context = env.context;
let builder = env.builder;
let v1 = roc_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero();
let v1 = roc_call_result_type(env, basic_type_from_layout(env, &return_layout)).const_zero();
let v2 = builder
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
@ -4050,7 +4067,8 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
}
};
let wrapper_return_type = roc_result_type(env, basic_type_from_layout(env, &return_layout));
let wrapper_return_type =
roc_call_result_type(env, basic_type_from_layout(env, &return_layout));
// argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
@ -5520,51 +5538,33 @@ fn define_global_str_literal<'a, 'ctx, 'env>(
}
}
fn define_global_error_str<'a, 'ctx, 'env>(
pub(crate) fn throw_internal_exception<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
message: &str,
) -> inkwell::values::GlobalValue<'ctx> {
let module = env.module;
// hash the name so we don't re-define existing messages
let name = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
message.hash(&mut hasher);
let hash = hasher.finish();
format!("_Error_message_{}", hash)
};
match module.get_global(&name) {
Some(current) => current,
None => unsafe { env.builder.build_global_string(message, name.as_str()) },
}
}
pub(crate) fn throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, message: &str) {
) {
let builder = env.builder;
// define the error message as a global
// (a hash is used such that the same value is not defined repeatedly)
let error_msg_global = define_global_error_str(env, message);
let str = build_string_literal(env, parent, message);
let cast = env
.builder
.build_bitcast(
error_msg_global.as_pointer_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
"cast_void",
)
.into_pointer_value();
env.call_panic(cast, PanicTagId::NullTerminatedString);
env.call_panic(env, str, CrashTag::Roc);
builder.build_unreachable();
}
pub(crate) fn throw_exception<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &mut Scope<'a, 'ctx>,
message: &Symbol,
tag: CrashTag,
) {
let msg_val = load_symbol(scope, message);
env.call_panic(env, msg_val, tag);
env.builder.build_unreachable();
}
fn get_foreign_symbol<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
foreign_symbol: roc_module::ident::ForeignSymbol,

View file

@ -1,5 +1,5 @@
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{add_func, get_panic_msg_ptr, C_CALL_CONV};
use crate::llvm::build::{add_func, get_panic_msg_ptr, get_panic_tag_ptr, C_CALL_CONV};
use crate::llvm::build::{CCReturn, Env, FunctionSpec};
use inkwell::module::Linkage;
use inkwell::types::BasicType;
@ -193,10 +193,9 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_panic").unwrap();
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
let roc_str_arg = params.next().unwrap();
// in debug mode, this is assumed to be NullTerminatedString
let _tag_id_arg = params.next().unwrap();
let tag_id_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
@ -210,8 +209,38 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
builder.position_at_end(entry);
// write our error message pointer
env.builder.build_store(get_panic_msg_ptr(env), ptr_arg);
// write our error message to the RocStr pointer
{
let loaded_roc_str = match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes4 => roc_str_arg,
// On 64-bit we pass RocStrs by reference internally
roc_target::PtrWidth::Bytes8 => {
builder.build_load(roc_str_arg.into_pointer_value(), "load_roc_str")
}
};
env.builder
.build_store(get_panic_msg_ptr(env), loaded_roc_str);
}
// write the panic tag.
// increment by 1, since the tag we'll get from the Roc program is 0-based,
// but we use 0 for marking a successful call.
{
let cast_tag_id = builder.build_int_z_extend(
tag_id_arg.into_int_value(),
env.context.i64_type(),
"zext_panic_tag",
);
let inc_tag_id = builder.build_int_add(
cast_tag_id,
env.context.i64_type().const_int(1, false),
"inc_panic_tag",
);
env.builder.build_store(get_panic_tag_ptr(env), inc_tag_id);
}
build_longjmp_call(env);

View file

@ -41,9 +41,9 @@ use crate::llvm::{
},
};
use super::convert::zig_with_overflow_roc_dec;
use super::{build::throw_internal_exception, convert::zig_with_overflow_roc_dec};
use super::{
build::{load_symbol, load_symbol_and_layout, throw_exception, Env, Scope},
build::{load_symbol, load_symbol_and_layout, Env, Scope},
convert::zig_dec_type,
};
@ -1557,7 +1557,7 @@ fn throw_on_overflow<'a, 'ctx, 'env>(
bd.position_at_end(throw_block);
throw_exception(env, message);
throw_internal_exception(env, parent, message);
bd.position_at_end(then_block);
@ -2003,8 +2003,9 @@ fn int_neg_raise_on_overflow<'a, 'ctx, 'env>(
builder.position_at_end(then_block);
throw_exception(
throw_internal_exception(
env,
parent,
"integer negation overflowed because its argument is the minimum value",
);
@ -2033,8 +2034,9 @@ fn int_abs_raise_on_overflow<'a, 'ctx, 'env>(
builder.position_at_end(then_block);
throw_exception(
throw_internal_exception(
env,
parent,
"integer absolute overflowed because its argument is the minimum value",
);

View file

@ -1,6 +1,7 @@
use std::ffi::CStr;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use roc_mono::ir::CrashTag;
use roc_std::RocStr;
/// This must have the same size as the repr() of RocCallResult!
pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::<u64>();
@ -8,7 +9,7 @@ pub const ROC_CALL_RESULT_DISCRIMINANT_SIZE: usize = std::mem::size_of::<u64>();
#[repr(C)]
pub struct RocCallResult<T> {
tag: u64,
error_msg: *mut c_char,
error_msg: *mut RocStr,
value: MaybeUninit<T>,
}
@ -32,14 +33,18 @@ impl<T: Default> Default for RocCallResult<T> {
}
}
impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
impl<T: Sized> From<RocCallResult<T>> for Result<T, (String, CrashTag)> {
fn from(call_result: RocCallResult<T>) -> Self {
match call_result.tag {
0 => Ok(unsafe { call_result.value.assume_init() }),
_ => Err({
let raw = unsafe { CStr::from_ptr(call_result.error_msg) };
n => Err({
let msg: &RocStr = unsafe { &*call_result.error_msg };
let tag = (n - 1) as u32;
let tag = tag
.try_into()
.unwrap_or_else(|_| panic!("received illegal tag: {tag}"));
raw.to_str().unwrap().to_owned()
(msg.as_str().to_owned(), tag)
}),
}
}
@ -120,7 +125,7 @@ macro_rules! run_jit_function {
$transform(success)
}
Err(error_msg) => {
Err((error_msg, _)) => {
eprintln!("This Roc code crashed with: \"{error_msg}\"");
Expr::MalformedClosure

View file

@ -8,8 +8,8 @@ use roc_module::low_level::{LowLevel, LowLevelWrapperType};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX};
use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc,
ProcLayout, Stmt,
BranchInfo, CallType, CrashTag, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc,
Param, Proc, ProcLayout, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_std::RocDec;
@ -717,7 +717,7 @@ impl<'a> WasmBackend<'a> {
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the wasm backend"),
Stmt::RuntimeError(msg) => self.stmt_runtime_error(msg),
Stmt::Crash(sym, tag) => self.stmt_crash(*sym, *tag),
}
}
@ -987,19 +987,31 @@ impl<'a> WasmBackend<'a> {
self.stmt(rc_stmt);
}
pub fn stmt_runtime_error(&mut self, msg: &'a str) {
// Create a zero-terminated version of the message string
let mut bytes = Vec::with_capacity_in(msg.len() + 1, self.env.arena);
bytes.extend_from_slice(msg.as_bytes());
bytes.push(0);
pub fn stmt_internal_error(&mut self, msg: &'a str) {
let msg_sym = self.create_symbol("panic_str");
let msg_storage = self.storage.allocate_var(
self.env.layout_interner,
Layout::Builtin(Builtin::Str),
msg_sym,
StoredVarKind::Variable,
);
// Store it in the app's data section
let elements_addr = self.store_bytes_in_data_section(&bytes);
// Store the message as a RocStr on the stack
let (local_id, offset) = match msg_storage {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(self.storage.stack_frame_pointer)
}
_ => internal_error!("String must always have stack memory"),
};
self.expr_string_literal(msg, local_id, offset);
// Pass its address to roc_panic
let tag_id = 0;
self.code_builder.i32_const(elements_addr as i32);
self.code_builder.i32_const(tag_id);
self.stmt_crash(msg_sym, CrashTag::Roc);
}
pub fn stmt_crash(&mut self, msg: Symbol, tag: CrashTag) {
// load the pointer
self.storage.load_symbols(&mut self.code_builder, &[msg]);
self.code_builder.i32_const(tag as _);
self.call_host_fn_after_loading_args("roc_panic", 2, false);
self.code_builder.unreachable_();
@ -1128,6 +1140,18 @@ impl<'a> WasmBackend<'a> {
let (local_id, offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
self.expr_string_literal(string, local_id, offset);
}
// Bools and bytes should not be stored in the stack frame
Literal::Bool(_) | Literal::Byte(_) => invalid_error(),
}
}
_ => invalid_error(),
};
}
fn expr_string_literal(&mut self, string: &str, local_id: LocalId, offset: u32) {
let len = string.len();
if len < 12 {
// Construct the bytes of the small string
@ -1136,8 +1160,7 @@ impl<'a> WasmBackend<'a> {
bytes[11] = 0x80 | (len as u8);
// Transform into two integers, to minimise number of instructions
let bytes_split: &([u8; 8], [u8; 4]) =
unsafe { std::mem::transmute(&bytes) };
let bytes_split: &([u8; 8], [u8; 4]) = unsafe { std::mem::transmute(&bytes) };
let int64 = i64::from_le_bytes(bytes_split.0);
let int32 = i32::from_le_bytes(bytes_split.1);
@ -1168,14 +1191,6 @@ impl<'a> WasmBackend<'a> {
self.code_builder.i32_store(Align::Bytes4, offset + 8);
};
}
// Bools and bytes should not be stored in the stack frame
Literal::Bool(_) | Literal::Byte(_) => invalid_error(),
}
}
_ => invalid_error(),
};
}
/// Create a string constant in the module data section
/// Return the data we need for code gen: linker symbol index and memory address

View file

@ -1362,7 +1362,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_const(i32::MIN);
backend.code_builder.i32_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
// x
@ -1388,7 +1388,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i64_const(i64::MIN);
backend.code_builder.i64_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
// x
@ -1422,7 +1422,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i32_const(i32::MIN);
backend.code_builder.i32_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
backend.code_builder.i32_const(0);
@ -1433,7 +1433,7 @@ impl<'a> LowLevelCall<'a> {
backend.code_builder.i64_const(i64::MIN);
backend.code_builder.i64_eq();
backend.code_builder.if_();
backend.stmt_runtime_error(PANIC_MSG);
backend.stmt_internal_error(PANIC_MSG);
backend.code_builder.end();
backend.code_builder.i64_const(0);

View file

@ -321,7 +321,7 @@ impl<'a> ParamMap<'a> {
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | Crash(..) => {
// these are terminal, do nothing
}
}
@ -827,7 +827,12 @@ impl<'a> BorrowInfState<'a> {
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) => {
Crash(msg, _) => {
// Crash is a foreign call, so we must own the argument.
self.own_var(*msg);
}
Ret(_) => {
// these are terminal, do nothing
}
}
@ -1001,7 +1006,7 @@ fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>)
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | Crash(..) => {
// these are terminal, do nothing
}
}

View file

@ -158,7 +158,9 @@ pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>)
stack.push(default_branch.1);
}
RuntimeError(_) => {}
Crash(sym, _) => {
result.insert(*sym);
}
}
}
@ -1240,7 +1242,20 @@ impl<'a, 'i> Context<'a, 'i> {
(expect, b_live_vars)
}
RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()),
Crash(x, _) => {
let info = self.get_var_info(*x);
let mut live_vars = MutSet::default();
live_vars.insert(*x);
if info.reference && !info.consume {
(self.add_inc(*x, 1, stmt), live_vars)
} else {
(stmt, live_vars)
}
}
Refcounting(_, _) => (stmt, MutSet::default()),
}
}
}
@ -1411,7 +1426,10 @@ pub fn collect_stmt(
vars
}
RuntimeError(_) => vars,
Crash(m, _) => {
vars.insert(*m);
vars
}
}
}

View file

@ -71,6 +71,16 @@ roc_error_macros::assert_sizeof_non_wasm!(ProcLayout, 8 * 8);
roc_error_macros::assert_sizeof_non_wasm!(Call, 9 * 8);
roc_error_macros::assert_sizeof_non_wasm!(CallType, 7 * 8);
fn runtime_error<'a>(env: &mut Env<'a, '_>, msg: &'a str) -> Stmt<'a> {
let sym = env.unique_symbol();
Stmt::Let(
sym,
Expr::Literal(Literal::Str(msg)),
Layout::Builtin(Builtin::Str),
env.arena.alloc(Stmt::Crash(sym, CrashTag::Roc)),
)
}
macro_rules! return_on_layout_error {
($env:expr, $layout_result:expr, $context_msg:expr) => {
match $layout_result {
@ -84,15 +94,17 @@ macro_rules! return_on_layout_error_help {
($env:expr, $error:expr, $context_msg:expr) => {{
match $error {
LayoutProblem::UnresolvedTypeVar(_) => {
return Stmt::RuntimeError(
return runtime_error(
$env,
$env.arena
.alloc(format!("UnresolvedTypeVar: {}", $context_msg,)),
);
)
}
LayoutProblem::Erroneous => {
return Stmt::RuntimeError(
return runtime_error(
$env,
$env.arena.alloc(format!("Erroneous: {}", $context_msg,)),
);
)
}
}
}};
@ -1611,6 +1623,7 @@ pub fn cond<'a>(
}
pub type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
#[derive(Clone, Debug, PartialEq)]
pub enum Stmt<'a> {
Let(Symbol, Expr<'a>, Layout<'a>, &'a Stmt<'a>),
@ -1655,7 +1668,29 @@ pub enum Stmt<'a> {
remainder: &'a Stmt<'a>,
},
Jump(JoinPointId, &'a [Symbol]),
RuntimeError(&'a str),
Crash(Symbol, CrashTag),
}
/// Source of crash, and its runtime representation to roc_panic.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum CrashTag {
/// The crash is due to Roc, either via a builtin or type error.
Roc = 0,
/// The crash is user-defined.
User = 1,
}
impl TryFrom<u32> for CrashTag {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Roc),
1 => Ok(Self::User),
_ => Err(()),
}
}
}
/// in the block below, symbol `scrutinee` is assumed be be of shape `tag_id`
@ -2303,7 +2338,7 @@ impl<'a> Stmt<'a> {
}
}
RuntimeError(s) => alloc.text(format!("Error {}", s)),
Crash(s, _src) => alloc.text("Crash ").append(symbol_to_doc(alloc, *s)),
Join {
id,
@ -3152,7 +3187,7 @@ fn generate_runtime_error_function<'a>(
);
});
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
let runtime_error = runtime_error(env, msg.into_bump_str());
let (args, ret_layout) = match layout {
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
@ -4300,7 +4335,7 @@ pub fn with_hole<'a>(
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
@ -4352,7 +4387,7 @@ pub fn with_hole<'a>(
// creating a record from the var will unpack it if it's just a single field.
let layout = match layout_cache.from_var(env.arena, record_var, env.subs) {
Ok(layout) => layout,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
let field_symbols = field_symbols.into_bump_slice();
@ -4531,8 +4566,8 @@ pub fn with_hole<'a>(
}
}
}
(Err(_), _) => Stmt::RuntimeError("invalid ret_layout"),
(_, Err(_)) => Stmt::RuntimeError("invalid cond_layout"),
(Err(_), _) => runtime_error(env, "invalid ret_layout"),
(_, Err(_)) => runtime_error(env, "invalid cond_layout"),
}
}
@ -4698,7 +4733,7 @@ pub fn with_hole<'a>(
};
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't access record with improper layout"),
Err(_) => return runtime_error(env, "Can't access record with improper layout"),
};
let mut index = None;
@ -4816,7 +4851,8 @@ pub fn with_hole<'a>(
}
}
Err(_error) => Stmt::RuntimeError(
Err(_error) => runtime_error(
env,
"TODO convert anonymous function error to a RuntimeError string",
),
}
@ -4872,7 +4908,8 @@ pub fn with_hole<'a>(
}
}
Err(_error) => Stmt::RuntimeError(
Err(_error) => runtime_error(
env,
"TODO convert anonymous function error to a RuntimeError string",
),
}
@ -4907,7 +4944,7 @@ pub fn with_hole<'a>(
let sorted_fields = match sorted_fields_result {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't update record with improper layout"),
Err(_) => return runtime_error(env, "Can't update record with improper layout"),
};
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -5093,10 +5130,10 @@ pub fn with_hole<'a>(
layout_cache,
);
if let Err(runtime_error) = inserted {
return Stmt::RuntimeError(
env.arena
.alloc(format!("RuntimeError: {:?}", runtime_error,)),
if let Err(e) = inserted {
return runtime_error(
env,
env.arena.alloc(format!("RuntimeError: {:?}", e,)),
);
} else {
drop(inserted);
@ -5560,8 +5597,20 @@ pub fn with_hole<'a>(
}
}
}
TypedHole(_) => Stmt::RuntimeError("Hit a blank"),
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(e.runtime_message())),
TypedHole(_) => runtime_error(env, "Hit a blank"),
RuntimeError(e) => runtime_error(env, env.arena.alloc(e.runtime_message())),
Crash { msg, ret_var: _ } => {
let msg_sym = possible_reuse_symbol_or_specialize(
env,
procs,
layout_cache,
&msg.value,
Variable::STR,
);
let stmt = Stmt::Crash(msg_sym, CrashTag::User);
assign_to_symbol(env, procs, layout_cache, Variable::STR, *msg, msg_sym, stmt)
}
}
}
@ -5820,16 +5869,22 @@ fn convert_tag_union<'a>(
let variant = match res_variant {
Ok(cached) => cached,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
return runtime_error(
env,
env.arena.alloc(format!(
"Unresolved type variable for tag {}",
tag_name.0.as_str()
)))
)),
)
}
Err(LayoutProblem::Erroneous) => {
return Stmt::RuntimeError(env.arena.alloc(format!(
return runtime_error(
env,
env.arena.alloc(format!(
"Tag {} was part of a type error!",
tag_name.0.as_str()
)));
)),
);
}
};
@ -5857,7 +5912,7 @@ fn convert_tag_union<'a>(
Layout::Builtin(Builtin::Int(IntWidth::U8)),
hole,
),
None => Stmt::RuntimeError("tag must be in its own type"),
None => runtime_error(env, "tag must be in its own type"),
}
}
@ -5897,7 +5952,7 @@ fn convert_tag_union<'a>(
if dataful_tag != tag_name {
// this tag is not represented, and hence will never be reached, at runtime.
Stmt::RuntimeError("voided tag constructor is unreachable")
runtime_error(env, "voided tag constructor is unreachable")
} else {
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
@ -6161,10 +6216,13 @@ fn tag_union_to_function<'a>(
}
}
Err(runtime_error) => Stmt::RuntimeError(env.arena.alloc(format!(
Err(e) => runtime_error(
env,
env.arena.alloc(format!(
"Could not produce tag function due to a runtime error: {:?}",
runtime_error,
))),
e,
)),
),
}
}
@ -6722,7 +6780,7 @@ fn from_can_when<'a>(
if branches.is_empty() {
// A when-expression with no branches is a runtime error.
// We can't know what to return!
return Stmt::RuntimeError("Hit a 0-branch when expression");
return runtime_error(env, "Hit a 0-branch when expression");
}
let opt_branches = to_opt_branches(env, procs, branches, exhaustive_mark, layout_cache);
@ -7025,8 +7083,7 @@ fn substitute_in_stmt_help<'a>(
None
}
}
RuntimeError(_) => None,
Crash(msg, tag) => substitute(subs, *msg).map(|new| &*arena.alloc(Crash(new, *tag))),
}
}
@ -8409,7 +8466,7 @@ fn evaluate_arguments_then_runtime_error<'a>(
let arena = env.arena;
// eventually we will throw this runtime error
let result = Stmt::RuntimeError(env.arena.alloc(msg));
let result = runtime_error(env, env.arena.alloc(msg));
// but, we also still evaluate and specialize the arguments to give better error messages
let arg_symbols = Vec::from_iter_in(
@ -8634,7 +8691,7 @@ fn call_by_name_help<'a>(
Err(_) => {
// One of this function's arguments code gens to a runtime error,
// so attempting to call it will immediately crash.
return Stmt::RuntimeError("TODO runtime error for invalid layout");
return runtime_error(env, "TODO runtime error for invalid layout");
}
}
}
@ -10133,7 +10190,7 @@ where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.call_by_name_options(&layout_cache.interner) {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Void => empty_lambda_set_error(env),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
@ -10312,9 +10369,9 @@ where
}
}
fn empty_lambda_set_error() -> Stmt<'static> {
fn empty_lambda_set_error<'a>(env: &mut Env<'a, '_>) -> Stmt<'a> {
let msg = "a Lambda Set is empty. Most likely there is a type error in your program.";
Stmt::RuntimeError(msg)
runtime_error(env, msg)
}
/// Use the lambda set to figure out how to make a call-by-name
@ -10332,7 +10389,7 @@ fn match_on_lambda_set<'a>(
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
match lambda_set.call_by_name_options(&layout_cache.interner) {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Void => empty_lambda_set_error(env),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
@ -10486,7 +10543,7 @@ fn union_lambda_set_to_switch<'a>(
// there is really nothing we can do here. We generate a runtime error here which allows
// code gen to proceed. We then assume that we hit another (more descriptive) error before
// hitting this one
return empty_lambda_set_error();
return empty_lambda_set_error(env);
}
let join_point_id = JoinPointId(env.unique_symbol());

View file

@ -241,7 +241,7 @@ fn function_s<'a, 'i>(
}
}
Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
Ret(_) | Jump(_, _) | Crash(..) => stmt,
}
}
@ -535,7 +535,7 @@ fn function_d_main<'a, 'i>(
(arena.alloc(new_join), found)
}
Ret(_) | Jump(_, _) | RuntimeError(_) => (stmt, has_live_var(&env.jp_live_vars, stmt, x)),
Ret(_) | Jump(_, _) | Crash(..) => (stmt, has_live_var(&env.jp_live_vars, stmt, x)),
}
}
@ -696,7 +696,7 @@ fn function_r<'a, 'i>(env: &mut Env<'a, 'i>, stmt: &'a Stmt<'a>) -> &'a Stmt<'a>
arena.alloc(expect)
}
Ret(_) | Jump(_, _) | RuntimeError(_) => {
Ret(_) | Jump(_, _) | Crash(..) => {
// terminals
stmt
}
@ -761,7 +761,7 @@ fn has_live_var<'a>(jp_live_vars: &JPLiveVarMap, stmt: &'a Stmt<'a>, needle: Sym
Jump(id, arguments) => {
arguments.iter().any(|s| *s == needle) || jp_live_vars[id].contains(&needle)
}
RuntimeError(_) => false,
Crash(m, _) => *m == needle,
}
}

View file

@ -299,6 +299,6 @@ fn insert_jumps<'a>(
Ret(_) => None,
Jump(_, _) => None,
RuntimeError(_) => None,
Crash(..) => None,
}
}

View file

@ -196,6 +196,9 @@ pub enum Expr<'a> {
Underscore(&'a str),
// The "crash" keyword
Crash,
// Tags
Tag(&'a str),

View file

@ -173,6 +173,7 @@ fn loc_term_or_underscore_or_conditional<'a>(
loc!(specialize(EExpr::SingleQuote, single_quote_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize(EExpr::Closure, closure_help(options))),
loc!(crash_kw()),
loc!(underscore_expression()),
loc!(record_literal_help()),
loc!(specialize(EExpr::List, list_literal_help())),
@ -238,6 +239,15 @@ fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
}
}
fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, _, next_state) = crate::parser::keyword_e(crate::keyword::CRASH, EExpr::Crash)
.parse(arena, state, min_indent)?;
Ok((MadeProgress, Expr::Crash, next_state))
}
}
fn loc_possibly_negative_or_negated_term<'a>(
options: ExprParseOptions,
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
@ -1886,7 +1896,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::MalformedClosure
| Expr::PrecedenceConflict { .. }
| Expr::RecordUpdate { .. }
| Expr::UnaryOp(_, _) => Err(()),
| Expr::UnaryOp(_, _)
| Expr::Crash => Err(()),
Expr::Str(string) => Ok(Pattern::StrLiteral(*string)),
Expr::SingleQuote(string) => Ok(Pattern::SingleQuote(string)),

View file

@ -7,5 +7,6 @@ pub const IS: &str = "is";
pub const DBG: &str = "dbg";
pub const EXPECT: &str = "expect";
pub const EXPECT_FX: &str = "expect-fx";
pub const CRASH: &str = "crash";
pub const KEYWORDS: [&str; 8] = [IF, THEN, ELSE, WHEN, AS, IS, EXPECT, EXPECT_FX];
pub const KEYWORDS: [&str; 10] = [IF, THEN, ELSE, WHEN, AS, IS, DBG, EXPECT, EXPECT_FX, CRASH];

View file

@ -358,6 +358,7 @@ pub enum EExpr<'a> {
Closure(EClosure<'a>, Position),
Underscore(Position),
Crash(Position),
InParens(EInParens<'a>, Position),
Record(ERecord<'a>, Position),

View file

@ -0,0 +1,10 @@
_ = crash ""
_ = crash "" ""
_ = crash 15 123
_ = try foo (\_ -> crash "")
_ =
_ = crash ""
crash
{ f: crash "" }

View file

@ -0,0 +1,210 @@
Defs(
Defs {
tags: [
Index(2147483648),
Index(2147483649),
Index(2147483650),
Index(2147483651),
Index(2147483652),
],
regions: [
@0-12,
@13-28,
@29-45,
@46-74,
@75-101,
],
space_before: [
Slice(start = 0, length = 0),
Slice(start = 0, length = 1),
Slice(start = 1, length = 1),
Slice(start = 2, length = 1),
Slice(start = 3, length = 1),
],
space_after: [
Slice(start = 0, length = 0),
Slice(start = 1, length = 0),
Slice(start = 2, length = 0),
Slice(start = 3, length = 0),
Slice(start = 4, length = 0),
],
spaces: [
Newline,
Newline,
Newline,
Newline,
],
type_defs: [],
value_defs: [
Body(
@0-1 Underscore(
"",
),
@4-12 Apply(
@4-9 Crash,
[
@10-12 Str(
PlainLine(
"",
),
),
],
Space,
),
),
Body(
@13-14 Underscore(
"",
),
@17-28 Apply(
@17-22 Crash,
[
@23-25 Str(
PlainLine(
"",
),
),
@26-28 Str(
PlainLine(
"",
),
),
],
Space,
),
),
Body(
@29-30 Underscore(
"",
),
@33-45 Apply(
@33-38 Crash,
[
@39-41 Num(
"15",
),
@42-45 Num(
"123",
),
],
Space,
),
),
Body(
@46-47 Underscore(
"",
),
@50-74 Apply(
@50-53 Var {
module_name: "",
ident: "try",
},
[
@54-57 Var {
module_name: "",
ident: "foo",
},
@59-73 ParensAround(
Closure(
[
@60-61 Underscore(
"",
),
],
@65-73 Apply(
@65-70 Crash,
[
@71-73 Str(
PlainLine(
"",
),
),
],
Space,
),
),
),
],
Space,
),
),
Body(
@75-76 Underscore(
"",
),
@81-101 SpaceBefore(
Defs(
Defs {
tags: [
Index(2147483648),
],
regions: [
@81-93,
],
space_before: [
Slice(start = 0, length = 0),
],
space_after: [
Slice(start = 0, length = 0),
],
spaces: [],
type_defs: [],
value_defs: [
Body(
@81-82 Underscore(
"",
),
@85-93 Apply(
@85-90 Crash,
[
@91-93 Str(
PlainLine(
"",
),
),
],
Space,
),
),
],
},
@96-101 SpaceBefore(
Crash,
[
Newline,
],
),
),
[
Newline,
],
),
),
],
},
@103-118 SpaceBefore(
Record(
[
@105-116 RequiredValue(
@105-106 "f",
[],
@108-116 Apply(
@108-113 Crash,
[
@114-116 Str(
PlainLine(
"",
),
),
],
Space,
),
),
],
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,9 @@
_ = crash ""
_ = crash "" ""
_ = crash 15 123
_ = try foo (\_ -> crash "")
_ =
_ = crash ""
crash
{ f: crash "" }

View file

@ -156,6 +156,7 @@ mod test_parse {
pass/comment_before_op.expr,
pass/comment_inside_empty_list.expr,
pass/comment_with_non_ascii.expr,
pass/crash.expr,
pass/destructure_tag_assignment.expr,
pass/empty_app_header.header,
pass/empty_hosted_header.header,

View file

@ -195,6 +195,12 @@ pub enum Problem {
type_got: u8,
alias_kind: AliasKind,
},
UnappliedCrash {
region: Region,
},
OverAppliedCrash {
region: Region,
},
}
impl Problem {
@ -325,7 +331,9 @@ impl Problem {
}
| Problem::MultipleListRestPattern { region }
| Problem::BadTypeArguments { region, .. }
| Problem::UnnecessaryOutputWildcard { region } => Some(*region),
| Problem::UnnecessaryOutputWildcard { region }
| Problem::OverAppliedCrash { region }
| Problem::UnappliedCrash { region } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => {
cycle_entries.first().map(|entry| entry.expr_region)

View file

@ -8352,4 +8352,20 @@ mod solve_expr {
@"translateStatic : [Element (List a)] as a -[[translateStatic(0)]]-> [Element (List b)]* as b"
)
}
#[test]
fn infer_contextual_crash() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [getInfallible] to "./platform"
getInfallible = \result -> when result is
Ok x -> x
_ -> crash "turns out this was fallible"
"#
),
"[Ok a]* -> a",
);
}
}

View file

@ -279,6 +279,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
)
.group()
),
Crash { .. } => todo!(),
ZeroArgumentTag { .. } => todo!(),
OpaqueRef { .. } => todo!(),
Dbg { .. } => todo!(),

View file

@ -0,0 +1,85 @@
use indoc::indoc;
use roc_std::RocList;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "hello crash""#]
fn crash_literal() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = if Bool.true then crash "hello crash" else 1u8
"#
),
1u8,
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "hello crash""#]
fn crash_variable() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
msg = "hello crash"
if Bool.true then crash msg else 1u8
"#
),
1u8,
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "turns out this was fallible""#]
fn crash_in_call() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
getInfallible = \result -> when result is
Ok x -> x
_ -> crash "turns out this was fallible"
main =
x : [Ok U64, Err Str]
x = Err ""
getInfallible x
"#
),
1u64,
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic = r#"User crash with message: "no new even primes""#]
fn crash_in_passed_closure() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = List.map [1, 2, 3] \n -> if n == 2 then crash "no new even primes" else ""
"#
),
RocList::from_slice(&[1u8]),
RocList<u8>
);
}

View file

@ -1,11 +1,11 @@
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::{assert_evals_to, expect_runtime_error_panic};
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::{assert_evals_to, expect_runtime_error_panic};
use crate::helpers::wasm::assert_evals_to;
// use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc;
@ -1019,7 +1019,8 @@ fn different_proc_types_specialized_to_same_layout() {
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = r#"Roc failed with message: "Can't create record with improper layout""#)]
fn call_with_bad_record_runtime_error() {
expect_runtime_error_panic!(indoc!(
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
@ -1028,7 +1029,12 @@ fn call_with_bad_record_runtime_error() {
get = \{a} -> a
get {b: ""}
"#
))
),
true,
bool,
|x| x,
true // ignore type errors
)
}
#[test]

View file

@ -8,7 +8,7 @@ use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult};
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
use roc_mono::ir::OptLevel;
use roc_mono::ir::{CrashTag, OptLevel};
use roc_region::all::LineInfo;
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use roc_utils::zig;
@ -544,7 +544,10 @@ macro_rules! assert_wasm_evals_to {
}
#[allow(dead_code)]
pub fn try_run_lib_function<T>(main_fn_name: &str, lib: &libloading::Library) -> Result<T, String> {
pub fn try_run_lib_function<T>(
main_fn_name: &str,
lib: &libloading::Library,
) -> Result<T, (String, CrashTag)> {
unsafe {
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<T>)> = lib
.get(main_fn_name.as_bytes())
@ -565,6 +568,7 @@ macro_rules! assert_llvm_evals_to {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_mono::ir::CrashTag;
let arena = Bump::new();
let context = Context::create();
@ -594,7 +598,10 @@ macro_rules! assert_llvm_evals_to {
#[cfg(windows)]
std::mem::forget(given);
}
Err(msg) => panic!("Roc failed with message: \"{}\"", msg),
Err((msg, tag)) => match tag {
CrashTag::Roc => panic!(r#"Roc failed with message: "{}""#, msg),
CrashTag::User => panic!(r#"User crash with message: "{}""#, msg),
},
}
// artificially extend the lifetime of `lib`
@ -655,29 +662,6 @@ macro_rules! assert_evals_to {
}};
}
#[allow(unused_macros)]
macro_rules! expect_runtime_error_panic {
($src:expr) => {{
#[cfg(feature = "gen-llvm-wasm")]
$crate::helpers::llvm::assert_wasm_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::llvm::identity,
true // ignore problems
);
#[cfg(not(feature = "gen-llvm-wasm"))]
$crate::helpers::llvm::assert_llvm_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::llvm::identity,
true // ignore problems
);
}};
}
#[allow(dead_code)]
pub fn identity<T>(value: T) -> T {
value
@ -689,5 +673,3 @@ pub(crate) use assert_evals_to;
pub(crate) use assert_llvm_evals_to;
#[allow(unused_imports)]
pub(crate) use assert_wasm_evals_to;
#[allow(unused_imports)]
pub(crate) use expect_runtime_error_panic;

View file

@ -32,23 +32,3 @@ pub unsafe fn roc_realloc(
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
/// # Safety
/// The Roc application needs this.
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
Err(_) => unreachable!(),
}
}

View file

@ -5,6 +5,7 @@ use roc_gen_wasm::wasm32_result::Wasm32Result;
use roc_gen_wasm::DEBUG_SETTINGS;
use roc_load::{ExecutionMode, LoadConfig, Threading};
use roc_reporting::report::DEFAULT_PALETTE_HTML;
use roc_std::RocStr;
use roc_wasm_module::{Export, ExportType};
use std::marker::PhantomData;
use std::path::PathBuf;
@ -193,7 +194,7 @@ where
let parsed = Module::parse(&env, &wasm_bytes[..]).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let panic_msg: Rc<Mutex<Option<(i32, i32)>>> = Default::default();
let panic_msg: Rc<Mutex<Option<(i32, u32)>>> = Default::default();
link_module(&mut module, panic_msg.clone());
let test_wrapper = module
@ -202,12 +203,18 @@ where
match test_wrapper.call() {
Err(e) => {
if let Some((msg_ptr, msg_len)) = *panic_msg.lock().unwrap() {
if let Some((msg_ptr, tag)) = *panic_msg.lock().unwrap() {
let memory: &[u8] = get_memory(&rt);
let msg_bytes = &memory[msg_ptr as usize..][..msg_len as usize];
let msg = std::str::from_utf8(msg_bytes).unwrap();
let msg = RocStr::decode(memory, msg_ptr as _);
Err(format!("Roc failed with message: \"{}\"", msg))
dbg!(tag);
let msg = match tag {
0 => format!(r#"Roc failed with message: "{}""#, msg),
1 => format!(r#"User crash with message: "{}""#, msg),
tag => format!(r#"Got an invald panic tag: "{}""#, tag),
};
Err(msg)
} else {
Err(format!("{}", e))
}
@ -253,7 +260,7 @@ where
let parsed = Module::parse(&env, wasm_bytes).expect("Unable to parse module");
let mut module = rt.load_module(parsed).expect("Unable to load module");
let panic_msg: Rc<Mutex<Option<(i32, i32)>>> = Default::default();
let panic_msg: Rc<Mutex<Option<(i32, u32)>>> = Default::default();
link_module(&mut module, panic_msg.clone());
let expected_len = num_refcounts as i32;
@ -316,13 +323,13 @@ fn read_i32(memory: &[u8], ptr: usize) -> i32 {
i32::from_le_bytes(bytes)
}
fn link_module(module: &mut Module, panic_msg: Rc<Mutex<Option<(i32, i32)>>>) {
fn link_module(module: &mut Module, panic_msg: Rc<Mutex<Option<(i32, u32)>>>) {
let try_link_panic = module.link_closure(
"env",
"send_panic_msg_to_rust",
move |_call_context, args: (i32, i32)| {
move |_call_context, (msg_ptr, tag): (i32, u32)| {
let mut w = panic_msg.lock().unwrap();
*w = Some(args);
*w = Some((msg_ptr, tag));
Ok(())
},
);
@ -390,19 +397,6 @@ macro_rules! assert_evals_to {
}};
}
#[allow(unused_macros)]
macro_rules! expect_runtime_error_panic {
($src:expr) => {{
$crate::helpers::wasm::assert_evals_to!(
$src,
false, // fake value/type for eval
bool,
$crate::helpers::wasm::identity,
true // ignore problems
);
}};
}
#[allow(dead_code)]
pub fn identity<T>(value: T) -> T {
value
@ -430,8 +424,5 @@ macro_rules! assert_refcounts {
#[allow(unused_imports)]
pub(crate) use assert_evals_to;
#[allow(unused_imports)]
pub(crate) use expect_runtime_error_panic;
#[allow(unused_imports)]
pub(crate) use assert_refcounts;

View file

@ -1,4 +1,5 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@ -123,12 +124,11 @@ void roc_dealloc(void *ptr, unsigned int alignment)
//--------------------------
extern void send_panic_msg_to_rust(char* msg, int len);
extern void send_panic_msg_to_rust(void* msg, uint32_t tag_id);
void roc_panic(char *msg, unsigned int tag_id)
void roc_panic(void* msg, unsigned int tag_id)
{
int len = strlen(msg);
send_panic_msg_to_rust(msg, len);
send_panic_msg_to_rust(msg, tag_id);
exit(101);
}

View file

@ -9,6 +9,7 @@ pub mod gen_compare;
pub mod gen_dict;
pub mod gen_list;
pub mod gen_num;
pub mod gen_panic;
pub mod gen_primitives;
pub mod gen_records;
pub mod gen_refcount;

View file

@ -5,7 +5,8 @@ procedure List.5 (#Attr.2, #Attr.3):
procedure Test.2 (Test.3):
let Test.7 : {} = Struct {};
Error a Lambda Set is empty. Most likely there is a type error in your program.
let Test.8 : Str = "a Lambda Set is empty. Most likely there is a type error in your program.";
Crash Test.8
procedure Test.0 ():
let Test.1 : List [] = Array [];

View file

@ -5,7 +5,8 @@ procedure List.5 (#Attr.2, #Attr.3):
procedure Test.2 (Test.3):
let Test.7 : {} = Struct {};
Error a Lambda Set is empty. Most likely there is a type error in your program.
let Test.8 : Str = "a Lambda Set is empty. Most likely there is a type error in your program.";
Crash Test.8
procedure Test.0 ():
let Test.1 : List [] = Array [];

View file

@ -0,0 +1,18 @@
procedure Test.1 (Test.2):
let Test.10 : U8 = 1i64;
let Test.11 : U8 = GetTagId Test.2;
let Test.12 : Int1 = lowlevel Eq Test.10 Test.11;
if Test.12 then
let Test.3 : U64 = UnionAtIndex (Id 1) (Index 0) Test.2;
dec Test.2;
ret Test.3;
else
dec Test.2;
let Test.9 : Str = "turns out this was fallible";
Crash Test.9
procedure Test.0 ():
let Test.13 : U64 = 78i64;
let Test.4 : [C Str, C U64] = TagId(1) Test.13;
let Test.6 : U64 = CallByName Test.1 Test.4;
ret Test.6;

View file

@ -205,58 +205,58 @@ procedure Json.96 (Json.97, Json.473, Json.95):
ret Json.475;
procedure List.137 (List.138, List.139, List.136):
let List.449 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.449;
let List.450 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.450;
procedure List.137 (List.138, List.139, List.136):
let List.521 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.521;
let List.523 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.523;
procedure List.18 (List.134, List.135, List.136):
let List.431 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
ret List.431;
procedure List.18 (List.134, List.135, List.136):
let List.503 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
ret List.503;
let List.504 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
ret List.504;
procedure List.4 (List.105, List.106):
let List.502 : U64 = 1i64;
let List.501 : List U8 = CallByName List.70 List.105 List.502;
let List.500 : List U8 = CallByName List.71 List.501 List.106;
ret List.500;
let List.503 : U64 = 1i64;
let List.502 : List U8 = CallByName List.70 List.105 List.503;
let List.501 : List U8 = CallByName List.71 List.502 List.106;
ret List.501;
procedure List.6 (#Attr.2):
let List.409 : U64 = lowlevel ListLen #Attr.2;
ret List.409;
procedure List.6 (#Attr.2):
let List.451 : U64 = lowlevel ListLen #Attr.2;
ret List.451;
let List.452 : U64 = lowlevel ListLen #Attr.2;
ret List.452;
procedure List.6 (#Attr.2):
let List.524 : U64 = lowlevel ListLen #Attr.2;
ret List.524;
let List.526 : U64 = lowlevel ListLen #Attr.2;
ret List.526;
procedure List.66 (#Attr.2, #Attr.3):
let List.446 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.446;
let List.447 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.447;
procedure List.66 (#Attr.2, #Attr.3):
let List.518 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.518;
let List.520 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.520;
procedure List.70 (#Attr.2, #Attr.3):
let List.481 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.481;
let List.482 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.482;
procedure List.71 (#Attr.2, #Attr.3):
let List.479 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.479;
let List.480 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.480;
procedure List.8 (#Attr.2, #Attr.3):
let List.523 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.523;
let List.525 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.525;
procedure List.89 (List.385, List.386, List.387):
let List.435 : U64 = 0i64;
@ -265,38 +265,38 @@ procedure List.89 (List.385, List.386, List.387):
ret List.434;
procedure List.89 (List.385, List.386, List.387):
let List.507 : U64 = 0i64;
let List.508 : U64 = CallByName List.6 List.385;
let List.506 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.507 List.508;
ret List.506;
let List.508 : U64 = 0i64;
let List.509 : U64 = CallByName List.6 List.385;
let List.507 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.508 List.509;
ret List.507;
procedure List.90 (List.461, List.462, List.463, List.464, List.465):
procedure List.90 (List.462, List.463, List.464, List.465, List.466):
joinpoint List.437 List.388 List.389 List.390 List.391 List.392:
let List.439 : Int1 = CallByName Num.22 List.391 List.392;
if List.439 then
let List.445 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.440 : {List U8, U64} = CallByName List.137 List.389 List.445 List.390;
let List.446 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.440 : {List U8, U64} = CallByName List.137 List.389 List.446 List.390;
let List.443 : U64 = 1i64;
let List.442 : U64 = CallByName Num.19 List.391 List.443;
jump List.437 List.388 List.440 List.390 List.442 List.392;
else
ret List.389;
in
jump List.437 List.461 List.462 List.463 List.464 List.465;
jump List.437 List.462 List.463 List.464 List.465 List.466;
procedure List.90 (List.534, List.535, List.536, List.537, List.538):
joinpoint List.509 List.388 List.389 List.390 List.391 List.392:
let List.511 : Int1 = CallByName Num.22 List.391 List.392;
if List.511 then
let List.517 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.512 : {List U8, U64} = CallByName List.137 List.389 List.517 List.390;
let List.515 : U64 = 1i64;
let List.514 : U64 = CallByName Num.19 List.391 List.515;
jump List.509 List.388 List.512 List.390 List.514 List.392;
procedure List.90 (List.536, List.537, List.538, List.539, List.540):
joinpoint List.510 List.388 List.389 List.390 List.391 List.392:
let List.512 : Int1 = CallByName Num.22 List.391 List.392;
if List.512 then
let List.519 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.513 : {List U8, U64} = CallByName List.137 List.389 List.519 List.390;
let List.516 : U64 = 1i64;
let List.515 : U64 = CallByName Num.19 List.391 List.516;
jump List.510 List.388 List.513 List.390 List.515 List.392;
else
ret List.389;
in
jump List.509 List.534 List.535 List.536 List.537 List.538;
jump List.510 List.536 List.537 List.538 List.539 List.540;
procedure Num.125 (#Attr.2):
let Num.282 : U8 = lowlevel NumIntCast #Attr.2;

View file

@ -118,8 +118,8 @@ procedure Json.96 (Json.97, Json.433, Json.95):
ret Json.435;
procedure List.137 (List.138, List.139, List.136):
let List.455 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.455;
let List.456 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.456;
procedure List.18 (List.134, List.135, List.136):
let List.437 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
@ -136,12 +136,12 @@ procedure List.6 (#Attr.2):
ret List.409;
procedure List.6 (#Attr.2):
let List.458 : U64 = lowlevel ListLen #Attr.2;
ret List.458;
let List.459 : U64 = lowlevel ListLen #Attr.2;
ret List.459;
procedure List.66 (#Attr.2, #Attr.3):
let List.452 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.452;
let List.453 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.453;
procedure List.70 (#Attr.2, #Attr.3):
let List.415 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
@ -152,8 +152,8 @@ procedure List.71 (#Attr.2, #Attr.3):
ret List.413;
procedure List.8 (#Attr.2, #Attr.3):
let List.457 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.457;
let List.458 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.458;
procedure List.89 (List.385, List.386, List.387):
let List.441 : U64 = 0i64;
@ -161,19 +161,19 @@ procedure List.89 (List.385, List.386, List.387):
let List.440 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.441 List.442;
ret List.440;
procedure List.90 (List.468, List.469, List.470, List.471, List.472):
procedure List.90 (List.469, List.470, List.471, List.472, List.473):
joinpoint List.443 List.388 List.389 List.390 List.391 List.392:
let List.445 : Int1 = CallByName Num.22 List.391 List.392;
if List.445 then
let List.451 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.446 : {List U8, U64} = CallByName List.137 List.389 List.451 List.390;
let List.452 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.446 : {List U8, U64} = CallByName List.137 List.389 List.452 List.390;
let List.449 : U64 = 1i64;
let List.448 : U64 = CallByName Num.19 List.391 List.449;
jump List.443 List.388 List.446 List.390 List.448 List.392;
else
ret List.389;
in
jump List.443 List.468 List.469 List.470 List.471 List.472;
jump List.443 List.469 List.470 List.471 List.472 List.473;
procedure Num.125 (#Attr.2):
let Num.263 : U8 = lowlevel NumIntCast #Attr.2;

View file

@ -126,8 +126,8 @@ procedure Json.96 (Json.97, Json.433, Json.95):
ret Json.435;
procedure List.137 (List.138, List.139, List.136):
let List.455 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.455;
let List.456 : {List U8, U64} = CallByName Json.114 List.138 List.139;
ret List.456;
procedure List.18 (List.134, List.135, List.136):
let List.437 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
@ -144,12 +144,12 @@ procedure List.6 (#Attr.2):
ret List.409;
procedure List.6 (#Attr.2):
let List.458 : U64 = lowlevel ListLen #Attr.2;
ret List.458;
let List.459 : U64 = lowlevel ListLen #Attr.2;
ret List.459;
procedure List.66 (#Attr.2, #Attr.3):
let List.452 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.452;
let List.453 : {Str, Str} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.453;
procedure List.70 (#Attr.2, #Attr.3):
let List.415 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
@ -160,8 +160,8 @@ procedure List.71 (#Attr.2, #Attr.3):
ret List.413;
procedure List.8 (#Attr.2, #Attr.3):
let List.457 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.457;
let List.458 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.458;
procedure List.89 (List.385, List.386, List.387):
let List.441 : U64 = 0i64;
@ -169,19 +169,19 @@ procedure List.89 (List.385, List.386, List.387):
let List.440 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.441 List.442;
ret List.440;
procedure List.90 (List.468, List.469, List.470, List.471, List.472):
procedure List.90 (List.469, List.470, List.471, List.472, List.473):
joinpoint List.443 List.388 List.389 List.390 List.391 List.392:
let List.445 : Int1 = CallByName Num.22 List.391 List.392;
if List.445 then
let List.451 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.446 : {List U8, U64} = CallByName List.137 List.389 List.451 List.390;
let List.452 : {Str, Str} = CallByName List.66 List.388 List.391;
let List.446 : {List U8, U64} = CallByName List.137 List.389 List.452 List.390;
let List.449 : U64 = 1i64;
let List.448 : U64 = CallByName Num.19 List.391 List.449;
jump List.443 List.388 List.446 List.390 List.448 List.392;
else
ret List.389;
in
jump List.443 List.468 List.469 List.470 List.471 List.472;
jump List.443 List.469 List.470 List.471 List.472 List.473;
procedure Num.125 (#Attr.2):
let Num.263 : U8 = lowlevel NumIntCast #Attr.2;

View file

@ -127,8 +127,8 @@ procedure Json.96 (Json.97, Json.438, Json.95):
ret Json.440;
procedure List.137 (List.138, List.139, List.136):
let List.461 : {List U8, U64} = CallByName Json.128 List.138 List.139;
ret List.461;
let List.462 : {List U8, U64} = CallByName Json.128 List.138 List.139;
ret List.462;
procedure List.18 (List.134, List.135, List.136):
let List.443 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
@ -145,12 +145,12 @@ procedure List.6 (#Attr.2):
ret List.409;
procedure List.6 (#Attr.2):
let List.462 : U64 = lowlevel ListLen #Attr.2;
ret List.462;
let List.463 : U64 = lowlevel ListLen #Attr.2;
ret List.463;
procedure List.66 (#Attr.2, #Attr.3):
let List.458 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.458;
let List.459 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.459;
procedure List.70 (#Attr.2, #Attr.3):
let List.415 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
@ -161,8 +161,8 @@ procedure List.71 (#Attr.2, #Attr.3):
ret List.413;
procedure List.8 (#Attr.2, #Attr.3):
let List.464 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.464;
let List.465 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.465;
procedure List.89 (List.385, List.386, List.387):
let List.447 : U64 = 0i64;
@ -170,19 +170,19 @@ procedure List.89 (List.385, List.386, List.387):
let List.446 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.447 List.448;
ret List.446;
procedure List.90 (List.474, List.475, List.476, List.477, List.478):
procedure List.90 (List.475, List.476, List.477, List.478, List.479):
joinpoint List.449 List.388 List.389 List.390 List.391 List.392:
let List.451 : Int1 = CallByName Num.22 List.391 List.392;
if List.451 then
let List.457 : Str = CallByName List.66 List.388 List.391;
let List.452 : {List U8, U64} = CallByName List.137 List.389 List.457 List.390;
let List.458 : Str = CallByName List.66 List.388 List.391;
let List.452 : {List U8, U64} = CallByName List.137 List.389 List.458 List.390;
let List.455 : U64 = 1i64;
let List.454 : U64 = CallByName Num.19 List.391 List.455;
jump List.449 List.388 List.452 List.390 List.454 List.392;
else
ret List.389;
in
jump List.449 List.474 List.475 List.476 List.477 List.478;
jump List.449 List.475 List.476 List.477 List.478 List.479;
procedure Num.125 (#Attr.2):
let Num.265 : U8 = lowlevel NumIntCast #Attr.2;

View file

@ -133,8 +133,8 @@ procedure Json.96 (Json.97, Json.438, Json.95):
ret Json.440;
procedure List.137 (List.138, List.139, List.136):
let List.461 : {List U8, U64} = CallByName Json.128 List.138 List.139;
ret List.461;
let List.462 : {List U8, U64} = CallByName Json.128 List.138 List.139;
ret List.462;
procedure List.18 (List.134, List.135, List.136):
let List.443 : {List U8, U64} = CallByName List.89 List.134 List.135 List.136;
@ -151,12 +151,12 @@ procedure List.6 (#Attr.2):
ret List.409;
procedure List.6 (#Attr.2):
let List.462 : U64 = lowlevel ListLen #Attr.2;
ret List.462;
let List.463 : U64 = lowlevel ListLen #Attr.2;
ret List.463;
procedure List.66 (#Attr.2, #Attr.3):
let List.458 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.458;
let List.459 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.459;
procedure List.70 (#Attr.2, #Attr.3):
let List.415 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
@ -167,8 +167,8 @@ procedure List.71 (#Attr.2, #Attr.3):
ret List.413;
procedure List.8 (#Attr.2, #Attr.3):
let List.464 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.464;
let List.465 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3;
ret List.465;
procedure List.89 (List.385, List.386, List.387):
let List.447 : U64 = 0i64;
@ -176,19 +176,19 @@ procedure List.89 (List.385, List.386, List.387):
let List.446 : {List U8, U64} = CallByName List.90 List.385 List.386 List.387 List.447 List.448;
ret List.446;
procedure List.90 (List.474, List.475, List.476, List.477, List.478):
procedure List.90 (List.475, List.476, List.477, List.478, List.479):
joinpoint List.449 List.388 List.389 List.390 List.391 List.392:
let List.451 : Int1 = CallByName Num.22 List.391 List.392;
if List.451 then
let List.457 : Str = CallByName List.66 List.388 List.391;
let List.452 : {List U8, U64} = CallByName List.137 List.389 List.457 List.390;
let List.458 : Str = CallByName List.66 List.388 List.391;
let List.452 : {List U8, U64} = CallByName List.137 List.389 List.458 List.390;
let List.455 : U64 = 1i64;
let List.454 : U64 = CallByName Num.19 List.391 List.455;
jump List.449 List.388 List.452 List.390 List.454 List.392;
else
ret List.389;
in
jump List.449 List.474 List.475 List.476 List.477 List.478;
jump List.449 List.475 List.476 List.477 List.478 List.479;
procedure Num.125 (#Attr.2):
let Num.265 : U8 = lowlevel NumIntCast #Attr.2;

View file

@ -5,7 +5,8 @@ procedure Bool.2 ():
procedure Test.0 ():
let Test.6 : Int1 = CallByName Bool.2;
if Test.6 then
Error voided tag constructor is unreachable
let Test.8 : Str = "voided tag constructor is unreachable";
Crash Test.8
else
let Test.5 : Str = "abc";
ret Test.5;

View file

@ -2030,3 +2030,21 @@ fn recursive_function_and_union_with_inference_hole() {
"#
)
}
#[mono_test]
fn crash() {
indoc!(
r#"
app "test" provides [main] to "./platform"
getInfallible = \result -> when result is
Ok x -> x
_ -> crash "turns out this was fallible"
main =
x : [Ok U64, Err Str]
x = Ok 78
getInfallible x
"#
)
}

View file

@ -1667,6 +1667,8 @@ impl Subs {
pub const TAG_NAME_BAD_UTF_8: SubsIndex<TagName> = SubsIndex::new(3);
pub const TAG_NAME_OUT_OF_BOUNDS: SubsIndex<TagName> = SubsIndex::new(4);
pub const STR_SLICE: VariableSubsSlice = SubsSlice::new(0, 1);
#[rustfmt::skip]
pub const AB_ENCODING: SubsSlice<Symbol> = SubsSlice::new(0, 1);
#[rustfmt::skip]
@ -1704,14 +1706,18 @@ impl Subs {
let mut subs = Subs {
utable: UnificationTable::default(),
variables: Vec::new(),
variables: vec![
// Used for STR_SLICE
Variable::STR,
],
tag_names,
symbol_names,
field_names: Vec::new(),
record_fields: Vec::new(),
// store an empty slice at the first position
variable_slices: vec![
// used for "TagOrFunction"
variable_slices: vec![VariableSubsSlice::default()],
VariableSubsSlice::default(),
],
unspecialized_lambda_sets: Vec::new(),
tag_name_cache: Default::default(),
uls_of_var: Default::default(),

View file

@ -3486,6 +3486,7 @@ pub enum Reason {
member_name: Symbol,
def_region: Region,
},
CrashArg,
}
#[derive(PartialEq, Eq, Debug, Clone)]
@ -3528,6 +3529,8 @@ pub enum Category {
AbilityMemberSpecialization(Symbol),
Crash,
Expect,
Dbg,
Unknown,

View file

@ -235,7 +235,7 @@ fn run_expect_pure<'a, W: std::io::Write>(
let sequence = ExpectSequence::new(shared_memory.ptr.cast());
let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
let result: Result<(), (String, _)> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
let shared_memory_ptr: *const u8 = shared_memory.ptr.cast();
@ -249,7 +249,7 @@ fn run_expect_pure<'a, W: std::io::Write>(
let renderer = Renderer::new(arena, interns, render_target, module_id, filename, &source);
if let Err(roc_panic_message) = result {
if let Err((roc_panic_message, _roc_panic_tag)) = result {
renderer.render_panic(writer, &roc_panic_message, expect.region)?;
} else {
let mut offset = ExpectSequence::START_OFFSET;
@ -305,9 +305,10 @@ fn run_expect_fx<'a, W: std::io::Write>(
child_memory.set_shared_buffer(lib);
let result: Result<(), String> = try_run_jit_function!(lib, expect.name, (), |v: ()| v);
let result: Result<(), (String, _)> =
try_run_jit_function!(lib, expect.name, (), |v: ()| v);
if let Err(msg) = result {
if let Err((msg, _)) = result {
panic!("roc panic {}", msg);
}

View file

@ -1086,7 +1086,36 @@ pub fn can_problem<'b>(
} else {
"TOO FEW TYPE ARGUMENTS".to_string()
};
severity = Severity::RuntimeError;
}
Problem::UnappliedCrash { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This "), alloc.keyword("crash"), alloc.reflow(" doesn't have a message given to it:")
]),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.keyword("crash"), alloc.reflow(" must be passed a message to crash with at the exact place it's used. "),
alloc.keyword("crash"), alloc.reflow(" can't be used as a value that's passed around, like functions can be - it must be applied immediately!"),
])
]);
title = "UNAPPLIED CRASH".to_string();
severity = Severity::RuntimeError;
}
Problem::OverAppliedCrash { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This "),
alloc.keyword("crash"),
alloc.reflow(" has too many values given to it:"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.keyword("crash"),
alloc.reflow(" must be given exacly one message to crash with."),
]),
]);
title = "OVERAPPLIED CRASH".to_string();
severity = Severity::RuntimeError;
}
};

View file

@ -1375,6 +1375,42 @@ fn to_expr_report<'b>(
}
}
Reason::CrashArg => {
let this_is = alloc.reflow("The value is");
let wanted = alloc.concat([
alloc.reflow("But I can only "),
alloc.keyword("crash"),
alloc.reflow(" with messages of type"),
]);
let details = None;
let lines = [
alloc
.reflow("This value passed to ")
.append(alloc.keyword("crash"))
.append(alloc.reflow(" is not a string:")),
alloc.region(lines.convert_region(region)),
type_comparison(
alloc,
found,
expected_type,
ExpectationContext::WhenCondition,
add_category(alloc, this_is, &category),
wanted,
details,
),
];
Report {
filename,
title: "TYPE MISMATCH".to_string(),
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
}
Reason::LowLevelOpArg { op, arg_index } => {
panic!(
"Compiler bug: argument #{} to low-level operation {:?} was the wrong type!",
@ -1680,6 +1716,10 @@ fn format_category<'b>(
alloc.concat([this_is, alloc.text(" an uniqueness attribute")]),
alloc.text(" of type:"),
),
Crash => {
internal_error!("calls to crash should be unconditionally admitted in any context, unexpected reachability!");
}
Storage(..) | Unknown => (
alloc.concat([this_is, alloc.text(" a value")]),
alloc.text(" of type:"),

View file

@ -12420,4 +12420,69 @@ All branches in an `if` must have the same type!
to be more general?
"###
);
test_report!(
crash_given_non_string,
indoc!(
r#"
crash {}
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This value passed to `crash` is not a string:
4 crash {}
^^
The value is a record of type:
{}
But I can only `crash` with messages of type
Str
"###
);
test_report!(
crash_unapplied,
indoc!(
r#"
crash
"#
),
@r###"
UNAPPLIED CRASH /code/proj/Main.roc
This `crash` doesn't have a message given to it:
4 crash
^^^^^
`crash` must be passed a message to crash with at the exact place it's
used. `crash` can't be used as a value that's passed around, like
functions can be - it must be applied immediately!
"###
);
test_report!(
crash_overapplied,
indoc!(
r#"
crash "" ""
"#
),
@r###"
OVERAPPLIED CRASH /code/proj/Main.roc
This `crash` has too many values given to it:
4 crash "" ""
^^^^^
`crash` must be given exacly one message to crash with.
"###
);
}

View file

@ -58,12 +58,16 @@ pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
pub unsafe extern "C" fn roc_panic(msg: &RocStr, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc crashed with:\n\n\t{}\n", string);
eprintln!("Roc crashed with:\n\n\t{}\n", msg.as_str());
print_backtrace();
std::process::exit(1);
}
1 => {
eprintln!("The program crashed with:\n\n\t{}\n", msg.as_str());
print_backtrace();
std::process::exit(1);