Merge branch 'trunk' into clippy

This commit is contained in:
Folkert de Vries 2020-10-14 12:09:27 +02:00 committed by GitHub
commit 391f5b5e17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 988 additions and 916 deletions

2
Cargo.lock generated
View file

@ -2435,6 +2435,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
@ -2512,6 +2513,7 @@ dependencies = [
"roc_solve",
"roc_types",
"roc_unify",
"ven_ena",
"ven_pretty",
]

View file

@ -44,6 +44,7 @@ target-lexicon = "0.10"
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"

View file

@ -416,26 +416,47 @@ pub fn build_roc_main<'a, 'ctx, 'env>(
env.arena.alloc(roc_main_fn)
}
pub fn promote_to_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
symbol: Symbol,
layout: &Layout<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
let fn_name = layout_ids
.get(symbol, layout)
.to_symbol_string(symbol, &env.interns);
let wrapped = env.module.get_function(&fn_name).unwrap();
make_main_function_help(env, layout, wrapped)
}
pub fn make_main_function<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
main_body: &roc_mono::ir::Stmt<'a>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
// internal main function
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
make_main_function_help(env, layout, roc_main_fn)
}
fn make_main_function_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
roc_main_fn: FunctionValue<'ctx>,
) -> (&'static str, &'a FunctionValue<'ctx>) {
// build the C calling convention wrapper
use inkwell::types::BasicType;
use PassVia::*;
let context = env.context;
let builder = env.builder;
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
// internal main function
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
// build the C calling convention wrapper
let main_fn_name = "$Test.main";
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let main_return_layout = Layout::Struct(&fields);

View file

@ -1019,8 +1019,7 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
main = \shared ->
wrapper = \shared ->
# This should not mutate the original
x =
when List.get (List.set shared 1 7.7) 1 is
@ -1034,7 +1033,7 @@ mod gen_list {
{ x, y }
main [ 2.1, 4.3 ]
wrapper [ 2.1, 4.3 ]
"#
),
(7.7, 4.3),
@ -1047,7 +1046,6 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
shared = [ 2, 4 ]
# This List.set is out of bounds, and should have no effect
@ -1062,8 +1060,6 @@ mod gen_list {
Err _ -> 0
{ x, y }
main {}
"#
),
(4, 4),
@ -1149,6 +1145,9 @@ mod gen_list {
assert_evals_to!(
indoc!(
r#"
app Quicksort provides [ main ] imports []
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
@ -1159,6 +1158,8 @@ mod gen_list {
_ ->
[]
main =
swap 0 1 [ 1, 2 ]
"#
),

View file

@ -482,12 +482,12 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 10 is
x if x == 5 -> 0
_ -> 42
main {}
wrapper {}
"#
),
42,
@ -500,12 +500,12 @@ mod gen_num {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 10 is
x if x == 10 -> 42
_ -> 0
main {}
wrapper {}
"#
),
42,

View file

@ -276,10 +276,10 @@ mod gen_primitives {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
(\a -> a) 5
main {}
wrapper {}
"#
),
5,
@ -292,14 +292,14 @@ mod gen_primitives {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
alwaysFloatIdentity : Int -> (Float -> Float)
alwaysFloatIdentity = \num ->
(\a -> a)
(alwaysFloatIdentity 2) 3.14
main {}
wrapper {}
"#
),
3.14,
@ -402,8 +402,9 @@ mod gen_primitives {
i64
);
}
#[test]
fn gen_nested_defs() {
fn gen_nested_defs_old() {
assert_evals_to!(
indoc!(
r#"
@ -443,6 +444,28 @@ mod gen_primitives {
);
}
#[test]
fn let_x_in_x() {
assert_evals_to!(
indoc!(
r#"
x = 5
answer =
1337
unused =
nested = 17
nested
answer
"#
),
1337,
i64
);
}
#[test]
fn factorial() {
assert_evals_to!(
@ -505,15 +528,36 @@ mod gen_primitives {
);
}
#[test]
#[ignore]
fn top_level_constant() {
assert_evals_to!(
indoc!(
r#"
app LinkedListLen0 provides [ main ] imports []
pi = 3.1415
main =
pi + pi
"#
),
3.1415 + 3.1415,
f64
);
}
#[test]
fn linked_list_len_0() {
assert_evals_to!(
indoc!(
r#"
app LinkedListLen0 provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ]
nil : LinkedList Int
nil = Nil
nil : {} -> LinkedList Int
nil = \_ -> Nil
length : LinkedList a -> Int
length = \list ->
@ -522,7 +566,8 @@ mod gen_primitives {
Cons _ rest -> 1 + length rest
length nil
main =
length (nil {})
"#
),
0,
@ -533,6 +578,7 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_twice_0() {
assert_evals_to!(
indoc!(
@ -559,6 +605,7 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_1() {
assert_evals_to!(
indoc!(
@ -586,6 +633,7 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_twice_1() {
assert_evals_to!(
indoc!(
@ -613,6 +661,7 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_3() {
assert_evals_to!(
indoc!(
@ -640,6 +689,7 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_sum() {
assert_evals_to!(
indoc!(
@ -664,8 +714,8 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_map() {
// `f` is not actually a function, so the call to it fails currently
assert_evals_to!(
indoc!(
r#"

View file

@ -455,12 +455,12 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 2 is
2 if False -> 0
_ -> 42
main {}
wrapper {}
"#
),
42,
@ -473,12 +473,12 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 2 is
2 if True -> 42
_ -> 0
main {}
wrapper {}
"#
),
42,
@ -491,12 +491,12 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
when 2 is
_ if False -> 0
_ -> 42
main {}
wrapper {}
"#
),
42,
@ -674,7 +674,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
x : [ Red, White, Blue ]
x = Blue
@ -686,7 +686,7 @@ mod gen_tags {
y
main {}
wrapper {}
"#
),
3.1,
@ -699,7 +699,7 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
main = \{} ->
wrapper = \{} ->
y =
when 1 + 2 is
3 -> 3
@ -708,7 +708,7 @@ mod gen_tags {
y
main {}
wrapper {}
"#
),
3,

View file

@ -1,9 +1,22 @@
use roc_collections::all::MutSet;
use roc_types::subs::Subs;
use roc_collections::all::{MutMap, MutSet};
pub fn helper_without_uniqueness<'a>(
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
src: &str,
stdlib: roc_builtins::std::StdLib,
leak: bool,
context: &'a inkwell::context::Context,
) -> (
@ -11,26 +24,62 @@ pub fn helper_without_uniqueness<'a>(
Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>,
) {
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_mono::layout::{Layout, LayoutCache};
use std::path::{Path, PathBuf};
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("Test.roc");
let src_dir = Path::new("fake/test/path");
let module_src;
let temp;
if src.starts_with("app") {
// this is already a module
module_src = src;
} else {
// this is an expression, promote it to a module
temp = promote_expr_to_module(src);
module_src = &temp;
}
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
arena,
filename,
&module_src,
stdlib,
src_dir,
exposed_types,
);
let loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
can_problems,
type_problems,
mono_problems,
mut procedures,
interns,
exposed_to_host,
..
} = loaded;
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.iter().copied().nth(0).unwrap();
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let CanExprOut {
loc_expr,
var_store,
var,
constraint,
home,
interns,
problems,
..
} = can_expr(src);
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code
let errors = problems
let errors = can_problems
.into_iter()
.filter(|problem| {
use roc_problem::can::Problem::*;
@ -43,15 +92,18 @@ pub fn helper_without_uniqueness<'a>(
})
.collect::<Vec<roc_problem::can::Problem>>();
let subs = Subs::new(var_store.into());
let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(
unify_problems,
type_problems,
Vec::new(),
"Encountered type mismatches: {:?}",
unify_problems
type_problems,
);
assert_eq!(
mono_problems,
Vec::new(),
"Encountered monomorphization errors: {:?}",
mono_problems,
);
let module = roc_gen::llvm::build::module_from_builtins(context, "app");
@ -66,77 +118,30 @@ pub fn helper_without_uniqueness<'a>(
let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}",
err
)
});
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: context,
context,
interns,
module,
ptr_bytes,
leak: leak,
leak,
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
let mut procs = roc_mono::ir::Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let mut layout_cache = LayoutCache::default();
let main_body =
roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache);
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0,
};
Vec::with_capacity(num_headers)
};
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
let main_body = roc_mono::inc_dec::visit_declaration(
mono_env.arena,
param_map,
mono_env.arena.alloc(main_body),
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procedures.len());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in procs.drain() {
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val));
@ -149,20 +154,34 @@ pub fn helper_without_uniqueness<'a>(
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
eprintln!(
"\n\nFunction {:?} failed LLVM verification in NON-OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap()
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in NON-OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
mode,
);
}
}
let (main_fn_name, main_fn) =
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
&env,
&mut layout_ids,
main_fn_symbol,
&main_fn_layout,
);
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -186,178 +205,6 @@ pub fn helper_without_uniqueness<'a>(
(main_fn_name, errors, execution_engine.clone())
}
pub fn helper_with_uniqueness<'a>(
arena: &'a bumpalo::Bump,
src: &str,
leak: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{infer_expr, uniq_expr};
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_mono::layout::{Layout, LayoutCache};
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr(src);
let errors = problems
.into_iter()
.filter(|problem| {
use roc_problem::can::Problem::*;
// Ignore "unused" problems
match problem {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
_ => true,
}
})
.collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(
unify_problems,
Vec::new(),
"Encountered one or more type mismatches: {:?}",
unify_problems
);
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(context, "app"));
let builder = context.create_builder();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
} else {
roc_gen::llvm::build::OptLevel::Optimize
};
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}",
err
)
});
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: context,
interns,
module,
ptr_bytes,
leak: leak,
exposed_to_host: MutSet::default(),
};
let mut procs = roc_mono::ir::Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let mut layout_cache = LayoutCache::default();
let main_body =
roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache);
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0,
};
Vec::with_capacity(num_headers)
};
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
let main_body = roc_mono::inc_dec::visit_declaration(
mono_env.arena,
param_map,
mono_env.arena.alloc(main_body),
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in procs.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
build_proc(&env, &mut layout_ids, proc, fn_val);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
} else {
eprintln!(
"\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n",
fn_val.get_name().to_str().unwrap()
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
);
}
}
let (main_fn_name, main_fn) =
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
// you're in the version with uniqueness!
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
fpm.run_on(&main_fn);
} else {
panic!("main function {} failed LLVM verification in OPTIMIZED build. Uncomment nearby statements to see more details.", main_fn_name);
}
mpm.run_on(module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
(main_fn_name, execution_engine)
}
// TODO this is almost all code duplication with assert_llvm_evals_to
// the only difference is that this calls uniq_expr instead of can_expr.
// Should extract the common logic into test helpers.
@ -372,11 +219,17 @@ macro_rules! assert_opt_evals_to {
let context = Context::create();
let (main_fn_name, execution_engine) =
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
let stdlib = roc_builtins::unique::uniq_stdlib();
let transform = |success| assert_eq!($transform(success), $expected);
run_jit_function!(execution_engine, main_fn_name, $ty, transform)
let (main_fn_name, errors, execution_engine) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
let expected = $expected;
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
@ -394,9 +247,10 @@ macro_rules! assert_llvm_evals_to {
let arena = Bump::new();
let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib();
let (main_fn_name, errors, execution_engine) =
$crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context);
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
let expected = $expected;
@ -413,29 +267,20 @@ macro_rules! assert_llvm_evals_to {
#[macro_export]
macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty) => {
($src:expr, $expected:expr, $ty:ty) => {{
assert_evals_to!($src, $expected, $ty, (|val| val));
}};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_evals_to!($src, $expected, $ty, $transform, true);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
// Run un-optimized tests, and then optimized tests, in separate scopes.
// These each rebuild everything from scratch, starting with
// parsing the source, so that there's no chance their passing
// or failing depends on leftover state from the previous one.
{
assert_llvm_evals_to!($src, $expected, $ty, (|val| val));
}
{
assert_opt_evals_to!($src, $expected, $ty, (|val| val));
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_llvm_evals_to!($src, $expected, $ty, $transform);
}
{
assert_opt_evals_to!($src, $expected, $ty, $transform);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
}

View file

@ -3,32 +3,6 @@ extern crate bumpalo;
#[macro_use]
pub mod eval;
use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::env::Env;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, MutMap, SendMap};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
/// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)]
@ -68,249 +42,3 @@ where
{
run_test()
}
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_solve::solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
}
pub fn uniq_expr(
expr_str: &str,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
}
pub fn uniq_expr_with(
arena: &Bump,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let home = test_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: mut old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_str);
// double check
let mut var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&mut var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
// TODO what to do with those rigids?
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
// load builtin types
let mut constraint = load_builtin_aliases(stdlib.aliases, constraint, &mut var_store);
constraint.instantiate_aliases(&mut var_store);
let subs2 = Subs::new(var_store.into());
(
loc_expr, output, problems, subs2, var, constraint, home, interns,
)
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
)
});
let mut var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
// since we aren't using modules here.
let mut with_builtins = loc_expr.value;
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
for (symbol, def) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
with_builtins = Expr::LetNonRec(
Box::new(def),
Box::new(Located {
region: Region::zero(),
value: with_builtins,
}),
var_store.fresh(),
SendMap::default(),
);
}
}
let loc_expr = Located {
region: loc_expr.region,
value: with_builtins,
};
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
// TODO determine what to do with those rigids
// for var in introduced_rigids {
// output.ftv.insert(var, format!("internal_{:?}", var).into());
// }
//load builtin types
let mut constraint =
load_builtin_aliases(roc_builtins::std::aliases(), constraint, &mut var_store);
constraint.instantiate_aliases(&mut var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
}
}

