Merge pull request #829 from rtfeldman/functions-in-repl

Functions in repl
This commit is contained in:
Richard Feldman 2020-12-26 23:38:53 -05:00 committed by GitHub
commit 16df2c8bcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 138 additions and 47 deletions

View file

@ -20,6 +20,10 @@ struct Env<'a, 'env> {
home: ModuleId, home: ModuleId,
} }
pub enum ToAstProblem {
FunctionLayout,
}
/// JIT execute the given main function, and then wrap its results in an Expr /// 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. /// so we can display them to the user using the formatter.
/// ///
@ -39,7 +43,7 @@ pub unsafe fn jit_to_ast<'a>(
home: ModuleId, home: ModuleId,
subs: &Subs, subs: &Subs,
ptr_bytes: u32, ptr_bytes: u32,
) -> Expr<'a> { ) -> Result<Expr<'a>, ToAstProblem> {
let env = Env { let env = Env {
arena, arena,
subs, subs,
@ -57,55 +61,57 @@ fn jit_to_ast_help<'a>(
main_fn_name: &str, main_fn_name: &str,
layout: &Layout<'a>, layout: &Layout<'a>,
content: &Content, content: &Content,
) -> Expr<'a> { ) -> Result<Expr<'a>, ToAstProblem> {
match layout { match layout {
Layout::Builtin(Builtin::Int1) => { Layout::Builtin(Builtin::Int1) => Ok(run_jit_function!(lib, main_fn_name, bool, |num| {
run_jit_function!(lib, main_fn_name, bool, |num| bool_to_ast( bool_to_ast(env, num, content)
env, num, content })),
))
}
Layout::Builtin(Builtin::Int8) => { Layout::Builtin(Builtin::Int8) => {
Ok(
// NOTE: this is does not handle 8-bit numbers yet // 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)) run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content)),
)
} }
Layout::Builtin(Builtin::Int64) => { 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, env,
i64_to_ast(env.arena, num), i64_to_ast(env.arena, num),
content content
)) )))
} }
Layout::Builtin(Builtin::Float64) => { 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, env,
f64_to_ast(env.arena, num), f64_to_ast(env.arena, num),
content 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| { run_jit_function!(lib, main_fn_name, &'static str, |string: &'static str| {
str_to_ast(env.arena, env.arena.alloc(string)) str_to_ast(env.arena, env.arena.alloc(string))
}) }),
} ),
Layout::Builtin(Builtin::EmptyList) => { 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, lib,
main_fn_name, main_fn_name,
(*const u8, usize), (*const u8, usize),
|(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) } |(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) }
), )),
Layout::Builtin(other) => { Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", 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 { Expr::Record {
update: None, update: None,
fields: &[], fields: &[],
final_comments: env.arena.alloc([]), final_comments: env.arena.alloc([]),
} }
}), })),
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const u8| match content { let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => { 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); let result_stack_size = layout.stack_size(env.ptr_bytes);
run_jit_function_dynamic_type!( Ok(run_jit_function_dynamic_type!(
lib, lib,
main_fn_name, main_fn_name,
result_stack_size as usize, result_stack_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const u8) } |bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
) ))
} }
Layout::Union(union_layouts) => match content { Layout::Union(union_layouts) => match content {
Content::Structure(FlatType::TagUnion(tags, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
@ -153,7 +159,7 @@ fn jit_to_ast_help<'a>(
let size = layout.stack_size(env.ptr_bytes); let size = layout.stack_size(env.ptr_bytes);
match union_variant { match union_variant {
UnionVariant::Wrapped(tags_and_layouts) => { UnionVariant::Wrapped(tags_and_layouts) => {
run_jit_function_dynamic_type!( Ok(run_jit_function_dynamic_type!(
lib, lib,
main_fn_name, main_fn_name,
size as usize, size as usize,
@ -181,7 +187,7 @@ fn jit_to_ast_help<'a>(
Expr::Apply(loc_tag_expr, output, CalledVia::Space) Expr::Apply(loc_tag_expr, output, CalledVia::Space)
} }
) ))
} }
_ => unreachable!("any other variant would have a different layout"), _ => unreachable!("any other variant would have a different layout"),
} }
@ -201,7 +207,7 @@ fn jit_to_ast_help<'a>(
} }
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => { 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"), Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
} }

