diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 4a20f0601d..5e4d07e6bc 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -20,6 +20,10 @@ struct Env<'a, 'env> { home: ModuleId, } +pub enum ToAstProblem { + FunctionLayout, +} + /// JIT execute the given main function, and then wrap its results in an Expr /// so we can display them to the user using the formatter. /// @@ -39,7 +43,7 @@ pub unsafe fn jit_to_ast<'a>( home: ModuleId, subs: &Subs, ptr_bytes: u32, -) -> Expr<'a> { +) -> Result, ToAstProblem> { let env = Env { arena, subs, @@ -57,55 +61,57 @@ fn jit_to_ast_help<'a>( main_fn_name: &str, layout: &Layout<'a>, content: &Content, -) -> Expr<'a> { +) -> Result, ToAstProblem> { match layout { - Layout::Builtin(Builtin::Int1) => { - run_jit_function!(lib, main_fn_name, bool, |num| bool_to_ast( - env, num, content - )) - } + Layout::Builtin(Builtin::Int1) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| { + bool_to_ast(env, num, content) + })), Layout::Builtin(Builtin::Int8) => { - // NOTE: this is does not handle 8-bit numbers yet - run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)) + Ok( + // NOTE: this is does not handle 8-bit numbers yet + run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)), + ) } Layout::Builtin(Builtin::Int64) => { - run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( + Ok(run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast( env, i64_to_ast(env.arena, num), content - )) + ))) } Layout::Builtin(Builtin::Float64) => { - run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast( + Ok(run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast( env, f64_to_ast(env.arena, num), content - )) + ))) } - Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => { + Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => Ok( run_jit_function!(lib, main_fn_name, &'static str, |string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) - }) - } + }), + ), Layout::Builtin(Builtin::EmptyList) => { - run_jit_function!(lib, main_fn_name, &'static str, |_| { Expr::List(&[]) }) + Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| { + Expr::List(&[]) + })) } - Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!( + Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!( lib, main_fn_name, (*const u8, usize), |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) } - ), + )), Layout::Builtin(other) => { todo!("add support for rendering builtin {:?} to the REPL", other) } - Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &u8, |_| { + Layout::PhantomEmptyStruct => Ok(run_jit_function!(lib, main_fn_name, &u8, |_| { Expr::Record { update: None, fields: &[], final_comments: env.arena.alloc([]), } - }), + })), Layout::Struct(field_layouts) => { let ptr_to_ast = |ptr: *const u8| match content { Content::Structure(FlatType::Record(fields, _)) => { @@ -134,12 +140,12 @@ fn jit_to_ast_help<'a>( let result_stack_size = layout.stack_size(env.ptr_bytes); - run_jit_function_dynamic_type!( + Ok(run_jit_function_dynamic_type!( lib, main_fn_name, result_stack_size as usize, |bytes: *const u8| { ptr_to_ast(bytes as *const u8) } - ) + )) } Layout::Union(union_layouts) => match content { Content::Structure(FlatType::TagUnion(tags, _)) => { @@ -153,7 +159,7 @@ fn jit_to_ast_help<'a>( let size = layout.stack_size(env.ptr_bytes); match union_variant { UnionVariant::Wrapped(tags_and_layouts) => { - run_jit_function_dynamic_type!( + Ok(run_jit_function_dynamic_type!( lib, main_fn_name, size as usize, @@ -181,7 +187,7 @@ fn jit_to_ast_help<'a>( Expr::Apply(loc_tag_expr, output, CalledVia::Space) } - ) + )) } _ => unreachable!("any other variant would have a different layout"), } @@ -201,7 +207,7 @@ fn jit_to_ast_help<'a>( } Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => { - todo!("add support for rendering functions in the REPL") + Err(ToAstProblem::FunctionLayout) } Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"), } diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 1d9427842a..a67dda0ddb 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -131,11 +131,15 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result layout.clone(), + None => { + return Ok(ReplOutput::NoProblems { + expr: "".to_string(), + expr_type: expr_type_str, + }); + } + }; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -249,7 +253,7 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result Result { + answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); + } + Err(FunctionLayout) => { + expr.push_str(""); + } + } Ok(ReplOutput::NoProblems { expr: expr.into_bump_str().to_string(), diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index c57a8459d8..ed94b3db4c 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -462,6 +462,40 @@ mod repl_eval { ); } + #[test] + fn identity_lambda() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("\\x -> x", " : a -> a"); + } + + #[test] + fn stdlib_function() { + // Even though this gets unwrapped at runtime, the repl should still + // report it as a record + expect_success("Num.abs", " : Num a -> Num a"); + } + + #[test] + fn too_few_args() { + expect_failure( + "Num.add 2", + indoc!( + r#" + ── TOO FEW ARGS ──────────────────────────────────────────────────────────────── + + The add function expects 2 arguments, but it got only 1: + + 4│ Num.add 2 + ^^^^^^^ + + Roc does not allow functions to be partially applied. Use a closure to + make partial application explicit. + "# + ), + ); + } + #[test] fn type_problem() { expect_failure( diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 435a0b8c02..9a76a70eda 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -20,7 +20,7 @@ use roc_module::symbol::{ use roc_mono::ir::{ CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs, }; -use roc_mono::layout::{Layout, LayoutCache}; +use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_parse::ast::{self, Attempting, StrLiteral, TypeAnnotation}; use roc_parse::header::{ ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent, @@ -3508,9 +3508,20 @@ fn add_def_to_module<'a>( mono_env.subs, ) { Ok(l) => l, - Err(err) => { - // a host-exposed function is not monomorphized - todo!("The host-exposed function {:?} does not have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", symbol, err) + Err(LayoutProblem::Erroneous) => { + let message = "top level function has erroneous type"; + procs.runtime_errors.insert(symbol, message); + return; + } + Err(LayoutProblem::UnresolvedTypeVar(v)) => { + let message = format!( + "top level function has unresolved type variable {:?}", + v + ); + procs + .runtime_errors + .insert(symbol, mono_env.arena.alloc(message)); + return; } }; @@ -3542,9 +3553,29 @@ fn add_def_to_module<'a>( // get specialized! if is_exposed { let annotation = def.expr_var; - let layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err| - todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err) - ); + + let layout = match layout_cache.from_var( + mono_env.arena, + annotation, + mono_env.subs, + ) { + Ok(l) => l, + Err(LayoutProblem::Erroneous) => { + let message = "top level function has erroneous type"; + procs.runtime_errors.insert(symbol, message); + return; + } + Err(LayoutProblem::UnresolvedTypeVar(v)) => { + let message = format!( + "top level function has unresolved type variable {:?}", + v + ); + procs + .runtime_errors + .insert(symbol, mono_env.arena.alloc(message)); + return; + } + }; procs.insert_exposed( symbol, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index dc08c98176..22c0a14b37 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1508,7 +1508,6 @@ pub fn specialize_all<'a>( )); procs.runtime_errors.insert(name, error_msg); - panic!("failed to specialize {:?}", name); } } } @@ -4869,9 +4868,7 @@ fn reuse_function_symbol<'a>( // this symbol is a function, that is used by-name (e.g. as an argument to another // function). Register it with the current variable, then create a function pointer // to it in the IR. - let layout = layout_cache - .from_var(env.arena, arg_var, env.subs) - .expect("creating layout does not fail"); + let res_layout = layout_cache.from_var(env.arena, arg_var, env.subs); // we have three kinds of functions really. Plain functions, closures by capture, // and closures by unification. Here we record whether this function captures @@ -4879,8 +4876,8 @@ fn reuse_function_symbol<'a>( let captures = partial_proc.captured_symbols.captures(); let captured = partial_proc.captured_symbols.clone(); - match layout { - Layout::Closure(argument_layouts, closure_layout, ret_layout) if captures => { + match res_layout { + Ok(Layout::Closure(argument_layouts, closure_layout, ret_layout)) if captures => { // this is a closure by capture, meaning it itself captures local variables. // we've defined the closure as a (function_ptr, closure_data) pair already @@ -4958,7 +4955,7 @@ fn reuse_function_symbol<'a>( stmt } - _ => { + Ok(layout) => { procs.insert_passed_by_name( env, arg_var, @@ -4974,6 +4971,17 @@ fn reuse_function_symbol<'a>( env.arena.alloc(result), ) } + Err(LayoutProblem::Erroneous) => { + let message = format!("The {:?} symbol has an erroneous type", symbol); + Stmt::RuntimeError(env.arena.alloc(message)) + } + Err(LayoutProblem::UnresolvedTypeVar(v)) => { + let message = format!( + "The {:?} symbol contains a unresolved type var {:?}", + symbol, v + ); + Stmt::RuntimeError(env.arena.alloc(message)) + } } } }