View file

@ -43,7 +43,7 @@ const ROC_FILE_EXTENSION: &str = "roc";
/// The . in between module names like Foo.Bar.Baz
const MODULE_SEPARATOR: char = '.';
const SHOW_MESSAGE_LOG: bool = true;
const SHOW_MESSAGE_LOG: bool = false;
macro_rules! log {
() => (if SHOW_MESSAGE_LOG { println!()} else {});
@ -736,9 +736,11 @@ pub fn load_and_typecheck(
) -> Result<LoadedModule, LoadingProblem> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?;
match load(
arena,
filename,
load_start,
stdlib,
src_dir,
exposed_types,
@ -758,9 +760,11 @@ pub fn load_and_monomorphize<'a>(
) -> Result<MonomorphizedModule<'a>, LoadingProblem> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?;
match load(
arena,
filename,
load_start,
stdlib,
src_dir,
exposed_types,
@ -771,6 +775,97 @@ pub fn load_and_monomorphize<'a>(
}
}
pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
stdlib: StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
) -> Result<MonomorphizedModule<'a>, LoadingProblem> {
use LoadResult::*;
let load_start = LoadStart::from_str(arena, filename, src)?;
match load(
arena,
load_start,
stdlib,
src_dir,
exposed_types,
Phase::MakeSpecializations,
)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
}
}
struct LoadStart<'a> {
pub arc_modules: Arc<Mutex<ModuleIds>>,
pub ident_ids_by_module: Arc<Mutex<IdentIdsByModule>>,
pub root_id: ModuleId,
pub root_msg: Msg<'a>,
}
impl<'a> LoadStart<'a> {
pub fn from_path(arena: &'a Bump, filename: PathBuf) -> Result<Self, LoadingProblem> {
let arc_modules = Arc::new(Mutex::new(ModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
// Load the root module synchronously; we can't proceed until we have its id.
let (root_id, root_msg) = {
let root_start_time = SystemTime::now();
load_filename(
arena,
filename,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
root_start_time,
)?
};
Ok(LoadStart {
arc_modules,
ident_ids_by_module,
root_id,
root_msg,
})
}
pub fn from_str(
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
) -> Result<Self, LoadingProblem> {
let arc_modules = Arc::new(Mutex::new(ModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
// Load the root module synchronously; we can't proceed until we have its id.
let (root_id, root_msg) = {
let root_start_time = SystemTime::now();
load_from_str(
arena,
filename,
src,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
root_start_time,
)?
};
Ok(LoadStart {
arc_modules,
ident_ids_by_module,
root_id,
root_msg,
})
}
}
enum LoadResult<'a> {
TypeChecked(LoadedModule),
Monomorphized(MonomorphizedModule<'a>),
@ -821,7 +916,8 @@ enum LoadResult<'a> {
/// to rebuild the module and can link in the cached one directly.)
fn load<'a>(
arena: &'a Bump,
filename: PathBuf,
//filename: PathBuf,
load_start: LoadStart<'a>,
stdlib: StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
@ -829,6 +925,18 @@ fn load<'a>(
) -> Result<LoadResult<'a>, LoadingProblem>
where
{
let LoadStart {
arc_modules,
ident_ids_by_module,
root_id,
root_msg,
} = load_start;
let (msg_tx, msg_rx) = bounded(1024);
msg_tx
.send(root_msg)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
// Reserve one CPU for the main thread, and let all the others be eligible
// to spawn workers. We use .max(2) to enforce that we always
// end up with at least 1 worker - since (.max(2) - 1) will
@ -848,28 +956,6 @@ where
worker_arenas.push(Bump::new());
}
let (msg_tx, msg_rx) = bounded(1024);
let arc_modules = Arc::new(Mutex::new(ModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
// Load the root module synchronously; we can't proceed until we have its id.
let (root_id, root_msg) = {
let root_start_time = SystemTime::now();
load_filename(
arena,
filename,
Arc::clone(&arc_modules),
Arc::clone(&ident_ids_by_module),
root_start_time,
)?
};
msg_tx
.send(root_msg)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
// We'll add tasks to this, and then worker threads will take tasks from it.
let injector = Injector::new();
@ -1598,6 +1684,30 @@ fn load_filename<'a>(
}
}
/// Load a module from a str
/// the `filename` is never read, but used for the module name
fn load_from_str<'a>(
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
module_ids: Arc<Mutex<ModuleIds>>,
ident_ids_by_module: Arc<Mutex<IdentIdsByModule>>,
module_start_time: SystemTime,
) -> Result<(ModuleId, Msg<'a>), LoadingProblem> {
let file_io_start = SystemTime::now();
let file_io_duration = file_io_start.elapsed().unwrap();
parse_header(
arena,
file_io_duration,
filename,
module_ids,
ident_ids_by_module,
src.as_bytes(),
module_start_time,
)
}
#[allow(clippy::too_many_arguments)]
fn send_header<'a>(
name: Located<roc_parse::header::ModuleName<'a>>,
@ -2056,11 +2166,59 @@ fn build_pending_specializations<'a>(
// Add modules' decls to Procs
for decl in decls {
use roc_can::def::Declaration::*;
match decl {
Declare(def) | Builtin(def) => add_def_to_module(
&mut layout_cache,
&mut procs,
&mut mono_env,
def,
&exposed_to_host,
false,
),
DeclareRec(defs) => {
for def in defs {
add_def_to_module(
&mut layout_cache,
&mut procs,
&mut mono_env,
def,
&exposed_to_host,
true,
)
}
}
InvalidCycle(_loc_idents, _regions) => {
todo!("TODO handle InvalidCycle");
}
}
}
let problems = mono_env.problems.to_vec();
Msg::FoundSpecializations {
module_id: home,
solved_subs: roc_types::solved_types::Solved(subs),
ident_ids,
layout_cache,
procs,
problems,
finished_info,
}
}
fn add_def_to_module<'a>(
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
mono_env: &mut roc_mono::ir::Env<'a, '_>,
def: roc_can::def::Def,
exposed_to_host: &MutSet<Symbol>,
is_recursive: bool,
) {
use roc_can::expr::Expr::*;
use roc_can::pattern::Pattern::*;
match decl {
Declare(def) | Builtin(def) => match def.loc_pattern.value {
match def.loc_pattern.value {
Identifier(symbol) => {
let is_exposed = exposed_to_host.contains(&symbol);
@ -2072,8 +2230,6 @@ fn build_pending_specializations<'a>(
loc_body,
..
} => {
// this is a non-recursive declaration
let is_tail_recursive = false;
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
// never gets called by Roc code, it will never
@ -2081,7 +2237,7 @@ fn build_pending_specializations<'a>(
if is_exposed {
let mut pattern_vars = bumpalo::collections::Vec::with_capacity_in(
loc_args.len(),
arena,
mono_env.arena,
);
for (var, _) in loc_args.iter() {
@ -2104,13 +2260,13 @@ fn build_pending_specializations<'a>(
}
procs.insert_named(
&mut mono_env,
&mut layout_cache,
mono_env,
layout_cache,
symbol,
annotation,
loc_args,
*loc_body,
is_tail_recursive,
is_recursive,
ret_var,
);
}
@ -2145,26 +2301,6 @@ fn build_pending_specializations<'a>(
other => {
todo!("TODO gracefully handle Declare({:?})", other);
}
},
DeclareRec(_defs) => {
todo!("TODO support DeclareRec");
}
InvalidCycle(_loc_idents, _regions) => {
todo!("TODO handle InvalidCycle");
}
}
}
let problems = mono_env.problems.to_vec();
Msg::FoundSpecializations {
module_id: home,
solved_subs: roc_types::solved_types::Solved(subs),
ident_ids,
layout_cache,
procs,
problems,
finished_info,
}
}

View file

@ -17,6 +17,7 @@ roc_solve = { path = "../solve" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.2", features = ["collections"] }
ven_ena = { path = "../../vendor/ena" }
[dev-dependencies]
roc_constrain = { path = "../constrain" }

View file

@ -14,7 +14,7 @@ use roc_types::subs::{Content, FlatType, Subs, Variable};
use std::collections::HashMap;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum MonoProblem {
PatternProblem(crate::exhaustive::Error),
}
@ -1365,10 +1365,12 @@ fn specialize_external<'a>(
// unify the called function with the specialized signature, then specialize the function body
let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot();
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_)));
let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_));
debug_assert!(is_valid);
let specialized_body = from_can(env, body, procs, layout_cache);
@ -1376,6 +1378,7 @@ fn specialize_external<'a>(
build_specialized_proc_from_var(env, layout_cache, pattern_symbols, fn_var)?;
// reset subs, so we don't get type errors when specializing for a different signature
layout_cache.rollback_to(cache_snapshot);
env.subs.rollback_to(snapshot);
// TODO WRONG
@ -1490,6 +1493,7 @@ fn specialize_solved_type<'a>(
use roc_types::subs::VarStore;
let snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.snapshot();
let mut free_vars = FreeVars::default();
let mut var_store = VarStore::new_from_subs(env.subs);
@ -1514,10 +1518,12 @@ fn specialize_solved_type<'a>(
.from_var(&env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
env.subs.rollback_to(snapshot);
layout_cache.rollback_to(cache_snapshot);
Ok((proc, layout))
}
Err(error) => {
env.subs.rollback_to(snapshot);
layout_cache.rollback_to(cache_snapshot);
Err(error)
}
}
@ -1630,13 +1636,31 @@ pub fn with_hole<'a>(
}
if let roc_can::pattern::Pattern::Identifier(symbol) = def.loc_pattern.value {
let mut stmt = with_hole(env, cont.value, procs, layout_cache, assigned, hole);
// this is an alias of a variable
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
substitute_in_exprs(env.arena, &mut stmt, symbol, original);
// special-case the form `let x = E in x`
// not doing so will drop the `hole`
match &cont.value {
roc_can::expr::Expr::Var(original) if *original == symbol => {
return with_hole(
env,
def.loc_expr.value,
procs,
layout_cache,
assigned,
hole,
);
}
_ => {}
}
// continue with the default path
let mut stmt = with_hole(env, cont.value, procs, layout_cache, assigned, hole);
// a variable is aliased
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
substitute_in_exprs(env.arena, &mut stmt, symbol, original);
stmt
} else {
with_hole(
env,
def.loc_expr.value,
@ -1645,6 +1669,7 @@ pub fn with_hole<'a>(
symbol,
env.arena.alloc(stmt),
)
}
} else {
// this may be a destructure pattern
let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value);
@ -2687,7 +2712,7 @@ pub fn from_can<'a>(
from_can(env, cont.value, procs, layout_cache)
}
LetNonRec(def, cont, _, _) => {
LetNonRec(def, cont, outer_pattern_vars, outer_annotation) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
if let Closure { .. } = &def.loc_expr.value {
// Now that we know for sure it's a closure, get an owned
@ -2726,14 +2751,120 @@ pub fn from_can<'a>(
}
}
match def.loc_expr.value {
roc_can::expr::Expr::Var(original) => {
let mut rest = from_can(env, cont.value, procs, layout_cache);
// a variable is aliased
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
substitute_in_exprs(env.arena, &mut rest, *symbol, original);
return rest;
} else {
}
roc_can::expr::Expr::LetNonRec(
nested_def,
nested_cont,
nested_pattern_vars,
nested_annotation,
) => {
use roc_can::expr::Expr::*;
// We must transform
//
// let answer = 1337
// in
// let unused =
// let nested = 17
// in
// nested
// in
// answer
//
// into
//
// let answer = 1337
// in
// let nested = 17
// in
// let unused = nested
// in
// answer
let new_def = roc_can::def::Def {
loc_pattern: def.loc_pattern,
loc_expr: *nested_cont,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
};
let new_inner = LetNonRec(
Box::new(new_def),
cont,
outer_pattern_vars,
outer_annotation,
);
let new_outer = LetNonRec(
nested_def,
Box::new(Located::at_zero(new_inner)),
nested_pattern_vars,
nested_annotation,
);
return from_can(env, new_outer, procs, layout_cache);
}
roc_can::expr::Expr::LetRec(
nested_defs,
nested_cont,
nested_pattern_vars,
nested_annotation,
) => {
use roc_can::expr::Expr::*;
// We must transform
//
// let answer = 1337
// in
// let unused =
// let nested = \{} -> nested {}
// in
// nested
// in
// answer
//
// into
//
// let answer = 1337
// in
// let nested = \{} -> nested {}
// in
// let unused = nested
// in
// answer
let new_def = roc_can::def::Def {
loc_pattern: def.loc_pattern,
loc_expr: *nested_cont,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
};
let new_inner = LetNonRec(
Box::new(new_def),
cont,
outer_pattern_vars,
outer_annotation,
);
let new_outer = LetRec(
nested_defs,
Box::new(Located::at_zero(new_inner)),
nested_pattern_vars,
nested_annotation,
);
return from_can(env, new_outer, procs, layout_cache);
}
_ => {
let rest = from_can(env, cont.value, procs, layout_cache);
return with_hole(
env,
def.loc_expr.value,
@ -2744,6 +2875,7 @@ pub fn from_can<'a>(
);
}
}
}
// this may be a destructure pattern
let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value);

