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,
}
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<Expr<'a>, 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<Expr<'a>, 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) => {
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))
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"),
}

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 expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => layout.clone(),
None => {
return Ok(ReplOutput::NoProblems {
expr: "<function>".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<R
let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let answer = unsafe {
let res_answer = unsafe {
eval::jit_to_ast(
&arena,
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);
use eval::ToAstProblem::*;
match res_answer {
Ok(answer) => {
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
}
Err(FunctionLayout) => {
expr.push_str("<function>");
}
}
Ok(ReplOutput::NoProblems {
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]
fn type_problem() {
expect_failure(

View file

@ -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,

View file

@ -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))
}
}
}
}