View file

@ -131,11 +131,15 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
let content = subs.get(main_fn_var).content; let content = subs.get(main_fn_var).content;
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
let (_, main_fn_layout) = procedures let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
.keys() Some(layout) => layout.clone(),
.find(|(s, _)| *s == main_fn_symbol) None => {
.unwrap() return Ok(ReplOutput::NoProblems {
.clone(); expr: "<function>".to_string(),
expr_type: expr_type_str,
});
}
};
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; 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<R
let lib = module_to_dylib(&env.module, &target, opt_level) let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test"); .expect("Error loading compiled dylib for test");
let answer = unsafe { let res_answer = unsafe {
eval::jit_to_ast( eval::jit_to_ast(
&arena, &arena,
lib, lib,
@ -264,7 +268,15 @@ pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<R
}; };
let mut expr = bumpalo::collections::String::new_in(&arena); let mut expr = bumpalo::collections::String::new_in(&arena);
use eval::ToAstProblem::*;
match res_answer {
Ok(answer) => {
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
}
Err(FunctionLayout) => {
expr.push_str("<function>");
}
}
Ok(ReplOutput::NoProblems { Ok(ReplOutput::NoProblems {
expr: expr.into_bump_str().to_string(), expr: expr.into_bump_str().to_string(),

View file

@ -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", "<function> : 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", "<function> : 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] #[test]
fn type_problem() { fn type_problem() {
expect_failure( expect_failure(

View file

@ -20,7 +20,7 @@ use roc_module::symbol::{
use roc_mono::ir::{ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs, 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::ast::{self, Attempting, StrLiteral, TypeAnnotation};
use roc_parse::header::{ use roc_parse::header::{
ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent, ExposesEntry, ImportsEntry, PackageEntry, PackageOrPath, PlatformHeader, To, TypedIdent,
@ -3508,9 +3508,20 @@ fn add_def_to_module<'a>(
mono_env.subs, mono_env.subs,
) { ) {
Ok(l) => l, Ok(l) => l,
Err(err) => { Err(LayoutProblem::Erroneous) => {
// a host-exposed function is not monomorphized let message = "top level function has erroneous type";
todo!("The host-exposed function {:?} does not have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", symbol, err) 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! // get specialized!
if is_exposed { if is_exposed {
let annotation = def.expr_var; 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( procs.insert_exposed(
symbol, symbol,

View file

@ -1508,7 +1508,6 @@ pub fn specialize_all<'a>(
)); ));
procs.runtime_errors.insert(name, error_msg); 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 // 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 // function). Register it with the current variable, then create a function pointer
// to it in the IR. // to it in the IR.
let layout = layout_cache let res_layout = layout_cache.from_var(env.arena, arg_var, env.subs);
.from_var(env.arena, arg_var, env.subs)
.expect("creating layout does not fail");
// we have three kinds of functions really. Plain functions, closures by capture, // we have three kinds of functions really. Plain functions, closures by capture,
// and closures by unification. Here we record whether this function captures // 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 captures = partial_proc.captured_symbols.captures();
let captured = partial_proc.captured_symbols.clone(); let captured = partial_proc.captured_symbols.clone();
match layout { match res_layout {
Layout::Closure(argument_layouts, closure_layout, ret_layout) if captures => { Ok(Layout::Closure(argument_layouts, closure_layout, ret_layout)) if captures => {
// this is a closure by capture, meaning it itself captures local variables. // 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 // we've defined the closure as a (function_ptr, closure_data) pair already
@ -4958,7 +4955,7 @@ fn reuse_function_symbol<'a>(
stmt stmt
} }
_ => { Ok(layout) => {
procs.insert_passed_by_name( procs.insert_passed_by_name(
env, env,
arg_var, arg_var,
@ -4974,6 +4971,17 @@ fn reuse_function_symbol<'a>(
env.arena.alloc(result), 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))
}
} }
} }
} }