View file

@ -226,9 +226,51 @@ impl<'a> Layout<'a> {
}
/// Avoid recomputing Layout from Variable multiple times.
/// We use `ena` for easy snapshots and rollbacks of the cache.
/// During specialization, a type variable `a` can be specialized to different layouts,
/// e.g. `identity : a -> a` could be specialized to `Bool -> Bool` or `Str -> Str`.
/// Therefore in general it's invalid to store a map from variables to layouts
/// But if we're careful when to invalidate certain keys, we still get some benefit
#[derive(Default, Debug)]
pub struct LayoutCache<'a> {
layouts: MutMap<Variable, Result<Layout<'a>, LayoutProblem>>,
layouts: ven_ena::unify::UnificationTable<ven_ena::unify::InPlace<CachedVariable<'a>>>,
}
#[derive(Debug, Clone)]
pub enum CachedLayout<'a> {
Cached(Layout<'a>),
NotCached,
Problem(LayoutProblem),
}
/// Must wrap so we can define a specific UnifyKey instance
/// PhantomData so we can store the 'a lifetime, which is needed to implement the UnifyKey trait,
/// specifically so we can use `type Value = CachedLayout<'a>`
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CachedVariable<'a>(Variable, std::marker::PhantomData<&'a ()>);
impl<'a> CachedVariable<'a> {
fn new(var: Variable) -> Self {
CachedVariable(var, std::marker::PhantomData)
}
}
// use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
impl<'a> ven_ena::unify::UnifyKey for CachedVariable<'a> {
type Value = CachedLayout<'a>;
fn index(&self) -> u32 {
self.0.index()
}
fn from_index(index: u32) -> Self {
CachedVariable(Variable::from_index(index), std::marker::PhantomData)
}
fn tag() -> &'static str {
"CachedVariable"
}
}
impl<'a> LayoutCache<'a> {
@ -244,19 +286,60 @@ impl<'a> LayoutCache<'a> {
// Store things according to the root Variable, to avoid duplicate work.
let var = subs.get_root_key_without_compacting(var);
let cached_var = CachedVariable::new(var);
self.expand_to_fit(cached_var);
use CachedLayout::*;
match self.layouts.probe_value(cached_var) {
Cached(result) => Ok(result),
Problem(problem) => Err(problem),
NotCached => {
let mut env = Env {
arena,
subs,
seen: MutSet::default(),
};
/*
let result = Layout::from_var(&mut env, var);
let cached_layout = match &result {
Ok(layout) => Cached(layout.clone()),
Err(problem) => Problem(problem.clone()),
};
self.layouts
.entry(var)
.or_insert_with(|| Layout::from_var(&mut env, var))
.clone()
*/
Layout::from_var(&mut env, var)
.update_value(cached_var, |existing| existing.value = cached_layout);
result
}
}
}
fn expand_to_fit(&mut self, var: CachedVariable<'a>) {
use ven_ena::unify::UnifyKey;
let required = (var.index() as isize) - (self.layouts.len() as isize) + 1;
if required > 0 {
self.layouts.reserve(required as usize);
for _ in 0..required {
self.layouts.new_key(CachedLayout::NotCached);
}
}
}
pub fn snapshot(
&mut self,
) -> ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>> {
self.layouts.snapshot()
}
pub fn rollback_to(
&mut self,
snapshot: ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>>,
) {
self.layouts.rollback_to(snapshot)
}
}

View file

@ -2006,4 +2006,64 @@ mod test_mono {
),
)
}
#[test]
fn let_x_in_x() {
compiles_to_ir(
indoc!(
r#"
x = 5
answer =
1337
unused =
nested = 17
nested
answer
"#
),
indoc!(
r#"
let Test.1 = 1337i64;
let Test.0 = 5i64;
let Test.3 = 17i64;
ret Test.1;
"#
),
)
}
#[test]
fn let_x_in_x_indirect() {
compiles_to_ir(
indoc!(
r#"
x = 5
answer =
1337
unused =
nested = 17
i = 1
nested
answer
"#
),
indoc!(
r#"
let Test.1 = 1337i64;
let Test.0 = 5i64;
let Test.3 = 17i64;
let Test.4 = 1i64;
ret Test.1;
"#
),
)
}
}

View file

@ -463,21 +463,35 @@ fn solve(
let visit_mark = young_mark.next();
let final_mark = visit_mark.next();
debug_assert!({
next_pools
debug_assert_eq!(
{
let offenders = next_pools
.get(next_rank)
.iter()
.filter(|var| {
subs.get_without_compacting(roc_types::subs::Variable::clone(
var,
))
.rank
.into_usize()
> next_rank.into_usize()
let current = subs.get_without_compacting(
roc_types::subs::Variable::clone(var),
);
current.rank.into_usize() > next_rank.into_usize()
})
.count()
== 0
});
.collect::<Vec<_>>();
let result = offenders.len();
if result > 0 {
dbg!(
&subs,
&offenders,
&let_con.def_types,
&let_con.def_aliases
);
}
result
},
0
);
// pop pool
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
@ -1207,8 +1221,6 @@ fn instantiate_rigids_help(
if let Some(copy) = desc.copy.into_variable() {
return copy;
} else if desc.rank != Rank::NONE {
return var;
}
let make_descriptor = |content| Descriptor {

View file

@ -681,7 +681,7 @@ fn unify_shared_tags(
merge(subs, ctx, Structure(flat_type))
} else {
mismatch!()
mismatch!("Problem with Tag Union")
}
}
@ -911,7 +911,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
RigidVar(_) | Structure(_) | Alias(_, _, _) => {
// Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same.
mismatch!()
mismatch!("Rigid with {:?}", &other)
}
Error => {
// Error propagates.